Qual é a diferença entre HEAD ^ e HEAD ~ no Git?

756

Quando eu especifico um objeto de confirmação de ancestral no Git, fico confuso entre HEAD^e HEAD~.

Ambos têm uma versão "numerada" como HEAD^3e HEAD~2.

Eles parecem muito parecidos ou iguais comigo, mas existem diferenças entre o til e o sinal de intercalação?

TK.
fonte
64
é ruim colar links, eu sei, mas esta é a melhor explicação que encontrei e há uma imagem nela. paulboxley.com/blog/2011/06/git-caret-and-tilde
igor:
4
Os links são especialmente ruins quando estão quebrados. Essa é a razão pela qual é mais seguro para responder à pergunta que ajudam a prevenir isso por causa da capacidade de copiar e colar algumas explicações :)
Samuel

Respostas:

763

Regras de ouro

  • Use a ~maior parte do tempo - para voltar várias gerações, geralmente o que você deseja
  • Use ^em consolidação de mesclagem - porque eles têm dois ou mais pais (imediatos)

Mnemônicos:

  • A ~aparência é quase linear e quer retroceder em linha reta
  • Caret ^sugere um segmento interessante de uma árvore ou uma bifurcação na estrada

Til

A seção "Especificando revisões" da git rev-parsedocumentação define ~como

<rev>~<n>, Por exemplo,master~3
um sufixo ~<n>a um parâmetro de revisão significa que o objeto que é a cometer n º ancestral geração do chamado cometer objeto, seguindo apenas os primeiros pais. Por exemplo, <rev>~3é equivalente a <rev>^^^que é equivalente a <rev>^1^1^1

Você pode obter os pais de qualquer confirmação, não apenas HEAD. Você também pode voltar através de gerações: por exemplo, master~2significa o avô da ponta do ramo mestre, favorecendo o primeiro pai na confirmação de mesclagem.

Caret

A história do Git é não linear: um gráfico acíclico direcionado (DAG) ou árvore. Para um commit com apenas um pai rev~e rev^significa a mesma coisa. O seletor de intercalação se torna útil com confirmações de mesclagem, porque cada um é filho de dois ou mais pais - e estirpe a linguagem emprestada da biologia.

HEAD^significa o primeiro pai imediato da ponta do ramo atual. HEAD^é a abreviação de HEAD^1, e você também pode abordar HEAD^2e assim por diante, conforme apropriado. A mesma seção da git rev-parsedocumentação define como

<rev>^, por exemplo HEAD^ ,v1.5.1^0
um sufixo ^para um parâmetro de revisão significa o primeiro pai desse objeto de confirmação. ^<n>significa o n th -mãe ([ por exemplo ] <rev>^é equivalente a <rev>^1). Como regra especial, <rev>^0significa o commit em si e é usado quando <rev>é o nome do objeto de um objeto de tag que se refere a um objeto de commit.

Exemplos

Esses especificadores ou seletores podem ser encadeados arbitrariamente, por exemplo , topic~3^2em inglês é o segundo pai do commit de mesclagem que é o bisavô (três gerações atrás) da ponta atual do ramo topic.

A seção mencionada acima da git rev-parsedocumentação rastreia muitos caminhos através de um histórico nocional do git. O tempo flui geralmente para baixo. As confirmações D, F, B e A são confirmações de mesclagem.

Aqui está uma ilustração, de Jon Loeliger. Os nós de confirmação B e C são pais do nó de confirmação A. As confirmações pai são ordenadas da esquerda para a direita.

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

Execute o código abaixo para criar um repositório git cujo histórico corresponda à ilustração citada.

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

Ele adiciona aliases no novo repositório descartável apenas para, git lolegit lola assim você pode ver o histórico como em

$ 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

Observe que na sua máquina os nomes dos objetos SHA-1 serão diferentes daqueles acima, mas as tags permitem que você endereça as confirmações por nome e verifique sua compreensão.

$ 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

As "Revisões de especificação" na git rev-parsedocumentação estão cheias de ótimas informações e valem uma leitura aprofundada. Veja também Ferramentas Git - Seleção de Revisão do livro Pro Git .

Ordem dos pais confirmados

O commit 89e4fcb0dd do próprio histórico do git é um commit de mesclagem, como git show 89e4fcb0ddindica a linha de cabeçalho Merge que exibe os nomes de objetos dos ancestrais imediatos.

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

Podemos confirmar a ordem pedindo git rev-parsepara mostrar os pais imediatos de 89e4fcb0dd em sequência.

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

A consulta do quarto pai pai inexistente resulta em um erro.

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

Se você deseja extrair apenas os pais, use um formato bonito %P para os hashes completos

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

ou %ppara pais abreviados.

$ git log -1 --pretty=%p 89e4fcb0dd
c670b1f876 649bf3a42f b67d40adbb
Greg Bacon
fonte
parece ^ pode lidar com todos os casos e pode-se perguntar por que ~ apareceu em primeiro lugar. Por que não lembrar apenas como ^ funciona?
Sbu
isso é super confuso ainda ... assumindo que G é HEAD, então se eu fizer um HEAD ^ isso seria D ... certo?
Patoshi # 16/17
12
@ duckx o gráfico está indo de cima para baixo, então A é o commit mais recente e G é um dos mais antigos. O caminho de G a D é para a frente, não para trás, pelo que posso dizer.
proporção áurea
@SimonBudin Eu acho que não é muito conveniente usar, ^^^^^^^não ~7é? É por isso que ~é útil
YakovL
1
@AdityaVikasDevarapalli Isso seria bom como sua própria pergunta.
Greg Bacon
340

A diferença entre HEAD^e HEAD~é bem descrita pela ilustração (de Jon Loeliger) encontrada em http://www.kernel.org/pub/software/scm/git/docs/git-rev-parse.html .

Esta documentação pode ser um pouco obscura para iniciantes, por isso reproduzi a ilustração abaixo:

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
g_fred
fonte
12
Apenas uma pergunta. Como é possível que um compromisso tenha mais de dois pais? (Veja B - os pais são D, E e F) Imagino que a única maneira de um commit ter dois pais é quando é um commit de mesclagem ... mas como você pode mesclar 3 commits ao mesmo tempo?
tsikov 29/09/2015
Se não me engano, isso pode ser óbvio, mas acho que deve ser especificado que HEAD ~ segue o ramo atual (como Diego Dias mencionado abaixo).
fibono
2
Além disso F = A^2^,.
Mateen Ulhaq
2
Assim, ^ == ^1 == LEFTMOST PARENT, ^2 == SECOND LEFTMOST PARENTe assim por diante. E ~ == ~1 == LEFTMOST PARENT, ~2 == LEFTMOST PARENTS LEFTMOST PARENT == LEFTMOST GRANDPARENT. Por extensão,~2^2 == LEFTMOST GRANDPARENTS SECOND LEFTMOST PARENT
Alexander Torstling
1
@AlexanderTorstling isso foi muito útil para mim. No entanto, o que esquerda e direita significam aqui?
Polynomial_donut
287

Ambos ~e ^por si só se referem ao pai do commit ( ~~e ^^ambos se referem ao commit dos avós, etc.). Mas eles diferem em significado quando são usados ​​com números:

  • ~2significa dois níveis na hierarquia , através do primeiro pai, se um commit tiver mais de um pai

  • ^2significa o segundo pai em que um commit tem mais de um pai (ou seja, porque é uma mesclagem)

Eles podem ser combinados, portanto HEAD~2^3, HEADo terceiro pai do commit dos avós.

Matthew Strawbridge
fonte
2
Lendo isso seguido pela figura de stackoverflow.com/questions/2221658/… fazia todo o sentido.
kunigami
23
Essa deve ser a resposta aceita, muito mais sucinta e útil que as outras.
RichVel 27/03
3
Essa resposta me fez distinguir entre acento circunflexo / til sem número e com número! Eu pensei que ^^era o mesmo, ^2mas não é.
Alexander Derck
278

Meus dois centavos...

insira a descrição da imagem aqui

Alex Janzik
fonte
E como H=A~2^2não H=A~2^1?
Mohammad Faisal
3
Se eu tinha descoberto corretamente, os commits A, B, D, Gestão no mesmo ramo ea cometer Dé uma mesclagem de Ge H, portanto, ter dois pais. Portanto, o commit ( H) de outro ramo é referência por ^2.
Mohammad Faisal
62

Aqui está uma explicação muito boa, extraída literalmente de http://www.paulboxley.com/blog/2011/06/git-caret-and-tilde :

ref~é uma abreviação para ref~1e significa o primeiro pai do commit. ref~2significa o primeiro pai do commit. ref~3significa o primeiro pai do primeiro pai do commit. E assim por diante.

ref^é uma abreviação para ref^1e significa o primeiro pai do commit. Mas onde os dois diferem é que ref^2significa o segundo pai do commit (lembre-se, os commit podem ter dois pais quando são uma mesclagem).

Os operadores ^e ~podem ser combinados.

insira a descrição da imagem aqui

dr_
fonte
5
Obrigado por realmente explicar as diferenças, em vez de publicar uma série de exemplos.
Kirk Broadhurst
32

O ^<n>formato permite selecionar o enésimo pai da confirmação (relevante nas mesclagens). O ~<n>formato permite selecionar o enésimo enésimo commit ancestral, sempre seguindo o primeiro pai. Veja a documentação do git-rev-parse para alguns exemplos.

jamessan
fonte
21

Vale ressaltar que o git também possui uma sintaxe para rastrear "de onde você veio" / "quero voltar agora" - por exemplo, HEAD@{1}fará referência ao local de onde você pulou para o novo local de confirmação.

Basicamente HEAD@{}variáveis capturar a história do movimento da cabeça, e você pode decidir usar uma cabeça em particular, olhando para reflogs de git usando o comando git reflog.

Exemplo:

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

Um exemplo pode ser que eu fiz commit local a-> b-> c-> d e depois voltei a descartar 2 commit para verificar meu código - git reset HEAD~2- e depois quero mover meu HEAD de volta para d - git reset HEAD@{1}.

acinzentado
fonte
17

Simplisticamente :

  • ~ especifica ancestrais
  • ^ especifica os pais

Você pode especificar uma ou mais ramificações ao mesclar. Então, um commit tem dois ou mais pais e ^é útil para indicar os pais.

Suponha que você esteja no ramo A e você tem mais dois ramos: B e C .

Em cada ramificação, os três últimos commits são:

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

Se agora no ramo A você executar o comando:

git merge B C

então você está combinando três ramos juntos (aqui o seu commit de mesclagem tem três pais)

e

~ indica o nono antepassado no primeiro ramo, então

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

^ indica o nono pai, então

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

O próximo uso de um ~ou ^ao lado do outro é no contexto do commit designado pelos caracteres anteriores.

Nota 1 :

  • HEAD~3é sempre igual a: HEAD~~~e a: HEAD^^^(todos indicam A1 ),

        e geralmente :

  • HEAD~né sempre igual a: HEAD~...~( n vezes ~) e a: HEAD^...^( n vezes ^).

Aviso 2 :

  • HEAD^3não é o mesmo que HEAD^^^(o primeiro indica C3 e o segundo indica A1 ),

        e geralmente :

  • HEAD^1é o mesmo que HEAD^,
  • mas para n > 1: nemHEAD^n sempre é o mesmo que HEAD^...^( n vezes ~).
simhumileco
fonte
15

TLDR

~ é o que você deseja na maioria das vezes, ele faz referência a confirmações anteriores à ramificação atual

^ faz referência aos pais (o git-merge cria um segundo pai ou mais)

A ~ é sempre o mesmo que A ^
A ~~ é sempre o mesmo que A ^^, e assim por diante
A ~ 2 não é o mesmo que A ^ 2, no entanto,
porque ~ 2 é uma abreviação de ~~
enquanto ^ 2 não é abreviação de qualquer coisa, significa o segundo pai

WeakPointer
fonte
11

HEAD ^^^ é o mesmo que HEAD ~ 3, selecionando o terceiro commit antes de HEAD

HEAD ^ 2 especifica o segundo cabeçalho em uma consolidação de mesclagem

knittl
fonte
9
  • HEAD ~ especifica o primeiro pai em um "ramo"

  • HEAD ^ permite que você selecione um pai específico da confirmação

Um exemplo:

Se você deseja seguir uma ramificação lateral, precisa especificar algo como

master~209^2~15
Diego Dias
fonte
5

exemplo real da diferença entre HEAD ~ e HEAD ^

CABEÇA ^ VS CABEÇA ~

Anas Alpure
fonte
0

Simplificando, para o primeiro nível de ascendência (ascendência, herança, linhagem etc.) HEAD ^ e HEAD ~ apontam para o mesmo commit, que é (localizado) um pai acima do HEAD (commit).

Além disso, HEAD ^ = HEAD ^ 1 = HEAD ~ = HEAD ~ 1. Mas HEAD ^^! = HEAD ^ 2! = HEAD ~ 2. No entanto, CABEÇA ^^ = CABEÇA ~ 2. Leia.

Além do primeiro nível de ascendência, as coisas ficam mais complicadas, especialmente se a ramificação / ramificação principal tiver sido mesclada (de outras ramificações). Há também a questão da sintaxe com o cursor, HEAD ^^ = HEAD ~ 2 (eles são equivalentes) MAS HEAD ^^! = HEAD ^ 2 (são duas coisas completamente diferentes).

Cada / o sinal de intercalação refere-se ao primeiro pai do HEAD, e é por isso que os caracteres agrupados são equivalentes a expressões til, porque se referem aos primeiros pais do primeiro pai (primeiro pai), etc., etc., com base estritamente no número de caracteres conectados ou no número que segue o til (de qualquer forma, ambos significam a mesma coisa), ou seja, fique com o primeiro pai e suba x gerações.

HEAD ~ 2 (ou HEAD ^^) refere-se ao commit que é dois níveis de ancestralidade acima / acima do commit atual (o HEAD) na hierarquia, significando o commit dos avós do HEAD.

HEAD ^ 2, por outro lado, refere-se NÃO ao commit do segundo pai, mas simplesmente ao commit do segundo pai. Isso ocorre porque o sinal de intercalação significa o pai da confirmação, e o número a seguir significa a qual / qual confirmação do pai é referida (o primeiro pai, no caso em que o cursor não é seguido por um número [porque é uma abreviação para o número sendo 1, significando o primeiro pai]). Diferentemente do sinal de intercalação, o número que segue depois não implica outro nível de hierarquia para cima, mas implica quantos níveis de lado, na hierarquia, é necessário encontrar o pai correto (confirmação). Diferente do número em uma expressão de til, ele é apenas um pai na hierarquia, independentemente do número (imediatamente) que prossegue o cursor. Em vez de para cima, o sinal de intercalação '

Então HEAD ^ 3 é igual ao terceiro pai do commit HEAD (NÃO o bisavô, que é o que HEAD ^^^ AND HEAD ~ 3 seria ...).

Sean Tank Garvey
fonte
-1

~ isso significa pai.

^ se tiver pais de dois ou mais, como consolidação de mesclagem, podemos selecionar o segundo pai ou outro.

portanto, se apenas uma coisa como (HEAD ~ ou HEAD ^), ela tem os mesmos resultados.

Margaux
fonte