Git 中的 HEAD^ 和 HEAD~ 有什么区别? [英] What's the difference between HEAD^ and HEAD~ in Git?

查看:71
本文介绍了Git 中的 HEAD^ 和 HEAD~ 有什么区别?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当我在 Git 中指定祖先提交对象时,我在 HEAD^HEAD~ 之间感到困惑.

两者都有一个编号"版本,如 HEAD^3HEAD~2.

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

解决方案

经验法则

  • 大部分时间使用 ~ — 返回几代,通常是您想要的
  • 在合并提交上使用 ^ — 因为它们有两个或多个(直接)父项

助记符:

  • 波浪号~ 外观几乎是线性的,想要直线倒退
  • Caret ^ 表示树或岔路口的有趣部分

波浪号

指定修订"部分git rev-parse 文档~ 定义为

<块引用>

~,例如master~3
修订参数的后缀 ~ 表示提交对象是指定提交对象的 nth 代祖先,如下只有第一个父母.例如,~3 等价于 ^^^ 等价于 ^1^1^1 ...

您可以访问任何提交的父级,而不仅仅是 HEAD.您也可以通过几代向后移动:例如,master~2 表示 master 分支尖端的祖父级,在合并提交时优先于第一个父级.

插入符号

Git 历史是非线性的:有向无环图 (DAG) 或树.对于只有一个父级的提交,rev~rev^ 表示相同的意思.插入符号选择器在合并提交中变得非常有用,因为每个提交都是两个或多个父母的孩子——并且从生物学中借用了语言.

HEAD^ 表示当前分支的tip的第一个直接父级.HEAD^HEAD^1 的缩写,你也可以酌情寻址HEAD^2 等等.git 的同一部分rev-parse 文档 将其定义为

<块引用>

^, eg HEAD^, v1.5.1^0
修订版参数的后缀 ^ 表示该提交对象的第一个父级.^ 表示 nth 个父级([eg] ^ 等价于 ^1).作为特殊规则,^0 表示提交本身,当 是引用提交的标记对象的对象名称时使用对象.

示例

这些说明符或选择器可以任意链接,eg,英文中的topic~3^2是合并提交的第二个父级,也就是曾祖父级(三代后)分支的当前提示topic.

git rev-parse上述部分文档 通过名义上的 git 历史跟踪了许多路径.时间通常向下流动.提交 D、F、B 和 A 是合并提交.

<块引用>

这是 Jon Loeliger 的插图.提交节点 B 和 C 都是提交节点 A 的父节点.父提交按从左到右的顺序排列.(注意,git log --graph 命令以相反的顺序显示历史记录.)

G H I J//德法 |/\ |/||/|乙丙//一种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使用严格;使用警告;使用 subs qw/后序/;使用 File::Temp qw/mkdtemp/;我的 %sha1;我的 %parents = (A=>[ qw/B C/],B=>[ qw/D E F/],C=>[ qw/F/],D=>[ qw/G H/],F=>[ qw/I J/],);子后序{我的($root,$hash) = @_;我的@parents = @{ $parents{$root} ||[] };@parents 的 postorder($_, $hash);如果 $sha1{$root} 返回;@parents = map "-p $sha1{$_}", @parents;chomp($sha1{$root} = `git commit-tree @parents -m "$root" $hash`);die "$0: git commit-tree failed";如果 $?;system("git tag -a -m '$sha1{$root}' '$root' '$sha1{$root}'") == 0 or die "$0: git tag failed";}$0 =~ s!^.*/!!;#/修复堆栈溢出高亮我的 $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";如果 $?;后序 'A', $tree;系统git update-ref HEAD $sha1{A}";die "$0: git update-ref failed";如果 $?;系统git update-ref master $sha1{A}";die "$0: git update-ref failed";如果 $?;# 浏览历史记录 - http://blog.kfish.org/2010/04/git-lola.html系统git config alias.lol 'log --graph --decorate --pretty=oneline --abbrev-commit'";系统git config alias.lola 'log --graph --decorate --pretty=oneline --abbrev-commit --all'";

它仅在新的一次性存储库中为 git lol<添加别名/code> 和 git lola 这样你就可以像

一样查看历史记录

$ git 哈哈* 29392c8 (HEAD -> master, tag: A) A||* a1ef6fd(标签:C)C|||*-. 8ae20e9(标签:B)B|  |||/||* 03160db(标签:F)F||||||* 9df28cb(标签:J)J||* 2afd329(标签:我)我|* a77cb1f(标签:E)E* cd75703(标签:D)D||* 3043d25(标签:H)H* 4ab0473(标签:G)G

请注意,在您的机器上,SHA-1 对象名称将与上述名称不同,但标签允许您按名称处理提交并检查您的理解.

$ git log -1 --format=%f $(git rev-parse A^)乙$ git log -1 --format=%f $(git rev-parse A~^3~)一世$ git log -1 --format=%f $(git rev-parse A^2~)F

git rev-parse指定修订"代码> 文档 包含大量信息,值得深入阅读.另请参阅Git 工具 - 修订选择Pro Git.

父提交顺序

来自 git 自身历史的提交 89e4fcb0dd 是一次合并提交,如 git show 89e4fcb0dd 用显示直系祖先对象名称的 Merge 标题行表示.

<块引用>

commit 89e4fcb0dd01b42e82b8f27f9a575111a26844df合并:c670b1f876 649bf3a42f b67d40adbb作者:Junio 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^3c670b1f876521c9f7cd40184bf7ed05aad843433649bf3a42f344e71b1b5a7f562576f911a1f7423b67d40adbbaf4f5c4898001bf062a9fd67e43368

查询不存在的第四个父级会导致错误.

$ git rev-parse 89e4fcb0dd^489e4fcb0dd^4致命:模棱两可的参数89e4fcb0dd^4":未知的修订版或路径不在工作树中.使用--"将路径与修订分开,如下所示:'git <命令>[<修订版>...] -- [<文件>...]'

如果您只想提取父母,请使用 漂亮格式 %P 用于完整哈希

$ git log -1 --pretty=%P 89e4fcb0ddc670b1f876521c9f7cd40184bf7ed05aad843433 649bf3a42f344e71b1b5a7f562576f911a1f7423 b67d40adbbaf4f5c48906607a48906607e

%p 用于缩写父母.

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

When I specify an ancestor commit object in Git, I'm confused between HEAD^ and HEAD~.

Both have a "numbered" version like HEAD^3 and HEAD~2.

They seem very similar or the same to me, but are there any differences between the tilde and the caret?

解决方案

Rules of thumb

  • Use ~ most of the time — to go back a number of generations, usually what you want
  • Use ^ on merge commits — because they have two or more (immediate) parents

Mnemonics:

  • Tilde ~ is almost linear in appearance and wants to go backward in a straight line
  • Caret ^ suggests an interesting segment of a tree or a fork in the road

Tilde

The "Specifying Revisions" section of the git rev-parse documentation defines ~ as

<rev>~<n>, e.g. master~3
A suffix ~<n> 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, <rev>~3 is equivalent to <rev>^^^ which is equivalent to <rev>^1^1^1

You can get to parents of any commit, not just HEAD. You can also move back through generations: for example, master~2 means the grandparent of the tip of the master branch, favoring the first parent on merge commits.

Caret

Git history is nonlinear: a directed acyclic graph (DAG) or tree. For a commit with only one parent, rev~ and rev^ mean the same thing. The caret selector becomes useful with merge commits because each one is the child of two or more parents — and strains language borrowed from biology.

HEAD^ means the first immediate parent of the tip of the current branch. HEAD^ is short for HEAD^1, and you can also address HEAD^2 and so on as appropriate. The same section of the git rev-parse documentation defines it as

<rev>^, e.g. HEAD^, v1.5.1^0
A suffix ^ to a revision parameter means the first parent of that commit object. ^<n> means the nth parent ([e.g.] <rev>^ is equivalent to <rev>^1). As a special rule, <rev>^0 means the commit itself and is used when <rev> is the object name of a tag object that refers to a commit object.

Examples

These specifiers or selectors can be chained arbitrarily, e.g., topic~3^2 in English is the second parent of the merge commit that is the great-grandparent (three generations back) of the current tip of the branch topic.

The aforementioned section of the git rev-parse documentation traces many paths through a notional git history. Time flows generally downward. Commits D, F, B, and A are merge commits.

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

Run the code below to create a git repository whose history matches the quoted illustration.

#! /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'";

It adds aliases in the new throwaway repo only for git lol and git lola so you can view history as in

$ 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

Note that on your machine the SHA-1 object names will differ from those above, but the tags allow you to address commits by name and check your understanding.

$ 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

The "Specifying Revisions" in the git rev-parse documentation is full of great information and is worth an in-depth read. See also Git Tools - Revision Selection from the book Pro Git.

Order of Parent Commits

The commit 89e4fcb0dd from git’s own history is a merge commit, as git show 89e4fcb0dd indicates with the Merge header line that displays the immediate ancestors’ object names.

commit 89e4fcb0dd01b42e82b8f27f9a575111a26844df
Merge: c670b1f876 649bf3a42f b67d40adbb
Author: 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 […]

We can confirm the ordering by asking git rev-parse to show 89e4fcb0dd’s immediate parents in sequence.

$ git rev-parse 89e4fcb0dd^1 89e4fcb0dd^2 89e4fcb0dd^3
c670b1f876521c9f7cd40184bf7ed05aad843433
649bf3a42f344e71b1b5a7f562576f911a1f7423
b67d40adbbaf4f5c4898001bf062a9fd67e43368

Querying the non-existent fourth parent results in an error.

$ 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>...]'

If you want to extract the parents only, use pretty format %P for the full hashes

$ git log -1 --pretty=%P 89e4fcb0dd
c670b1f876521c9f7cd40184bf7ed05aad843433 649bf3a42f344e71b1b5a7f562576f911a1f7423 b67d40adbbaf4f5c4898001bf062a9fd67e43368

or %p for abbreviated parents.

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

这篇关于Git 中的 HEAD^ 和 HEAD~ 有什么区别?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆