Quando specifico un oggetto commit antenato in Git, sono confuso tra HEAD^
e HEAD~
.
Entrambi hanno una versione "numerata" come HEAD^3
e HEAD~2
.
Sembrano molto simili o uguali a me, ma ci sono differenze tra la tilde e il caret?
Quando specifico un oggetto commit antenato in Git, sono confuso tra HEAD^
e HEAD~
.
Entrambi hanno una versione "numerata" come HEAD^3
e HEAD~2
.
Sembrano molto simili o uguali a me, ma ci sono differenze tra la tilde e il caret?
Risposte:
~
maggior parte del tempo per tornare indietro di diverse generazioni, di solito quello che vuoi^
su commit di unione - perché hanno due o più genitori (immediati)mnemonics:
~
un aspetto quasi lineare e vuole tornare indietro in linea retta^
suggerisce un segmento interessante di un albero o un bivioLa sezione "Specifica delle revisioni" della git rev-parse
documentazione 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~2
significa il nonno della punta del ramo principale, favorendo il primo genitore in commit unione.
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^2
e così via, se del caso. La stessa sezione della git rev-parse
documentazione lo definisce come
<rev>^
, ad esempioHEAD^
,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>^0
indica il commit stesso e viene utilizzato quando<rev>
è il nome oggetto di un oggetto tag che fa riferimento a un oggetto commit.
Questi specificatori o selettori possono essere concatenati arbitrariamente, ad esempio , topic~3^2
in 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-parse
documentazione 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 lol
egit 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-parse
documentazione sono piene di ottime informazioni e meritano una lettura approfondita. Vedi anche Git Tools - Revision Selection dal libro Pro Git .
Il commit 89e4fcb0dd della cronologia di git è un commit di unione, come git show 89e4fcb0dd
indicato 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-parse
di 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 %p
per genitori abbreviati.
$ git log -1 --pretty=%p 89e4fcb0dd
c670b1f876 649bf3a42f b67d40adbb
^^^^^^^
invece di ~7
, vero? Ecco perché ~
è utile
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
F = A^2^
.
^ == ^1 == LEFTMOST PARENT
, ^2 == SECOND LEFTMOST PARENT
e così via. E ~ == ~1 == LEFTMOST PARENT
, ~2 == LEFTMOST PARENTS LEFTMOST PARENT == LEFTMOST GRANDPARENT
. Per estensione,~2^2 == LEFTMOST GRANDPARENTS SECOND LEFTMOST PARENT
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:
~2
indica due livelli nella gerarchia , tramite il primo genitore se un commit ha più di un genitore
^2
indica 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^3
significa HEAD
.
^^
fosse lo stesso ^2
ma non lo è.
I miei due centesimi...
H=A~2^2
no H=A~2^1
?
A
, B
, D
, G
sono sullo stesso ramo e il commit D
è una fusione di G
e H
, avendo così due genitori. Quindi il commit ( H
) da un altro ramo è riferimento da ^2
.
Ecco una spiegazione molto buona presa alla lettera da http://www.paulboxley.com/blog/2011/06/git-caret-and-tilde :
ref~
è una scorciatoia perref~1
e indica il primo genitore del commit.ref~2
indica il primo genitore del primo genitore del commit.ref~3
indica il primo genitore del primo genitore del commit. E così via.
ref^
è una scorciatoia perref^1
e indica il primo genitore del commit. Ma dove i due differiscono è ciòref^2
significa che il secondo genitore del commit (ricorda, i commit possono avere due genitori quando sono una fusione).Gli operatori
^
e~
possono essere combinati.
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.
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}
.
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:
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 A3HEAD~2
indica A2HEAD~3
indica A1^
indica l'ennesimo genitore, quindi
HEAD^
indica A3HEAD^2
indica B3HEAD^3
indica C3L'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^3
non è uguale a HEAD^^^
(il primo indica C3 e il secondo indica A1 ),e generalmente :
HEAD^1
è uguale a HEAD^
,HEAD^n
è sempre non è lo stesso di HEAD^...^
( n volte ~
).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
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
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 ...).