当我在 Git 中指定祖先提交对象时,我在HEAD^
和HEAD~
之间感到困惑。
两者都有一个 “编号” 版本,例如HEAD^3
和HEAD~2
。
在我看来,它们看起来非常相似或相同,但是波浪号和插入符号之间是否有任何区别?
~
来回溯几代人,通常是您想要的^
- 因为它们有两个或更多(直接)父母助记符:
~
几乎是线性的外观,并希望在一条直线上,以留后路^
表示道路上有趣的树或叉子部分git rev-parse
文档的“指定修订” 部分将~
定义为
<rev>~<n>
,例如master~3
修订版参数的后缀~<n>
表示作为命名提交对象的第n代祖先的提交对象,仅跟随第一个父对象。例如,<rev>~3
〜3 等同于<rev>^^^
,等同于<rev>^1^1^1
……
您可以进行任何提交的父母,而不仅仅是HEAD
。您还可以几代后退:例如, master~2
表示 master 分支尖端的祖父母,在合并提交时偏向于第一个父代。
Git 历史是非线性的:有向无环图(DAG)或树。对于只有一个父母的提交, rev~
和rev^
是同一件事。插入符选择器对于合并提交很有用,因为每个提交都是两个或多个父母的孩子 - 并限制了从生物学借来的语言。
HEAD^
表示当前分支的尖端的第一个直接父代。 HEAD^
是HEAD^1
缩写,您也可以根据需要寻址HEAD^2
等。 git rev-parse
文档的同一部分将其定义为
<rev>^
,例如HEAD^
,v1.5.1^0
版本参数的后缀^
表示该提交对象的第一个父对象。^<n>
表示第n个父级([例如]<rev>^
等效于<rev>^1
)。作为特殊规则,<rev>^0
表示提交本身,并且当<rev>
是引用提交对象的标记对象的对象名称时使用。
这些说明符或选择器可以任意链接,例如, topic~3^2
是合并提交的第二个父级,它是分支topic
当前提示的曾祖父母(返回三代)。
git rev-parse
文档的上述部分通过概念性的 git 历史记录跟踪了许多路径。时间通常向下流动。提交 D,F,B 和 A 是合并提交。
这是乔恩 · 洛里格(Jon Loeliger)的插图。提交节点 B 和 C 都是提交节点 A 的父级。父级提交按从左到右的顺序排列。 (注意,
git log --graph
命令以相反的顺序显示历史记录。)G H I J \ / \ / D E F \ | / \ \ | / | \|/ | B C \ / \ / A A = = A^0 B = A^ = A^1 = A~1 C = A^2 D = A^^ = A^1^1 = A~2 E = B^2 = A^^2 F = B^3 = A^^3 G = A^^^ = A^1^1^1 = A~3 H = D^2 = B^^2 = A^^^2 = A~2^2 I = F^ = B^3^ = A^^3^ J = F^2 = B^3^2 = A^^3^2
运行下面的代码以创建一个 git 存储库,其历史记录与引用的插图匹配。
#! /usr/bin/env perl
use strict;
use warnings;
use subs qw/ postorder /;
use File::Temp qw/ mkdtemp /;
my %sha1;
my %parents = (
A => [ qw/ B C / ],
B => [ qw/ D E F / ],
C => [ qw/ F / ],
D => [ qw/ G H / ],
F => [ qw/ I J / ],
);
sub postorder {
my($root,$hash) = @_;
my @parents = @{ $parents{$root} || [] };
postorder($_, $hash) for @parents;
return if $sha1{$root};
@parents = map "-p $sha1{$_}", @parents;
chomp($sha1{$root} = `git commit-tree @parents -m "$root" $hash`);
die "$0: git commit-tree failed" if $?;
system("git tag -a -m '$sha1{$root}' '$root' '$sha1{$root}'") == 0 or die "$0: git tag failed";
}
$0 =~ s!^.*/!!; # / fix Stack Overflow highlighting
my $repo = mkdtemp "repoXXXXXXXX";
chdir $repo or die "$0: chdir: $!";
system("git init") == 0 or die "$0: git init failed";
chomp(my $tree = `git write-tree`); die "$0: git write-tree failed" if $?;
postorder 'A', $tree;
system "git update-ref HEAD $sha1{A}"; die "$0: git update-ref failed" if $?;
system "git update-ref master $sha1{A}"; die "$0: git update-ref failed" if $?;
# for browsing history - http://blog.kfish.org/2010/04/git-lola.html
system "git config alias.lol 'log --graph --decorate --pretty=oneline --abbrev-commit'";
system "git config alias.lola 'log --graph --decorate --pretty=oneline --abbrev-commit --all'";
git lol
和git lola
的新的一次性仓库中添加别名,因此您可以按以下方式查看历史记录:
$ git lol
* 29392c8 (HEAD -> master, tag: A) A
|\
| * a1ef6fd (tag: C) C
| |
| \
*-. \ 8ae20e9 (tag: B) B
|\ \ \
| | |/
| | * 03160db (tag: F) F
| | |\
| | | * 9df28cb (tag: J) J
| | * 2afd329 (tag: I) I
| * a77cb1f (tag: E) E
* cd75703 (tag: D) D
|\
| * 3043d25 (tag: H) H
* 4ab0473 (tag: G) G
请注意,在您的计算机上,SHA-1 对象名称与上面的名称不同,但是标签允许您按名称处理提交并检查您的理解。
$ git log -1 --format=%f $(git rev-parse A^)
B
$ git log -1 --format=%f $(git rev-parse A~^3~)
I
$ git log -1 --format=%f $(git rev-parse A^2~)
F
git rev-parse
文档中的“Specification Revisions”充满了重要信息,值得深入阅读。另请参阅《 Pro Git 》一书中的 “ Git 工具 - 版本选择”。
git 自己的历史记录中的提交89e4fcb0dd git show 89e4fcb0dd
指示的那样,带有显示直接祖先对象名称的 Merge 标头行。
commit 89e4fcb0dd01b42e82b8f27f9a575111a26844df Merge: c670b1f876 649bf3a42f b67d40adbb Author: Junio C Hamano <[email protected]> Date: Mon Oct 29 10:15:31 2018 +0900 Merge branches 'bp/reset-quiet' and 'js/mingw-http-ssl' into nd/config-split […]
我们可以通过要求git rev-parse
来顺序显示 89e4fcb0dd 的直接父母来确认排序。
$ git rev-parse 89e4fcb0dd^1 89e4fcb0dd^2 89e4fcb0dd^3
c670b1f876521c9f7cd40184bf7ed05aad843433
649bf3a42f344e71b1b5a7f562576f911a1f7423
b67d40adbbaf4f5c4898001bf062a9fd67e43368
查询不存在的第四父级将导致错误。
$ git rev-parse 89e4fcb0dd^4
89e4fcb0dd^4
fatal: ambiguous argument '89e4fcb0dd^4': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'
如果只想提取父母,则将漂亮的%P
格式用于完整的哈希
$ git log -1 --pretty=%P 89e4fcb0dd
c670b1f876521c9f7cd40184bf7ed05aad843433 649bf3a42f344e71b1b5a7f562576f911a1f7423 b67d40adbbaf4f5c4898001bf062a9fd67e43368
或%p
对于缩写父母)。
$ git log -1 --pretty=%p 89e4fcb0dd
c670b1f876 649bf3a42f b67d40adbb
之间的差HEAD^
和HEAD~
深受图示(通过 Jon Loeliger)描述上找到http://www.kernel.org/pub/software/scm/git/docs/git-rev-parse.html 。
该文档可能对初学者来说有些晦涩,因此我复制了以下插图:
G H I J
\ / \ /
D E F
\ | / \
\ | / |
\|/ |
B C
\ /
\ /
A
A = = A^0
B = A^ = A^1 = A~1
C = A^2
D = A^^ = A^1^1 = A~2
E = B^2 = A^^2
F = B^3 = A^^3
G = A^^^ = A^1^1^1 = A~3
H = D^2 = B^^2 = A^^^2 = A~2^2
I = F^ = B^3^ = A^^3^
J = F^2 = B^3^2 = A^^3^2
~
和^
均指代提交的父项( ~~
和^^
均指代祖父母的提交等),但是当与数字一起使用时,它们的含义不同:
~2
表示层次结构中的两个级别,如果提交具有多个父级,则通过第一个父级
^2
表示提交中有多个父项的第二个父项(即,因为它是合并项)
这些可以组合,所以HEAD~2^3
表示HEAD
的祖父母提交的第三个父提交。