What's the difference between HEAD^ and HEAD~ in Git?
当我在Git中指定祖先提交对象时,我在
两者都有"编号"版本,例如
在我看来,它们看起来非常相似或相同,但是波浪号和插入符号之间是否有任何区别?
经验法则
-
大多数时候使用
~ -可以追溯到很多代,通常是您想要的 -
在合并提交上使用
^ -因为它们有两个或多个(直接)父级
口诀:
-
Tilde
~ 的外观几乎是线性的,并且想要沿直线向后移动 -
插入符
^ 提示道路上有趣的树或叉子部分
波浪号
, e.g. ~ master~3
A suffix~ to a revision parameter means the commit object that is the nth generation ancestor of the named commit object, following only the first parents. [For example,]is equivalent to ~3 which is equivalent to ^^^ … ^1^1^1
您可以进行任何提交的父母,而不仅仅是
插入符号
Git历史是非线性的:有向无环图(DAG)或树。对于只有一个父对象的提交,
, e.g. ^ HEAD^ ,v1.5.1^0
A suffix^ to a revision parameter means the first parent of that commit object.^ means the nth parent ([e.g.]is equivalent to ^ ). As a special rule, ^1 means the commit itself and is used when ^0 is the object name of a tag object that refers to a commit object.
例子
这些说明符或选择器可以任意链接,例如,英语中的
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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 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存储库,其历史记录与引用的插图匹配。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | #! /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'"; |
它仅在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | $ 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对象名称与上面的名称不同,但是标签允许您按名称处理提交并检查您的理解。
1 2 3 4 5 6 | $ 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自己的历史记录中的提交89e4fcb0dd是合并提交,如
1
2
3
4
5
6 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 […]
我们可以要求
1 2 3 4 | $ git rev-parse 89e4fcb0dd^1 89e4fcb0dd^2 89e4fcb0dd^3 c670b1f876521c9f7cd40184bf7ed05aad843433 649bf3a42f344e71b1b5a7f562576f911a1f7423 b67d40adbbaf4f5c4898001bf062a9fd67e43368 |
查询不存在的第四父级会导致错误。
1 2 3 4 5 | $ 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>...]' |
如果只想提取父母,请使用漂亮的格式
1 2 | $ git log -1 --pretty=%P 89e4fcb0dd c670b1f876521c9f7cd40184bf7ed05aad843433 649bf3a42f344e71b1b5a7f562576f911a1f7423 b67d40adbbaf4f5c4898001bf062a9fd67e43368 |
或
1 2 | $ git log -1 --pretty=%p 89e4fcb0dd c670b1f876 649bf3a42f b67d40adbb |
http://www.kernel.org/pub/software/scm/git/docs/git-rev-parse.html上的插图(Jon Loeliger)很好地描述了
本文档对于初学者可能有点晦涩,因此我复制了以下插图:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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 表示提交具有多个父项的第二个父项(即,因为它是合并项)
这些可以组合,所以
我的两分钱
这是从http://www.paulboxley.com/blog/2011/06/git-caret-and-tilde逐字记录的很好解释:
ref~ is shorthand forref~1 and means the commit's first parent.ref~2 means the commit's first parent's first parent.ref~3 means the commit's first parent's first parent's first parent. And so on.
ref^ is shorthand forref^1 and means the commit's first parent. But where the two differ is thatref^2 means the commit's second parent (remember, commits can have two parents when they are a merge).The
^ and~ operators can be combined.
值得注意的是,git还具有用于跟踪"从何处来" /"现在要回去"的语法-例如,
基本上
例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | 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 |
一个例子可能是我做了local-commits a-> b-> c-> d,然后我放弃了2次提交以检查我的代码-
简单地:
-
~ 指定祖先 -
^ 指定父母
合并时可以指定一个或多个分支。然后一个提交有两个或两个以上的父母,然后
假设您在分支A上,并且还有两个分支:B和C。
在每个分支上,最后三个提交是:
- 答:A1,A2,A3
- B:B1,B2,B3
- C:C1,C3,C3
如果现在在分支A上,则执行以下命令:
1 | git merge B C |
然后您将三个分支合并在一起(这里您的合并提交有三个父级)
和
-
HEAD~ 表示A3 -
HEAD~2 表示A2 -
HEAD~3 表示A1
-
HEAD^ 表示A3 -
HEAD^2 表示B3 -
HEAD^3 表示C3
注意1:
-
HEAD~3 始终等于:HEAD~~~ 和HEAD^^^ (每个表示A1),
通常:
-
HEAD~n 始终等于:HEAD~...~ (n倍~ )和: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并非简写形式,它表示第二个父级
HEAD ^^^与HEAD?3相同,选择HEAD之前的第三次提交
HEAD ^ 2指定合并提交中的第二个头
-
HEAD?指定"分支"上的第一个父对象
-
HEAD ^允许您选择提交的特定父项
一个例子:
如果要遵循侧分支,则必须指定类似
1 | master~209^2~15 |
HEAD?和HEAD ^之间差异的实际示例
因此,如果只有一个东西((HEAD?或HEAD ^)),则结果相同。
简而言之,对于第一级亲子关系(祖先,继承,世系等),HEAD ^和HEAD?都指向同一提交,即(位于)HEAD(提交)上方的一个父级。
此外,HEAD ^ = HEAD ^ 1 = HEAD?= HEAD?1。但是HEAD ^^!= HEAD ^ 2!= HEAD?2。 HEAD ^^ = HEAD?2。继续阅读。
除了第一级的育儿之外,事情变得更加棘手,特别是如果工作分支/主分支已经合并(来自其他分支)。插入符号还存在语法问题,HEAD ^^ = HEAD?2(它们等效),但是HEAD ^^!= HEAD ^ 2(它们完全是两个不同的东西)。
每个/插入符号都指HEAD的第一父代,这就是为什么将在一起的插入号等同于代字号表达式的原因,因为它们严格地基于连接的插入号上的数字来引用第一父代(第一父代)的第一父代等。或波浪号后面的数字(无论哪种方式,它们都代表相同的意思),即与第一个父母住在一起并世世代代。
HEAD?2(或HEAD ^^)指的是提交,它是层次结构中当前提交(HEAD)的上一级/上一级的提交,表示HEAD的祖父母提交。
另一方面,HEAD ^ 2并不是指第一个父母的第二个父母的提交,而只是指第二个父母的提交。这是因为插入符号表示提交的父级,并且后面的数字表示引用哪个/哪个父提交(在插入符后没有数字的情况下,第一个父级[因为它是该数字的简写形式为1,表示第一个父级])。与插入符号不同,此后的数字并不表示层次结构向上另一级,而是表示在层次结构中侧身有多少个层次,需要找到正确的父级(提交)。与代字号表达式中的数字不同,该字符在层次结构中仅是一个父级,而不管(立即)插入号中的数字如何。对于整个层次结构中的父母,插入号的尾数不是向上,而是侧向计数(在向上的父级水平上,等于连续的插入号的数量)。
所以HEAD ^ 3等于HEAD提交的第三个父对象(不是曾祖父母,这就是HEAD ^^^ AND HEAD?3会是...)。