Git中HEAD^和HEAD~的区别是什么?

当我在Git中指定一个祖先提交对象时,我混淆了HEAD^HEAD~

两者都有“编号”版本,如HEAD^3HEAD~2

在我看来它们非常相似或相同,但是波浪号和插入符号之间有什么不同吗?

284361 次浏览

HEAD^^^HEAD~3相同,选择HEAD之前的第三次提交

HEAD^2指定合并提交中的第二个头

^<n>格式允许您选择提交的第n个父节点(与合并相关)。~<n>格式允许您选择第n个祖先提交,始终紧跟在第一个父提交之后。有关一些示例,请参阅git-rev-parse的文档。

  • HEAD~指定“分支”上的第一个父节点。

  • HEAD^允许你选择一个特定的提交父节点

一个例子:

如果你想要遵循一个分支,你必须指定像这样的东西

master~209^2~15

经验法则

  • 大多数时候使用~ -回溯到许多代,通常是你想要的
  • 在合并提交时使用^——因为它们有两个或多个(直接的)父节点

助记符:

  • 波浪线~在外观上几乎是线性的,并且希望以直线向后移动
  • 插入符号^表示一棵树的有趣部分或道路的岔路口

波浪号

#0文档的“指定修订”部分定义~

< p > # 2
修订参数的后缀~<n>表示提交对象是已命名提交对象的nth代祖先,仅在第一代父对象之后。例如,<rev>~3等价于<rev>^^^<rev>^^^等价于<rev>^1^1^1

你可以联系任何承诺的父母,而不仅仅是HEAD。您还可以通过代向后移动:例如,master~2表示主分支尖端的祖父辈,在合并提交时倾向于第一个父辈。

脱字符号

Git历史是非线性的:有向无环图(DAG)或树。对于只有一个父节点的提交,rev~rev^意味着相同的事情。插入符号选择器在合并提交中变得非常有用,因为每个插入符号都是两个或多个父节点的子节点——这是从生物学中借来的语言。

HEAD^表示当前分支顶端的第一个立即父结点。HEAD^HEAD^1的缩写,您还可以适当地称呼HEAD^2等等。6号将其定义为

< p > # 3
修订参数的后缀^表示该提交对象的第一个父对象。^<n>表示nth父元素([如。]<rev>^相当于<rev>^1)。作为一个特殊的规则,<rev>^0表示提交本身,当<rev>是引用提交对象的标记对象的对象名时使用

例子

这些说明符或选择器可以任意链接,如。topic~3^2在英语中是合并提交的第二个父级,它是分支topic的当前尖端的曾祖父级(三代之前)。

前面提到的#0文档部分在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^0B = A^   = A^1     = A~1C = A^2D = A^^  = A^1^1   = A~2E = B^2  = A^^2F = B^3  = A^^3G = A^^^ = A^1^1^1 = A~3H = D^2  = B^^2    = A^^^2  = A~2^2I = 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 highlightingmy $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.htmlsystem "git config alias.lol  'log --graph --decorate --pretty=oneline --abbrev-commit'";system "git config alias.lola 'log --graph --decorate --pretty=oneline --abbrev-commit --all'";

它在新的一次性回购中仅为#0和#1添加别名,以便您可以在中查看历史

$ 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自身历史记录的提交89年e4fcb0dd是一个合并提交,正如git show 89e4fcb0dd所示,它使用merge标题行显示直接祖先的对象名称。

commit 89e4fcb0dd01b42e82b8f27f9a575111a26844dfMerge: c670b1f876 649bf3a42f b67d40adbbAuthor: Junio C Hamano <gitster@pobox.com>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^3c670b1f876521c9f7cd40184bf7ed05aad843433649bf3a42f344e71b1b5a7f562576f911a1f7423b67d40adbbaf4f5c4898001bf062a9fd67e43368

查询不存在的第四个父节点将导致错误。

$ git rev-parse 89e4fcb0dd^489e4fcb0dd^4fatal: 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 89e4fcb0ddc670b1f876521c9f7cd40184bf7ed05aad843433 649bf3a42f344e71b1b5a7f562576f911a1f7423 b67d40adbbaf4f5c4898001bf062a9fd67e43368

或者%p表示缩写的父母。

$ git log -1 --pretty=%p 89e4fcb0ddc670b1f876 649bf3a42f b67d40adbb

HEAD^HEAD~之间的区别在http://www.kernel.org/pub/software/scm/git/docs/git-rev-parse.html的插图(由Jon Loeliger绘制)中得到了很好的描述。

这个文档对于初学者来说可能有点晦涩,所以我复制了下面的插图:

G   H   I   J\ /     \ /D   E   F\  |  / \\ | /   |\|/    |B     C\   /\ /AA =      = A^0B = A^   = A^1     = A~1C = A^2D = A^^  = A^1^1   = A~2E = B^2  = A^^2F = B^3  = A^^3G = A^^^ = A^1^1^1 = A~3H = D^2  = B^^2    = A^^^2  = A~2^2I = F^   = B^3^    = A^^3^J = F^2  = B^3^2   = A^^3^2

值得注意的是,git也有一个用于跟踪“从哪里来的”/“想要返回-现在”的语法——例如,HEAD@{1}将引用您跳转到新提交位置的位置。

基本上,HEAD@{}变量捕获HEAD移动的历史,您可以通过使用命令git reflog查看git的reflogs来决定使用特定的HEAD。

例子:

0aee51f HEAD@{0}: reset: moving to HEAD@{5}290e035 HEAD@{1}: reset: moving to HEAD@{7}0aee51f HEAD@{2}: reset: moving to HEAD@{3}290e035 HEAD@{3}: reset: moving to HEAD@{3}9e77426 HEAD@{4}: reset: moving to HEAD@{3}290e035 HEAD@{5}: reset: moving to HEAD@{3}0aee51f HEAD@{6}: reset: moving to HEAD@{3}290e035 HEAD@{7}: reset: moving to HEAD@{3}9e77426 HEAD@{8}: reset: moving to HEAD@{3}290e035 HEAD@{9}: reset: moving to HEAD@{1}0aee51f HEAD@{10}: reset: moving to HEAD@{4}290e035 HEAD@{11}: reset: moving to HEAD^9e77426 HEAD@{12}: reset: moving to HEAD^eb48179 HEAD@{13}: reset: moving to HEAD~f916d93 HEAD@{14}: reset: moving to HEAD~0aee51f HEAD@{15}: reset: moving to HEAD@{5}f19fd9b HEAD@{16}: reset: moving to HEAD~1290e035 HEAD@{17}: reset: moving to HEAD~2eb48179 HEAD@{18}: reset: moving to HEAD~20aee51f HEAD@{19}: reset: moving to HEAD@{5}eb48179 HEAD@{20}: reset: moving to HEAD~20aee51f HEAD@{21}: reset: moving to HEAD@{1}f916d93 HEAD@{22}: reset: moving to HEAD@{1}0aee51f HEAD@{23}: reset: moving to HEAD@{1}f916d93 HEAD@{24}: reset: moving to HEAD^0aee51f HEAD@{25}: commit (amend): 3rd commmit35a7332 HEAD@{26}: checkout: moving from temp2_new_br to temp2_new_br35a7332 HEAD@{27}: commit (amend): 3rd commmit72c0be8 HEAD@{28}: commit (amend): 3rd commmit

举个例子,我做了本地提交a->b->c->d,然后我回去丢弃2个提交来检查我的代码- git reset HEAD~2 -然后我想把我的HEAD移回d - git reset HEAD@{1}

~^各自指的都是提交的父对象(~~^^都指的是祖父母提交,等等),但是当它们与数字一起使用时,它们的含义有所不同:

  • ~2表示在层次结构中向上两层,如果一个提交有多个父级,则通过第一个父级

  • ^2表示,第二个父元素,其中提交有多个父元素(即,因为它是一个merge)

它们可以组合在一起,因此HEAD~2^3意味着HEAD的祖父母提交的第三个父提交。

我的意见…

enter image description here

这里有一个非常好的解释,一字不差地引用了第0条:

ref~ref~1的简写,表示提交的第一个父对象。ref~2表示提交的第一个父级的第一个父级。ref~3意味着提交的第一个父级的第一个父级的第一个父级。等等。

ref^ref^1的简写,表示提交的第一个父对象。但两者的不同之处在于,ref^2意味着提交的第二个父节点(记住,当提交是一个merge时,它们可以有两个父节点)。

^~操作符可以组合。

enter image description here

# 0:

  • ~指定祖先
  • ^指定父母

合并时可以指定一个或多个分支。然后一个提交有两个或多个父级,然后^用于表示父级。

假设您在分支一个上,并且还有两个分支:BC

在每个分支上的最后三个提交是:

  • 一个: A1A2A3
  • B: B1B2B3
  • C: C1C3C3

如果现在在分支一个上执行命令:

git merge B C

然后你将三个分支组合在一起(这里你的合并提交有三个父分支)

而且

~表示第一个分支中的第n个祖先,因此

  • HEAD~表示A3
  • HEAD~2表示A2
  • HEAD~3表示A1

^表示第n个父节点,因此

  • HEAD^表示A3
  • HEAD^2表示B3
  • HEAD^3表示C3

~^的下一次相邻使用是在前面字符指定的提交上下文中。

# 0:

  • HEAD~3总是等于:HEAD~~~和:HEAD^^^(每个表示A1),

# 0:

  • HEAD~n总是等于:HEAD~...~(n乘以~)和:HEAD^...^(n乘以^)。

# 0:

  • HEAD^3表示,与HEAD^^^相同(第一个表示C3,第二个表示A1),

# 0:

  • 0和1是一样的,
  • 但对于n >1: HEAD^n总是HEAD^...^相同(n乘以^)。

简单地说,对于亲子关系的第一级(祖先,继承,世系等),HEAD^和HEAD~都指向同一个提交,它位于HEAD(提交)之上的一个父级。

此外,HEAD^ = HEAD^1 = HEAD~ = HEAD~1。但是HEAD^^ != HEAD^2 != HEAD~2。然而头^^ =头~2。继续读下去。

在第一级亲子关系之外,事情变得更加棘手,特别是如果工作分支/主分支有合并(来自其他分支)。还有一个插入符号的语法问题,HEAD^^ = HEAD~2(它们是等价的)但是HEAD^^ != HEAD^2(它们是完全不同的两个东西)。

每个/插入符号指的是HEAD的第一个父代,这就是为什么串在一起的插入符号相当于波浪号表达式,因为它们指的是第一个父代的(第一个父代的)第一个父代,等等,严格基于连接插入符号上的数字或波浪号后面的数字(无论哪种方式,它们都意味着相同的事情),即保持第一个父代并向上x代。

HEAD~2(或HEAD^^)指的是在层次结构中当前提交(HEAD)上/上两级祖先的提交,这意味着HEAD的祖父级提交。

另一方面,HEAD^2并不是指第一个父节点的第二个父节点的提交,而是指第二个父节点的提交。这是因为插入符号表示提交的父级,后面的数字表示引用的是哪个/什么父级提交(在插入符号后面没有数字的情况下是第一个父级,因为它是数字1的简写,表示第一个父级)。与插入符号不同,后面的数字并不表示向上的层次结构的另一层,而是表示横向的层次结构的多少层,需要找到正确的父层(提交)。与波浪号表达式中的数字不同,它在层次结构中只有一个父级,无论(紧接)插入符号后面的数字是什么。插入符号的尾数不是向上,而是在层次结构中横向计算父字符(在父字符向上的级别上,相当于连续插入字符的数量)。

所以HEAD^3等于HEAD提交的第三个父结点(不是曾祖结点,即HEAD^^^ AND HEAD~3)。

TLDR

~是你大多数时候想要的,它引用过去提交到当前分支

^引用父节点(git-merge创建第二个或更多父节点)

A~总是等于A^
A~~总是和A^^一样,以此类推
A~2并不等于A^2,
因为~2是~~的缩写
虽然^2不是任何东西的缩写,但它意味着第二个父元素

HEAD~和HEAD^之间区别的一个实际例子:

HEAD^ VS HEAD~ .

~代表父母。

^,如果它有两个或更多的父节点,比如合并提交。我们可以选择父节点中的第二个或另一个。

如果只有一个东西,比如(HEAD~或HEAD^),它的结果是相同

如果你想知道在命令中是输入HEAD^还是HEAD~,请输入随便用哪一种:

它们都是同一个提交的名称 -当前提交的第一个父类。

master~master^也是如此——这两个名字都是master的第一个父类。

就像2 + 22 x 2都是4一样,它们是不同的方式到达那里,但答案是一样的。

这回答了一个问题:Git中HEAD^和HEAD~之间有什么区别?

如果你只是做了一个合并(所以你当前的提交有一个以上的父),或者你仍然对插入号和波浪号的工作原理感兴趣,请参阅其他答案(我不会在这里重复)以获得深入的解释,以及如何重复使用它们(例如HEAD~~~),或与数字一起使用(例如HEAD^2)。否则,我希望这个答案能帮你节省一些时间。

^分支选择器
# 0 < br >通过移动到所选的分支(在提交树上后退一步)

,选择(合并)提交的第二个分支

~提交选择器
# 0 < br >在默认/选择的分支

上向后提交2次

将~和^相对引用定义为PARENT选择器是迄今为止我在互联网上看到的主要定义——包括官方的Git Book。是的,它们是PARENT选择器,但这个“解释”的问题是;是它完全违背了我们的目标:这是如何区分两者…:)

另一个问题是当我们被鼓励使用^ BRANCH选择器进行COMMIT选择时(也就是HEAD^ === HEAD~)同样,是的,你可以这样使用它,但这不是它的设计目的。^ BRANCH选择器的向后移动行为是一个副作用,而不是它的目的只有在合并提交时,才能将一个数字分配给^ BRANCH选择器。因此,只有在需要在分支机构之间进行选择时,才能充分利用其容量。在fork中表达选择的最直接的方法是踏上所选的路径/分支——这是在提交树上后退一步。

.它只是一个副作用,不是主要用途

OP:当我在Git中指定一个祖先提交对象时,我混淆了HEAD^和HEAD~。

Git中的HEAD^和HEAD~有什么区别?

HEAD^(插入号)和HEAD~(波浪号)之间的区别在于它们如何从指定的起点向后遍历历史,在这种特殊情况下HEAD

波浪号~

<rev>~[<n>] =选择<n>th代祖先,只在第一个*父代之后

插入符号^

<rev>^[<n>] =选择第一代祖先的<n>th父元素

*第一个父节点总是在merge的左边,例如,在被合并到的分支上提交。

把~和^连在一起

如下图所示,两个选择器可以组合使用。还要注意,不要使用HEAD作为起点,可以使用任何常规的引用,如< >强分支< / >强< >强标记< / >强甚至<强>提交哈希< / >强

此外,根据要选择的祖先,可以互换使用,如下表所示。

 Git中相对引用的说明

关于这个主题的详细介绍可以在第一篇博客文章中找到。