Qual è la differenza tra HEAD ^ e HEAD ~ in Git?


756

Quando specifico un oggetto commit antenato in Git, sono confuso tra HEAD^e HEAD~.

Entrambi hanno una versione "numerata" come HEAD^3e HEAD~2.

Sembrano molto simili o uguali a me, ma ci sono differenze tra la tilde e il caret?


64
è male incollare i collegamenti, lo so ma questa è la migliore spiegazione che ho trovato e c'è un'immagine in esso. paulboxley.com/blog/2011/06/git-caret-and-tilde
igor

4
I collegamenti sono particolarmente dannosi quando vengono interrotti. Questo è il motivo per cui è più sicuro rispondere alla domanda che aiuta a prevenire questo a causa della possibilità di copiare alcune spiegazioni incollate :)
Samuel

Risposte:


763

Regole empiriche

  • Usa la ~maggior parte del tempo per tornare indietro di diverse generazioni, di solito quello che vuoi
  • Utilizzare ^su commit di unione - perché hanno due o più genitori (immediati)

mnemonics:

  • Tilde ha ~un aspetto quasi lineare e vuole tornare indietro in linea retta
  • Caret ^suggerisce un segmento interessante di un albero o un bivio

tilde

La sezione "Specifica delle revisioni" della git rev-parsedocumentazione definisce ~come

<rev>~<n>, Per esempiomaster~3
Un suffisso ~<n>a un parametro di revisione significa che l'oggetto che è il commit n ° generazione antenato del nome commettere oggetto, seguendo solo i primi genitori. Ad esempio, <rev>~3è equivalente al <rev>^^^quale equivale a <rev>^1^1^1...

Puoi arrivare ai genitori di qualsiasi impegno, non solo HEAD. Puoi anche tornare indietro di generazione in generazione: ad esempio, master~2significa il nonno della punta del ramo principale, favorendo il primo genitore in commit unione.

segno di omissione

La storia di Git non è lineare: un grafico aciclico diretto (DAG) o un albero. Per un commit con un solo genitore, rev~e rev^significano la stessa cosa. Il selettore del cursore diventa utile con i commit di unione perché ognuno è figlio di due o più genitori - e mette a dura prova il linguaggio preso in prestito dalla biologia.

HEAD^indica il primo genitore immediato della punta del ramo corrente. HEAD^è l'abbreviazione di HEAD^1, e puoi anche indirizzare HEAD^2e così via, se del caso. La stessa sezione della git rev-parsedocumentazione lo definisce come

<rev>^, ad esempio HEAD^ ,v1.5.1^0
un suffisso ^per un parametro di revisione indica il primo genitore dell'oggetto commit. ^<n>intende il n ° genitore ([ es ] <rev>^è equivalente a <rev>^1). Come regola speciale, <rev>^0indica il commit stesso e viene utilizzato quando <rev>è il nome oggetto di un oggetto tag che fa riferimento a un oggetto commit.

Esempi

Questi specificatori o selettori possono essere concatenati arbitrariamente, ad esempio , topic~3^2in inglese è il secondo genitore del commit di unione che è il bisnonno (tre generazioni indietro) dell'attuale punta del ramo topic.

La sezione della git rev-parsedocumentazione sopra menzionata traccia molti percorsi attraverso una storia git nozionale. Il tempo scorre generalmente verso il basso. I commit D, F, B e A sono commit unisci.

Ecco un'illustrazione, di Jon Loeliger. Entrambi i nodi di commit B e C sono genitori del nodo di commit A. I commit parent sono ordinati da sinistra a destra.

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

Esegui il codice seguente per creare un repository git la cui cronologia corrisponde all'illustrazione tra virgolette.

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

Aggiunge alias nel nuovo repository usa git lolegit lola getta solo per e quindi è possibile visualizzare la cronologia come 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

Si noti che sul proprio computer i nomi degli oggetti SHA-1 differiranno da quelli sopra, ma i tag consentono di indirizzare gli commit per nome e verificare la propria comprensione.

$ 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

Le "Specifiche di revisione" nella git rev-parsedocumentazione sono piene di ottime informazioni e meritano una lettura approfondita. Vedi anche Git Tools - Revision Selection dal libro Pro Git .

Ordine degli impegni principali

Il commit 89e4fcb0dd della cronologia di git è un commit di unione, come git show 89e4fcb0ddindicato dalla riga di intestazione Unisci che mostra i nomi degli oggetti degli antenati immediati.

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

Possiamo confermare l'ordinazione chiedendo git rev-parsedi mostrare i genitori immediati di 89e4fcb0dd in sequenza.

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

L'interrogazione del quarto genitore inesistente provoca un errore.

$ 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 vuoi estrarre solo i genitori, usa il formato grazioso %P per gli hash completi

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

o %pper genitori abbreviati.

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

sembra ^ in grado di gestire tutti i casi e ci si può chiedere perché ~ sia apparso in primo luogo. Perché non ricordi solo come ^ funziona?
Sal

questo è ancora molto confuso ... supponendo che G sia HEAD, quindi se faccio una HEAD ^ sarebbe D ... giusto?
Patoshi パ ト シ

12
@duckx il grafico sta effettivamente andando dall'alto verso il basso, quindi A è il commit più recente e G è uno dei più vecchi. Il percorso da G a D è avanti, non indietro, da quello che posso dire.
goldenratio

@SimonBudin Immagino che non sia molto comodo da usare ^^^^^^^invece di ~7, vero? Ecco perché ~è utile
YakovL

1
@AdityaVikasDevarapalli Sarebbe una buona domanda.
Greg Bacon,

340

La differenza tra HEAD^e HEAD~è ben descritta dall'illustrazione (di Jon Loeliger) che si trova su http://www.kernel.org/pub/software/scm/git/docs/git-rev-parse.html .

Questa documentazione può essere un po 'oscura per i principianti, quindi ho riprodotto questa illustrazione di seguito:

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

12
Solo una domanda. Come è possibile che un impegno abbia più di due genitori? (Vedi B: sono i genitori D, E e F) Immagino che l'unico modo in cui un commit può avere due genitori è quando è un commit di tipo merge ... ma come puoi unire 3 commit contemporaneamente?
Tsikov,

Se non sbaglio, questo può essere ovvio, ma penso che dovrebbe essere specificato che HEAD ~ segue l'attuale ramo (come Diego Dias menzionato di seguito).
fibono,

2
Inoltre, F = A^2^.
Mateen Ulhaq,

2
Così, ^ == ^1 == LEFTMOST PARENT, ^2 == SECOND LEFTMOST PARENTe così via. E ~ == ~1 == LEFTMOST PARENT, ~2 == LEFTMOST PARENTS LEFTMOST PARENT == LEFTMOST GRANDPARENT. Per estensione,~2^2 == LEFTMOST GRANDPARENTS SECOND LEFTMOST PARENT
Alexander Torstling,

1
@AlexanderTorstling questo è stato molto utile per me. Tuttavia, cosa significa sinistra e destra qui?
polynomial_donut,

287

Entrambi ~e ^da soli si riferiscono al genitore del commit ( ~~ed ^^entrambi si riferiscono al commit del nonno, ecc.) Ma differiscono nel significato quando vengono usati con i numeri:

  • ~2indica due livelli nella gerarchia , tramite il primo genitore se un commit ha più di un genitore

  • ^2indica il secondo genitore in cui un commit ha più di un genitore (ovvero perché è un merge)

Questi possono essere combinati, quindi il commit del terzo genitore del commit del nonno HEAD~2^3significa HEAD.


2
Leggere questo seguito dall'immagine da stackoverflow.com/questions/2221658/… ha perfettamente senso.
Kunigami,

23
Questa dovrebbe essere la risposta accettata, molto più concisa e utile delle altre.
RichVel

3
Questa risposta mi ha fatto distinguere tra cursore / tilde senza numero e con numero! Pensavo ^^fosse lo stesso ^2ma non lo è.
Alexander Derck,

278

I miei due centesimi...

inserisci qui la descrizione dell'immagine


E come H=A~2^2no H=A~2^1?
Mohammad Faisal,

3
Se avessi capito correttamente, i commit A, B, D, Gsono sullo stesso ramo e il commit Dè una fusione di Ge H, avendo così due genitori. Quindi il commit ( H) da un altro ramo è riferimento da ^2.
Mohammad Faisal,

62

Ecco una spiegazione molto buona presa alla lettera da http://www.paulboxley.com/blog/2011/06/git-caret-and-tilde :

ref~è una scorciatoia per ref~1e indica il primo genitore del commit. ref~2indica il primo genitore del primo genitore del commit. ref~3indica il primo genitore del primo genitore del commit. E così via.

ref^è una scorciatoia per ref^1e indica il primo genitore del commit. Ma dove i due differiscono è ciò ref^2significa che il secondo genitore del commit (ricorda, i commit possono avere due genitori quando sono una fusione).

Gli operatori ^e ~possono essere combinati.

inserisci qui la descrizione dell'immagine


5
Grazie per aver effettivamente spiegato le differenze piuttosto che pubblicare una serie di esempi.
Kirk Broadhurst,

32

Il ^<n>formato consente di selezionare l'ennesimo genitore del commit (rilevante nelle fusioni). Il ~<n>formato consente di selezionare l'ennesimo commit antenato, sempre seguendo il primo genitore. Vedi la documentazione di git-rev-parse per alcuni esempi.


21

Vale la pena notare che git ha anche una sintassi per il tracciamento di "da-dove-sei-venuto" / "vuoi-tornare-ora-indietro" - ad esempio, HEAD@{1}farà riferimento al luogo da cui sei passato a una nuova posizione di commit.

Fondamentalmente le HEAD@{}variabili catturano la storia del movimento HEAD, e puoi decidere di usare una testa particolare guardando nei reflog di git usando il comando git reflog.

Esempio:

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

Un esempio potrebbe essere che ho eseguito local-commit a-> b-> c-> d e poi sono tornato indietro scartando 2 commit per controllare il mio codice - git reset HEAD~2- e poi voglio spostare nuovamente la mia HEAD su d - git reset HEAD@{1}.


17

Semplicisticamente :

  • ~ specifica gli antenati
  • ^ specifica i genitori

È possibile specificare uno o più rami durante l'unione. Quindi un commit ha due o più genitori ed ^è quindi utile per indicare i genitori.

Supponete di essere sul ramo A e si dispone di più di due rami: B e C .

Su ogni ramo gli ultimi tre commit sono:

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

Se ora sul ramo A si esegue il comando:

git merge B C

quindi stai combinando tre rami insieme (qui il tuo commit di unione ha tre genitori)

e

~ indica l'ennesimo antenato nel primo ramo, quindi

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

^ indica l'ennesimo genitore, quindi

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

L'uso successivo ~o uno ^accanto all'altro è nel contesto del commit designato da caratteri precedenti.

Avviso 1 :

  • HEAD~3è sempre uguale a: HEAD~~~e a: HEAD^^^(ogni indica A1 ),

        e generalmente :

  • HEAD~nè sempre uguale a: HEAD~...~( n volte ~) e a: HEAD^...^( n volte ^).

Avviso 2 :

  • HEAD^3non è uguale a HEAD^^^(il primo indica C3 e il secondo indica A1 ),

        e generalmente :

  • HEAD^1è uguale a HEAD^,
  • ma per n > 1: HEAD^nè sempre non è lo stesso di HEAD^...^( n volte ~).

15

TLDR

~ è quello che vuoi la maggior parte del tempo, fa riferimento a commit passati al ramo corrente

^ riferimenti ai genitori (git-merge crea un secondo genitore o più)

A ~ è sempre lo stesso di A ^
A ~~ è sempre lo stesso di A ^^, e così su
A ~ 2 non è lo stesso di A ^ 2 comunque,
perché ~ 2 è una scorciatoia per ~~
mentre ^ 2 non lo è stenografia per qualsiasi cosa, significa il 2 ° genitore


11

HEAD ^^^ è uguale a HEAD ~ 3, selezionando il terzo commit prima di HEAD

HEAD ^ 2 specifica la seconda testa in un commit di unione


9
  • HEAD ~ specifica il primo genitore su un "ramo"

  • HEAD ^ ti permette di selezionare un genitore specifico del commit

Un esempio:

Se vuoi seguire un ramo laterale, devi specificare qualcosa di simile

master~209^2~15


0

In poche parole, per il primo livello di parentela (antenati, eredità, lignaggio, ecc.) HEAD ^ e HEAD ~ indicano entrambi lo stesso commit, che è (situato) un genitore sopra HEAD (commit).

Inoltre, HEAD ^ = HEAD ^ 1 = HEAD ~ = HEAD ~ 1. Ma HEAD ^^! = HEAD ^ 2! = HEAD ~ 2. Eppure HEAD ^^ = HEAD ~ 2. Continuare a leggere.

Oltre al primo livello di parentela, le cose si complicano, specialmente se il ramo di lavoro / ramo principale ha avuto fusioni (da altri rami). C'è anche la questione della sintassi con il cursore, HEAD ^^ = HEAD ~ 2 (sono equivalenti) MA HEAD ^^! = HEAD ^ 2 (sono due cose completamente diverse).

Ciascuno / il punto di riferimento si riferisce al primo genitore di HEAD, motivo per cui i punti di giunzione messi insieme sono equivalenti alle espressioni tilde, perché si riferiscono ai primi genitori del primo genitore (primo genitore), ecc., Ecc. Basati rigorosamente sul numero di punti collegati o sul numero che segue la tilde (in entrambi i casi, significano entrambi la stessa cosa), ovvero stare con il primo genitore e risalire x generazioni.

HEAD ~ 2 (o HEAD ^^) si riferisce al commit che è due livelli di antenati sopra / sopra il commit corrente (HEAD) nella gerarchia, ovvero il commit nonno di HEAD.

HEAD ^ 2, d'altra parte, si riferisce NON al commit del secondo genitore del primo genitore, ma semplicemente al commit del secondo genitore. Questo perché il cursore indica il genitore del commit e il numero seguente indica a quale / a quale commit del genitore viene fatto riferimento (il primo genitore, nel caso in cui il cursore non sia seguito da un numero [perché è l'abbreviazione del numero essendo 1, che significa il primo genitore]). A differenza del cursore, il numero che segue in seguito non implica un altro livello di gerarchia verso l'alto, ma piuttosto implica quanti livelli lateralmente, nella gerarchia, è necessario trovare il genitore corretto (commit). A differenza del numero in un'espressione tilde, nella gerarchia è presente un solo genitore, indipendentemente dal numero (immediatamente) che procede al punto di inserimento. Invece che verso l'alto, il cursore

Quindi HEAD ^ 3 è uguale al terzo genitore del commit HEAD (NON il bisnonno, che è ciò che HEAD ^^^ AND HEAD ~ 3 sarebbe ...).


-1

~ questo significa genitore.

^ se ha genitori di due o più, come unire un commit, possiamo selezionare un secondo genitore o un altro.

quindi se solo una cosa come (HEAD ~ o HEAD ^), ha gli stessi risultati.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.