What's the difference between HEAD^ and HEAD~ in Git?

前端 未结 16 1839
醉酒成梦
醉酒成梦 2020-11-22 10:43

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

Both have a \"numbered\" version like HEAD^3<

相关标签:
16条回答
  • 2020-11-22 11:24

    Simplistically:

    • ~ specifies ancestors
    • ^ specifies parents

    You can specify one or more branches when merging. Then a commit has two or more parents and then ^ is useful to indicate parents.

    Suppose you are on branch A and you have two more branches: B and C.

    On each branch the three last commits are:

    • A: A1, A2, A3
    • B: B1, B2, B3
    • C: C1, C3, C3

    If now on branch A you execute the command:

    git merge B C
    

    then you are combining three branches together (here your merge commit has three parents)

    and

    ~ indicates the n'th ancestor in the first branch, so

    • HEAD~ indicates A3
    • HEAD~2 indicates A2
    • HEAD~3 indicates A1

    ^ indicates the n'th parent, so

    • HEAD^ indicates A3
    • HEAD^2 indicates B3
    • HEAD^3 indicates C3

    The next use of ~ or ^ next to each other is in the context of the commit designated by previous characters.

    Notice 1:

    • HEAD~3 is always equal to: HEAD~~~ and to: HEAD^^^ (every indicates A1),

            and generally:

    • HEAD~n is always equal to: HEAD~...~ (n times ~) and to: HEAD^...^ (n times ^).

    Notice 2:

    • HEAD^3 is not the same as HEAD^^^ (the first indicates C3 and the second indicates A1),

            and generally:

    • HEAD^1 is the same as HEAD^,
    • but for n > 1: HEAD^n is always not the same as HEAD^...^ (n times ~).
    0 讨论(0)
  • 2020-11-22 11:25

    My two cents...

    enter image description here

    0 讨论(0)
  • 2020-11-22 11:26

    The difference between HEAD^ and HEAD~ is well described by the illustration (by Jon Loeliger) found on http://www.kernel.org/pub/software/scm/git/docs/git-rev-parse.html.

    This documentation can be a bit obscure to beginners so I've reproduced that illustration below:

    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
    
    0 讨论(0)
  • 2020-11-22 11:28

    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
    
    0 讨论(0)
  • 2020-11-22 11:29

    Both ~ and ^ on their own refer to the parent of the commit (~~ and ^^ both refer to the grandparent commit, etc.) But they differ in meaning when they are used with numbers:

    • ~2 means up two levels in the hierarchy, via the first parent if a commit has more than one parent

    • ^2 means the second parent where a commit has more than one parent (i.e. because it's a merge)

    These can be combined, so HEAD~2^3 means HEAD's grandparent commit's third parent commit.

    0 讨论(0)
  • 2020-11-22 11:34

    The ^<n> format allows you to select the nth parent of the commit (relevant in merges). The ~<n> format allows you to select the nth ancestor commit, always following the first parent. See git-rev-parse's documentation for some examples.

    0 讨论(0)
提交回复
热议问题