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

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

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


当前回答

我的意见…

其他回答

以下是从http://www.paulboxley.com/blog/2011/06/git-caret-and-tilde上逐字逐句摘录的一个很好的解释:

Ref ~是Ref ~1的简写,表示提交的第一个父对象。Ref ~2表示提交的第一个父对象的第一个父对象。Ref ~3表示提交的第一个父级的第一个父级的第一个父级。等等。 Ref ^是Ref ^1的简写,表示提交的第一个父对象。但两者的不同之处在于ref^2表示提交的第二个父对象(记住,当提交是一个merge时,它们可以有两个父对象)。 ^和~操作符可以组合使用。

简单地说:

~指定祖先 ^指定父母

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

假设你在分支A上,还有两个分支:B和C。

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

A: a1, a2, a3 B: b1 b2 b3 C: c1 c3 c3

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

git merge B C

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

and

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

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

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

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

~或^的下一个用法是在前面的字符指定的提交上下文中。

注意1:

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

和一般:

HEAD~n总是等于:HEAD~…~ (n次~)和to: HEAD^…^ (n乘以^)

注意2:

HEAD^3和HEAD^^^不一样(第一个表示C3,第二个表示A1),

和一般:

HEAD^1和HEAD^是一样的, 但是对于n > 1: HEAD^n总是不等于HEAD^…^ (n乘以^)

TLDR

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

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

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

经验法则

大多数情况下使用~来回溯几代,通常是你想要的 在合并提交时使用^,因为它们有两个或多个(直接)父节点

助记符:

波浪线~在外观上几乎是线性的,并且想要以直线向后走 卡瑞特表示一棵树的有趣部分或道路的岔路口

波浪号

git rev-parse文档的“指定修订”部分将~定义为

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

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

插入符号

Git历史是非线性的:有向无环图(DAG)或树。对于只有一个父节点的commit, 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在英语中是合并提交的第二个父节点,它是当前分支主题尖端的曾祖父节点(三代之前)。

前面提到的git rev-parse文档在git历史中跟踪了许多路径。时间通常是向下流动的。提交D、F、B和A是合并提交。

Here is an illustration, by Jon Loeliger. Both commit nodes B and C are parents of commit node A. Parent commits are ordered left-to-right. (N.B. The git log --graph command displays history in the opposite order.) 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文档中的“指定修订”包含大量信息,值得深入阅读。参见《Git工具-版本选择》一书。

父级提交顺序

从git自己的历史记录中提交89e4fcb0dd是一个合并提交,正如git show 89e4fcb0dd用merge标题行指示的那样,该标题行显示了直接祖先的对象名称。

提交89年e4fcb0dd01b42e82b8f27f9a575111a26844df 合并:c670b1f876 649bf3a42f b67d40adbb 作者:juno C Hamano <gitster@pobox.com> 日期:2018年10月29日星期一10:15:31 +0900 合并分支“bp/reset-quiet”和“js/mingw-http-ssl”到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表示缩写parents。

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

值得注意的是,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~1
290e035 HEAD@{17}: reset: moving to HEAD~2
eb48179 HEAD@{18}: reset: moving to HEAD~2
0aee51f HEAD@{19}: reset: moving to HEAD@{5}
eb48179 HEAD@{20}: reset: moving to HEAD~2
0aee51f 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 commmit
35a7332 HEAD@{26}: checkout: moving from temp2_new_br to temp2_new_br
35a7332 HEAD@{27}: commit (amend): 3rd commmit
72c0be8 HEAD@{28}: commit (amend): 3rd commmit

一个例子可以是我做了本地提交a->b->c->d,然后我回去丢弃2个提交来检查我的代码- git重置HEAD~2 -然后在那之后我想把我的HEAD移回d - git重置HEAD@{1}。