В чем разница между HEAD ^ и HEAD ~ в Git? - PullRequest
623 голосов
/ 08 февраля 2010

Когда я указываю объект коммитов предка в Git, я путаюсь между HEAD^ и HEAD~.

Оба имеют «нумерованную» версию, такую ​​как HEAD^3 и HEAD~2.

Они кажутся мне очень похожими или одинаковыми, но есть ли различия между тильдой и каретой?

Ответы [ 13 ]

582 голосов
/ 08 февраля 2010

Эмпирические правила

  • Используйте ~ большую часть времени - чтобы вернуться на несколько поколений, обычно то, что вы хотите
  • Используйте ^ в коммитах слияния - потому что у них два или более (непосредственных) родителя

Мнемоника:

  • Тильда ~ выглядит почти линейно и хочет идти назад по прямой
  • Каретка ^ предлагает интересный отрезок дерева или развилку на дороге

Тильда

Раздел «Задание редакций» документации git rev-parse определяет ~ как

<rev>~<n>, например, master~3
Суффикс ~<n> к параметру ревизии означает объект фиксации, который является предком n поколения указанного объекта фиксации, следуя только первым родителям. [Например,] <rev>~3 эквивалентно <rev>^^^, что эквивалентно <rev>^1^1^1 & hellip;

Вы можете получить к родителям любой коммит, а не только HEAD. Вы также можете перемещаться назад через поколения: например, master~2 означает прародителя кончика главной ветви, отдавая предпочтение первому родителю при фиксации слияния.

Каре

История Git нелинейная: ориентированный ациклический граф (DAG) или дерево. Для коммита только с одним родителем 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 на английском языке - это второй родительский элемент коммита слияния, который является пра-прародителем (три поколения назад) текущего наконечника филиал topic.

В вышеупомянутом разделе git rev-parse документации прослеживается множество путей через условную историю git. Время течет в основном вниз. Коммиты D, F, B и A являются коммитами слияния.

Вот иллюстрация Джона Лелигера. Оба узла фиксации B и C являются родителями узла фиксации A. Родительские коммиты располагаются слева направо.

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 Tools - Выбор редакции из книги Pro Git .

Порядок родительских комиссий

Коммит 89e4fcb0dd из собственной истории git является коммитом слияния, как указывает git show 89e4fcb0dd в строке заголовка Merge, которая отображает имена объектов непосредственных предков.

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 […]

Мы можем подтвердить порядок, попросив 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 для сокращенных родителей.

$ git log -1 --pretty=%p 89e4fcb0dd
c670b1f876 649bf3a42f b67d40adbb
322 голосов
/ 21 сентября 2012

Разница между HEAD^ и HEAD~ хорошо описана на иллюстрации (Джон Лоелигер), найденной на http://www.kernel.org/pub/software/scm/git/docs/git-rev-parse.html.

Эта документация может быть немного неясной для начинающих, поэтому я воспроизвел эту иллюстрацию ниже:

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
260 голосов
/ 17 мая 2014

Оба ~ и ^ сами по себе относятся к родителю коммита (~~ и ^^ оба относятся к коммиту деда и т. Д.) Но они различаются по значению при использовании с числами:

  • ~2 означает вверх на два уровня в иерархии через первого родителя, если у фиксации более одного родителя

  • ^2 означает второго родителя , где у коммита более одного родителя (то есть, потому что это слияние)

Их можно объединить, поэтомуHEAD~2^3 означает третий родительский коммит HEAD.

259 голосов
/ 18 марта 2015

Мои два цента ...

enter image description here

46 голосов
/ 27 марта 2017

Вот очень хорошее объяснение, взятое дословно из http://www.paulboxley.com/blog/2011/06/git-caret-and-tilde:

ref~ является сокращением для ref~1 и означает первого родителя коммита. ref~2 означает первого родителя первого коммита. ref~3 означает первого родителя первого родителя коммита. И так далее.

ref^ является сокращением для ref^1 и означает первого родителя коммита. Но разница между ними заключается в том, что ref^2 означает второго родителя коммита (помните, что коммиты могут иметь двух родителей при слиянии).

Операторы ^ и ~ могут быть объединены.

enter image description here

31 голосов
/ 08 февраля 2010

Формат ^<n> позволяет вам выбрать n-го родителя коммита (актуально при слиянии). Формат ~<n> позволяет выбрать фиксацию n-го предка, всегда следуя за первым родителем. См. git-rev-parse документацию для некоторых примеров.

18 голосов
/ 20 февраля 2014

Стоит отметить, что git также имеет синтаксис для отслеживания «откуда-то-ты-пришел» / «хочу-вернуться-сейчас» - например, HEAD@{1} будет ссылаться на место, откуда ты перескочил на новое место фиксации.

В основном HEAD@{} переменные фиксируют историю движения HEAD, и вы можете решить использовать конкретную голову, изучив повторные журналы git с помощью команды git reflog.

Пример:

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 reset HEAD~2 - и затем после этого я хочу переместить свою HEAD вернуться к д - git reset HEAD@{1}.

12 голосов
/ 28 августа 2017

Упрощенно

  • ~ указывает на предков
  • ^ указывает родителей

Вы можете указать одну или несколько веток при объединении. Тогда в коммите есть два или более родителей, а затем ^ полезно указать родителей.

Предположим, вы находитесь на ветке A и у вас есть еще две ветви: B и C .

В каждой ветви три последних коммита:

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

Если сейчас на ветке A вы выполняете команду:

git merge B C

тогда вы объединяете три ветви вместе (здесь ваше объединение коммит имеет трех родителей)

и

~ указывает 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 раз ~) и: HEAD^...^ ( n раз ^).

Уведомление 2 :

  • HEAD^3 означает не и HEAD^^^ (первое указывает C3 , а второе указывает A1 ),

и вообще :

  • HEAD^1 совпадает с HEAD^,
  • но для n > 1: HEAD^n всегда не то же самое, что HEAD^...^ ( n раз ~).
10 голосов
/ 08 февраля 2010

HEAD ^^^ совпадает с HEAD ~ 3, выбирая третий коммит перед HEAD

HEAD ^ 2 указывает второй заголовок в коммите слияния

9 голосов
/ 26 января 2018

TLDR

~ это то, что вы хотите большую часть времени, оно ссылается на прошлые коммиты на текущую ветку

^ ссылается на родителей (git-merge создает второго или более родителей)

A ~ всегда совпадает с A ^
~~ всегда совпадает с A ^^ и т. Д.
A ~ 2 не то же самое, что A ^ 2, однако,
потому что ~ 2 это сокращение от ~~
в то время как ^ 2 не является сокращением для чего-либо, это означает, что второй родитель

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...