Questa non è esattamente una risposta reale, ma ho bisogno di accedere alla formattazione e molto spazio. Cercherò di descrivere la teoria alla base di quelle che considero le due migliori risposte: quella accettata e quella (almeno al momento) in cima alla classifica . Ma in realtà, rispondono a domande diverse .
I commit in Git sono molto spesso "su" più di un ramo alla volta. In effetti, è proprio di questo che si tratta. Dato:
...--F--G--H <-- master
\
I--J <-- develop
dove le lettere maiuscole stanno per gli ID hash Git effettivi, spesso cerchiamo solo commitH
o solo commitI-J
nel nostro git log
output. I commit fino a G
sono su entrambi i rami, quindi vorremmo escluderli.
(Nota che nei grafici disegnati in questo modo, i commit più recenti sono verso destra. I nomi selezionano il singolo commit più a destra su quella riga. Ciascuno di questi commit ha un commit genitore, che è il commit alla loro sinistra: il genitore di H
è G
, e il genitore di J
è I
. Il genitore di I
è di G
nuovo. Il genitore di G
è F
, e F
ha un genitore che semplicemente non è mostrato qui: fa parte della ...
sezione.)
Per questo caso particolarmente semplice possiamo utilizzare:
git log master..develop # note: two dots
da visualizzare I-J
, o:
git log develop..master # note: two dots
per visualizzare H
solo. Il nome a destra, dopo i due punti, dice a Git: sì, questi commit . Il nome a sinistra, prima dei due punti, dice a Git: no, non questi commit . Git inizia alla fine - in commit H
o commit J
- e funziona all'indietro . Per (molto) di più su questo, vedere Think Like (a) Git .
Il modo in cui è formulata la domanda originale, il desiderio è di trovare commit che siano raggiungibili da un nome particolare, ma non da qualsiasi altro nome nella stessa categoria generale. Cioè, se abbiamo un grafico più complesso:
O--P <-- name5
/
N <-- name4
/
...--F--G--H--I---M <-- name1
\ /
J-----K <-- name2
\
L <-- name3
potremmo scegliere uno di questi nomi, come name4
o name3
, e chiedere: quali commit possono essere trovati con quel nome, ma non con nessuno degli altri nomi? Se scegliamo name3
la risposta è commit L
. Se scegliamo name4
, la risposta è nessun commit: il commit che name4
nomina commit N
ma commit N
può essere trovato iniziando da name5
e lavorando all'indietro.
La risposta accettata funziona con i nomi di monitoraggio remoto, piuttosto che con i nomi dei rami, e consente di designarne uno, quello scritto, origin/merge-only
come nome selezionato e di esaminare tutti gli altri nomi in quello spazio dei nomi. Evita anche di mostrare i merge: se scegliamo name1
come "nome interessante", e diciamo mostrami i commit che sono raggiungibili da name1
ma non da qualsiasi altro nome , vedremo il merge commit M
così come il commit regolare I
.
La risposta più popolare è abbastanza diversa. Si tratta di attraversare il grafico del commit senza seguire entrambe le gambe di un'unione e senza mostrare nessuno dei commit che sono unioni. Se iniziamo con name1
, ad esempio, non mostreremo M
(è un merge), ma supponendo che il primo genitore di merge M
sia commit I
, non guarderemo nemmeno i commit J
e K
. Finiremo mostrando commettere I
, e si impegna inoltre H
, G
, F
, e così via, nessuno di questi sono commit di unione e tutti sono raggiungibili con partendo M
e lavorando a ritroso, visitando solo la prima madre di ogni merge commit.
La risposta più popolare è abbastanza adatta, ad esempio, a guardare master
quando master
è inteso come un ramo di sola unione. Se tutto il "lavoro reale" è stato fatto sui rami laterali che sono stati successivamente fusi master
, avremo uno schema come questo:
I---------M---------N <-- master
\ / \ /
o--o--o o--o--o
dove tutti i o
commit senza nome sono normali (non-merge) e M
e N
sono merge commit. Il I
commit è il commit iniziale: il primo vero commit mai fatto e l'unico che dovrebbe essere su master che non è un merge commit. Se il programma git log --first-parent --no-merges master
mostra un commit diverso da I
, abbiamo una situazione come questa:
I---------M----*----N <-- master
\ / \ /
o--o--o o--o--o
dove vogliamo vedere il commit *
che è stato fatto direttamente su master
, non unendo qualche ramo di funzionalità.
In breve, la risposta popolare è ottima per guardare master
quando master
dovrebbe essere solo unione, ma non è così eccezionale per altre situazioni. La risposta accettata funziona per queste altre situazioni.
I nomi di monitoraggio remoto sono come i nomi di origin/master
filiale ?
Alcune parti di Git dicono che non lo sono:
git checkout master
...
git status
dice on branch master
, ma:
git checkout origin/master
...
git status
dice HEAD detached at origin/master
. Preferisco essere d'accordo con git checkout
/ git switch
: origin/master
non è il nome di un ramo perché non puoi "accedervi".
La risposta accettata utilizza nomi di monitoraggio remoto origin/*
come "nomi di filiale":
git log --no-merges origin/merge-only \
--not $(git for-each-ref --format="%(refname)" refs/remotes/origin |
grep -Fv refs/remotes/origin/merge-only)
La riga centrale, che invoca git for-each-ref
, itera sui nomi di tracciamento remoto per il telecomando denominato origin
.
La ragione per cui questa è una buona soluzione al problema originale è che qui siamo interessati ai nomi di ramo di qualcun altro , piuttosto che ai nostri nomi di ramo. Ma questo significa che abbiamo definito branch come qualcosa di diverso dai nostri nomi di branch . Va bene: sii consapevole che lo stai facendo, quando lo fai.
git log
attraversa alcune parti del grafico di commit
Quello che stiamo veramente cercando qui sono serie di ciò che ho chiamato daglet: vedi Cosa si intende esattamente per "ramo"? Cioè, stiamo cercando frammenti all'interno di qualche sottoinsieme del grafico di commit complessivo .
Ogni volta che abbiamo Git che guarda il nome di un ramo come master
, un nome di tag come v2.1
, o un nome di monitoraggio remoto come origin/master
, tendiamo a voler che Git ci parli di quel commit e di ogni commit che possiamo ottenere da quel commit: a partire da lì e lavorando all'indietro.
In matematica, questo è indicato come camminare su un grafico . Il grafico commit di Git è un grafico aciclico diretto o DAG e questo tipo di grafico è particolarmente adatto per camminare. Quando si percorre un tale grafico, si visiterà ogni vertice del grafico che è raggiungibile tramite il percorso utilizzato. I vertici nel grafo Git sono i commit, con i bordi che sono archi - collegamenti unidirezionali - che vanno da ogni figlio a ogni genitore. (È qui che entra in gioco Think Like (a) Git . La natura unidirezionale degli archi significa che Git deve funzionare all'indietro, da figlio a genitore.)
I due comandi principali di Git per camminare sui grafici sono git log
e git rev-list
. Questi comandi sono estremamente simili - infatti sono per lo più costruiti dagli stessi file sorgente - ma il loro output è diverso: git log
produce output che gli umani possono leggere, mentre git rev-list
produce output destinati alla lettura di altri programmi Git. 1 Entrambi i comandi eseguono questo tipo di passeggiata nel grafico.
La passeggiata del grafico che fanno è specifica: data una serie di commit del punto di partenza (forse solo un commit, forse un mucchio di hash ID, forse un mucchio di nomi che risolvono in hash ID), percorri il grafico, visitando i commit . Direttive particolari, come --not
o un prefisso ^
, o --ancestry-path
, o --first-parent
, modificano il grafico in qualche modo.
Mentre eseguono il grafico a piedi, visitano ogni commit. Ma stampano solo alcuni sottoinsiemi selezionati dei commit eseguiti. Direttive come --no-merges
o --before <date>
dicono al codice grafico che si impegna a stampare .
Per fare questa visita, un commit alla volta, questi due comandi utilizzano una coda di priorità . Corri git log
o git rev-list
e dai un punto di partenza che si impegna. Mettono quei commit nella coda delle priorità. Ad esempio, un semplice:
git log master
trasforma il nome master
in un ID hash grezzo e inserisce quell'ID hash nella coda. O:
git log master develop
trasforma entrambi i nomi in ID hash e, supponendo che si tratti di due diversi ID hash, li inserisce entrambi nella coda.
La priorità dei commit in questa coda è determinata da ancora più argomenti. Ad esempio, l'argomento --author-date-order
indica git log
o git rev-list
utilizza il timestamp dell'autore , anziché il timestamp del committer. L'impostazione predefinita è utilizzare il timestamp del committer e scegliere il commit più recente per data: quello con la data numerica più alta. Quindi master develop
, supponendo che questi si risolvano su due commit diversi, Git mostrerà quello che è arrivato per primo dopo , perché sarà in cima alla coda.
In ogni caso, il codice di controllo della revisione ora viene eseguito in un ciclo:
- Mentre ci sono commit nella coda:
- Rimuovi la prima voce dalla coda.
- Decidi se stampare o meno questo commit. Ad esempio
--no-merges
:: non stampa nulla se è un commit di unione; --before
: non stampare nulla se la data non è antecedente all'ora designata. Se la stampa non è soppressa, stampa il commit: for git log
, mostra il suo log; per git rev-list
, stampa il suo ID hash.
- Metti alcuni o tutti i commit genitore di questo commit nella coda (purché non sia presente ora e non sia già stato visitato 2 ). L'impostazione predefinita normale è inserire tutti i genitori. L'utilizzo
--first-parent
sopprime tutto tranne il primo genitore di ogni unione.
(Entrambi git log
e git rev-list
possono semplificare la cronologia con o senza la riscrittura dei genitori a questo punto, ma lo tralasciamo qui.)
Per una catena semplice, come iniziare da HEAD
e lavorare all'indietro quando non ci sono commit di unione, la coda ha sempre un commit in essa all'inizio del ciclo. C'è un commit, quindi lo estraiamo e lo stampiamo e mettiamo il suo genitore (singolo) nella coda e andiamo di nuovo in giro, e seguiamo la catena all'indietro fino a raggiungere il primo commit, o l'utente si stanca git log
dell'output e chiude il programma. In questo caso, nessuna delle opzioni di ordinamento ha importanza: c'è sempre un solo commit da mostrare.
Quando ci sono unioni e seguiamo entrambi i genitori, entrambe le "gambe" dell'unione, o quando dai git log
o git rev-list
più di un commit iniziale, le opzioni di ordinamento sono importanti.
Infine, considera l'effetto di --not
o ^
davanti a uno specificatore di commit. Questi hanno diversi modi per scriverli:
git log master --not develop
o:
git log ^develop master
o:
git log develop..master
tutti significano la stessa cosa. L' --not
è come il prefisso ^
, tranne che si applica a più di un nome:
git log ^branch1 ^branch2 branch3
significa non ramo1, non ramo2, sì ramo3; ma:
git log --not branch1 branch2 branch3
significa non branch1, non branch2, non branch3, e devi usare un secondo --not
per spegnerlo:
git log --not branch1 branch2 --not branch3
che è un po 'imbarazzante. Le due direttive "not" vengono combinate tramite XOR, quindi se lo desideri puoi scrivere:
git log --not branch1 branch2 ^branch3
a significare non branch1, non branch2, sì branch3 , se vuoi offuscare .
Funzionano tutti influenzando il percorso del grafico. Mentre git log
o git rev-list
percorre il grafico, si assicura di non mettere nella coda di priorità alcun commit raggiungibile da nessuno dei riferimenti negati . (In effetti, influenzano anche la configurazione iniziale: i commit negati non possono entrare nella coda di priorità direttamente dalla riga di comando, quindi git log master ^master
non mostra nulla, ad esempio.)
Tutta la sintassi elaborata descritta nella documentazione di gitrevisions ne fa uso, e puoi esporla con una semplice chiamata agit rev-parse
. Per esempio:
$ git rev-parse origin/pu...origin/master # note: three dots
b34789c0b0d3b137f0bb516b417bd8d75e0cb306
fc307aa3771ece59e174157510c6db6f0d4b40ec
^b34789c0b0d3b137f0bb516b417bd8d75e0cb306
La sintassi a tre punti indica i commit raggiungibili dal lato sinistro o destro, ma esclusi i commit raggiungibili da entrambi . In questo caso il origin/master
commit,, b34789c0b
è esso stesso raggiungibile da origin/pu
(fc307aa37...
) quindi l' origin/master
hash appare due volte, una volta con una negazione, ma in realtà Git raggiunge la sintassi a tre punti inserendo due riferimenti positivi - i due hash ID non negati - e uno negativo, rappresentato dal ^
prefisso.
Allo stesso modo:
$ git rev-parse master^^@
2c42fb76531f4565b5434e46102e6d85a0861738
2f0a093dd640e0dad0b261dae2427f2541b5426c
La ^@
sintassi indica tutti i genitori del commit dato e master^
se stesso, il primo genitore del commit selezionato dal nome del ramo, master
è un commit di unione, quindi ha due genitori. Questi sono i due genitori. E:
$ git rev-parse master^^!
0b07eecf6ed9334f09d6624732a4af2da03e38eb
^2c42fb76531f4565b5434e46102e6d85a0861738
^2f0a093dd640e0dad0b261dae2427f2541b5426c
Il ^!
suffisso indica il commit stesso, ma nessuno dei suoi genitori . In questo caso, master^
è 0b07eecf6...
. Abbiamo già visto entrambi i genitori con il ^@
suffisso; eccoli di nuovo, ma questa volta negati.
1 Molti programmi Git vengono letteralmente eseguiti git rev-list
con varie opzioni e leggono il suo output, per sapere quali commit e / o altri oggetti Git usare.
2 Poiché il grafico è aciclico , è possibile garantire che nessuno sia già stato visitato, se aggiungiamo il vincolo non mostrare mai un genitore prima di mostrare tutti i suoi figli alla priorità. --date-order
, --author-date-order
e--topo-order
aggiungi questo vincolo. L'ordinamento predefinito, che non ha nome, no. Se i timestamp dei commit sono irregolari, ad esempio se alcuni commit sono stati effettuati "in futuro" da un computer il cui orologio era spento, in alcuni casi ciò potrebbe portare a un output dall'aspetto strano.
Se sei arrivato così lontano, ora sai molto di più git log
Sommario:
git log
riguarda la visualizzazione di alcuni commit selezionati mentre si percorrono alcune o tutte le parti del grafico.
- L'
--no-merges
argomento, che si trova sia nelle risposte accettate che in quelle attualmente in cima alla classifica, sopprime mostrando alcuni commit che sono eseguiti.
- L'
--first-parent
argomento, dalla risposta attualmente in cima alla classifica, sopprime il camminare alcune parti del grafico, durante la passeggiata nel grafico stesso.
- Il
--not
prefisso agli argomenti della riga di comando, come usato nella risposta accettata, sopprime la visita di alcune parti del grafico, fin dall'inizio.
Otteniamo le risposte che ci piacciono, a due domande diverse, utilizzando queste funzionalità.
git log ^branch1 ^branch2 merge-only-branch
sintassi?