Come trovo il prossimo commit in git? (bambino / i bambini di riferimento)


Risposte:


185

Per elencare tutti i commit, partendo da quello attuale, quindi dal suo figlio e così via - fondamentalmente git log standard, ma andando nel senso opposto nel tempo, usa qualcosa come

git log --reverse --ancestry-path 894e8b4e93d8f3^..master

dove 894e8b4e93d8f3 è il primo commit che si desidera mostrare.


3
Per il caso specifico nella domanda iniziale, basta sostituire HEAD^per 894e8b4e93d8f3^.
Søren Løvborg,

2
Forse, aggiungi --oneline è un risultato di output breve migliore.
firo,

1
Devo usare ..., ^..fallisce in silenzio
TankorSmash,

1
Entrare fatal: unrecognized argument: --ancestry-pathin git versione 1.7.1
user151841

6
Funzionerà solo se si mastertrova sul percorso degli antenati del commit corrente. Vedi il secondo frammento di codice della mia risposta per una soluzione che funzionerà in tutti i casi.
Tom Hale,

37

Il creatore di Hudson (ora Jenkins) Kohsuke Kawaguchi ha appena pubblicato (novembre 2013):
kohsuke / git-children-of :

Dato un commit, trova i figli immediati di quel commit.

#!/bin/bash -e
# given a commit, find immediate children of that commit.
for arg in "$@"; do
  for commit in $(git rev-parse $arg^0); do
    for child in $(git log --format='%H %P' --all | grep -F " $commit" | cut -f1 -d' '); do
      git describe $child
    done
  done
done

Come illustrato da questa discussione , in un VCS basato sulla storia rappresentata da un DAG (Directed Acyclic Graph) , non esiste "un genitore" o "un figlio".

        C1 -> C2 -> C3
      /               \
A -> B                  E -> F
      \               /
        D1 -> D2 ----/

L'ordinamento dei commit viene effettuato tramite "topo-order" o "date-order" (vedi il libro GitPro )

Ma da Git1.6.0 , puoi elencare i figli di un commit.

git rev-list --children
git log --children

Nota: per i commit parent , hai lo stesso problema, con il suffisso ^per un parametro di revisione che significa il primo parent dell'oggetto commit. ^<n>indica il <n>genitore (ovvero rev^ equivalente a rev^1).

Se sei in filiale fooe emetti " git merge bar", allora foosarà il primo genitore.
Vale a dire: il primo genitore è il ramo in cui ti trovavi quando ti sei unito, e il secondo è il commit sul ramo in cui ti sei unito.


6
git rev-list --childrensembra proprio quello che voglio, ma non DWIM. Sembra elencare tutti i genitori e i loro figli. Suppongo di poterli elencare tutti e analizzarli ... bleh, ma è qualcosa.
Schwern,

@Schwern: vero, git rev-list --childrennon è per elencare solo i bambini, ma per elencare i genitori con i loro figli ... devi sempre analizzare.
VonC,

Ho provato quel codice su un commit con due figli: $ git children0of 9dd5932 fatal: No annotated tags can describe '71d7b5dd89d241072a0a078ff2c7dfec05d52e1f'. However, there were unannotated tags: try --tags. quale output ottieni?
Tom Hale,

@TomHale Non posso provarlo in questo momento, ma fai una nuova domanda (con OS e versione Git), in questo modo tutti possono testare.
VonC,

1
L'unico problema git-children-ofè che usa git descrivono che tenta di formattare l'SHA come leggibile dall'uomo, il che può fallire con l'errore di @ TomHale e che dà risultati del genere v1.0.4-14-g2414721confondenti se ti aspettavi uno SHA. Sostituirlo con un semplice lo echorende uno strumento eccellente, grazie!
Nickolay,

18

quello che ho trovato è

git rev-list --ancestry-path commit1..commit2

dove ho impostato commit1come commit corrente e commit2al capo corrente. Questo mi restituisce un elenco di tutti i commit che creano un percorso tra commit1e commit2.

L'ultima riga dell'output è figlio di commit1 (sul percorso di commit2).


3
Quindi basta aggiungere | tail -1per ottenere il bambino.
Jesse Glick,

8

So cosa vuoi dire. È frustrante avere una sintassi abbondante per andare a commit precedenti, ma nessuno per passare a quelli successivi. In una storia complessa, il problema di "qual è il prossimo impegno" diventa piuttosto difficile, ma poi nella fusione complessa emerge la stessa durezza con gli impegni "precedenti". Nel caso semplice, all'interno di un singolo ramo con una storia lineare (anche solo localmente per un numero limitato di commit) sarebbe bello e sensato andare avanti e indietro.

Il vero problema con questo, tuttavia, è che i commit dei bambini non sono referenziati, è solo un elenco con collegamenti a ritroso. Trovare il commit del bambino richiede una ricerca, che non è poi così male, ma probabilmente non è qualcosa che Git vuole mettere nella logica refspec.

Ad ogni modo, mi sono posto su questa domanda perché voglio semplicemente fare un passo avanti nella storia, una volta alla volta, facendo dei test, e talvolta devi fare un passo avanti e non indietro. Bene, con qualche pensiero in più ho trovato questa soluzione:

Scegli un commit prima di dove ti trovi. Questo potrebbe probabilmente essere una testa di ramo. Se sei al ramo ~ 10, quindi "git checkout branch ~ 9", quindi "git checkout branch ~ 8" per ottenere il successivo, quindi "git checkout branch ~ 7" e così via.

Diminuire il numero dovrebbe essere davvero facile in uno script se ne hai bisogno. Molto più semplice dell'analisi di git rev-list.


Sebbene utile, non trova il prossimo commit. Cammina verso l'attuale commit.
Schwern,

1
Bene, allora suppongo che potresti fare: " BRANCH=master; git co $BRANCH~$[ $(git rev-list HEAD..$BRANCH | wc -l) - 1 ]" Devi andare verso un ramo, assolutamente no.
Vontrapp,

8

Due risposte pratiche:

Un bambino

Basato sulla risposta di @Michael , ho hackerato l' childalias nel mio .gitconfig.

Funziona come previsto nel caso predefinito ed è anche versatile.

# Get the child commit of the current commit.
# Use $1 instead of 'HEAD' if given. Use $2 instead of curent branch if given.
child = "!bash -c 'git log --format=%H --reverse --ancestry-path ${1:-HEAD}..${2:\"$(git rev-parse --abbrev-ref HEAD)\"} | head -1' -"

L'impostazione predefinita è dare il figlio di HEAD (a meno che non venga fornito un altro argomento commit-ish) seguendo la progenie un passo verso la punta del ramo corrente (a meno che non venga fornito un altro commit-ish come secondo argomento).

Utilizzare %hinvece di %Hse si desidera il modulo hash breve.

Più bambini

Con una TESTA staccata (non c'è ramo) o per ottenere tutti i bambini indipendentemente dai rami:

# For the current (or specified) commit-ish, get the all children, print the first child 
children = "!bash -c 'c=${1:-HEAD}; set -- $(git rev-list --all --not \"$c\"^@ --children | grep $(git rev-parse \"$c\") ); shift; echo $1' -"

Cambia $1in $*per stampare tutti i bambini.

Puoi anche passare --alla un commit-ish per visualizzare solo i figli che sono antenati di quel commit, in altre parole, per mostrare solo i bambini "nella direzione di" il commit dato. Questo può aiutarti a restringere l'output da molti bambini a uno solo.


7

Non esiste un unico "prossimo commit". Poiché la cronologia in Git è un DAG e non una riga, molti commit possono avere un genitore comune (filiali) e i commit possono avere più di un genitore (fusioni).

Se hai in mente un ramo particolare, puoi guardare il suo registro e vedere quale commit elenca quello attuale come suo genitore.


37
Secondo quella logica non esiste nemmeno un "commit precedente", ma c'è molta sintassi per ottenere i genitori.
Schwern,

7
@Schwern: non esiste neanche un "commit precedente"; <rev>^è "parent commit" ('first parent' per i merge commit).
Jakub Narębski,

6

Ho provato molte soluzioni diverse e nessuna ha funzionato per me. Ho dovuto inventare il mio.

trova il prossimo commit

function n() {
    git log --reverse --pretty=%H master | grep -A 1 $(git rev-parse HEAD) | tail -n1 | xargs git checkout
}

trova il commit precedente

function p() {
    git checkout HEAD^1
}

6

Nel caso in cui non si abbia in mente un commit "destinazione" particolare, ma si desidera invece vedere i commit figlio che potrebbero trovarsi su qualsiasi ramo, è possibile utilizzare questo comando:

git rev-list --children --all | grep ^${COMMIT}

Se vuoi vedere tutti i bambini e i nipoti , devi usare rev-list --childrenricorsivamente, in questo modo:

git rev-list --children --all | \
egrep ^\($(git rev-list --children --all | \
           grep ^${COMMIT} | \
           sed 's/ /|/g')\)

(La versione che dà solo ai nipoti userebbe un metodo più complesso sede / o cut.)

Infine, puoi inserire quello in un log --graphcomando per vedere la struttura ad albero, in questo modo:

git log --graph --oneline --decorate \
\^${COMMIT}^@ \
$(git rev-list --children --all | \
  egrep ^\($(git rev-list --children --all | \
             grep ^${COMMIT} | \
             sed 's/ /|/g')\))

Nota : tutti i comandi precedenti presuppongono che tu abbia impostato la variabile shell ${COMMIT}su un riferimento (branch, tag, sha1) del commit di cui sei interessato.


2
questo risponde alla domanda che non sapevo formulare per Google e StackOverflow, ma che stavo cercando di porre. grazie per aver riconosciuto in modo proattivo la necessità
Tommy Knowlton,

per me non ${COMMIT}esiste, ma potresti usare $(git rev-parse HEAD) invece
Radon8472

scusa, ho aggiunto una nota in merito ${COMMIT}.
Matt McHenry il

5

(Questo è iniziato come una risposta a una domanda duplicata. Ho fatto un po 'di editing leggero per ripulirlo.)

Tutte le frecce interne di Git sono unidirezionali, rivolte all'indietro. Non esiste quindi una sintassi breve per spostarsi in avanti: semplicemente non è possibile.

E ' è possibile "muoversi contro le frecce", ma il modo per farlo è sorprendente se non avete visto prima, e poi evidente in seguito. Diciamo che abbiamo:

A <-B <-C <-D <-E   <-- last
        ^
        |
         \--------- middle

L'utilizzo middle~2segue le frecce due volte da Cdietro a A. Quindi come ci spostiamo da Ca D? La risposta è: iniziamo da E, usando il nome last, e lavoriamo all'indietro fino a quando arriviamo middle, registrando i punti che visitiamo lungo la strada . Quindi ci spostiamo semplicemente nella direzione di last: muoviamo di un passo verso Do due verso E.

Ciò è particolarmente importante quando abbiamo filiali:

          D--E   <-- feature1
         /
...--B--C   <-- master
         \
          F--G   <-- feature2

Quale commit è un passo dopo C? Non c'è una risposta corretta fino a quando non si aggiunge alla domanda: nella direzione di feature___ (riempire lo spazio vuoto).

Per elencare i commit tra C(escluso C) stesso e, diciamo, Gusiamo:

git rev-list --topo-order --ancestry-path master..feature2

Questo --topo-orderassicura che anche in presenza di complesse ramificazioni e fusioni, i commit escano in ordine topologicamente ordinato. Ciò è necessario solo se la catena non è lineare. Il --ancestry-pathvincolo significa che quando lavoriamo a ritroso da feature2, elenchiamo solo i commit che hanno commesso Ccome uno dei loro stessi antenati. Cioè, se il grafico, o comunque il relativo pezzo, in realtà assomiglia a questo:

A--B--C   <-- master
 \     \
  \     F--G--J   <-- feature2
   \         /
    H-------I   <-- feature3

una semplice richiesta del modulo feature2..masterenumera commit J, Ge I, e Fe Hin un certo ordine. Con --ancestry-pathknock out He I: non sono discendenti di C, solo di A. Con --topo-orderci assicuriamo che l'attuale ordine di enumerazione sia J, quindi G, allora F.

Il git rev-listcomando distribuisce questi ID hash sull'output standard, uno per riga. Per avanzare di un passo nella direzione di feature2, quindi, vogliamo solo l' ultima riga.

È possibile (e allettante e può essere utile) aggiungere in --reversemodo da git rev-liststampare i commit in ordine inverso dopo averli generati. Questo funziona, ma se lo usi in una pipeline come questa:

git rev-list --topo-order --ancestry-path --reverse <id1>...<id2> | head -1

per ottenere il "prossimo commit nella direzione di id2", e c'è un elenco molto lungo di commit, il git rev-listcomando può ottenere una pipe spezzata quando tenta di scrivere su headcui ha smesso di leggere il suo input ed è uscito. Dal momento che gli errori di tubo rotto vengono normalmente ignorati dalla shell, questo funziona principalmente. Assicurati solo che siano ignorati nel tuo utilizzo.

È anche allettante aggiungere -n 1al git rev-listcomando, insieme a --reverse. Non farlo! Ciò si git rev-listinterrompe dopo aver fatto un passo indietro e quindi invertire l'elenco (una voce) di commit visitati. Quindi questo produce solo <id2>ogni volta.

Nota a margine importante

Si noti che con i frammenti del grafico "diamante" o "anello benzene":

       I--J
      /    \
...--H      M--...  <-- last
      \    /
       K--L

spostando una commettere "avanti" da Hdirezione lastsi arriva sia I o K . Non c'è niente che tu possa fare al riguardo: entrambi i commit sono un passo avanti! Se poi inizi dal commit risultante e fai un altro passo, ora sei impegnato in qualunque percorso tu abbia iniziato.

La cura per questo è di evitare di muoverti di un passo alla volta e rimanere bloccati in catene dipendenti dal percorso. Invece, se prevedi di visitare un'intera catena di percorsi degli antenati, prima di fare qualsiasi altra cosa , fai un elenco completo di tutti i commit nella catena:

git rev-list --topo-order --reverse --ancestry-path A..B > /tmp/list-of-commits

Quindi, visita ogni commit in questo elenco, uno alla volta, e otterrai l'intera catena. La --topo-orderfarà in modo di colpire I-e- Jin questo ordine, e K-e- Lin questo ordine (anche se non c'è un modo semplice per prevedere se si farà la coppia IJ prima o dopo la coppia KL).


" Non c'è una risposta corretta fino a quando non si aggiunge alla domanda: nella direzione di feature___ " Questo è un ottimo punto. Grazie per la tua risposta.
Schwern il

4

Ho questo alias in ~/.gitconfig

first-child = "!f() { git log  --reverse --ancestry-path --pretty=%H $1..${2:-HEAD} | head -1; }; f"


che cos'è f()? E deve head -1essere il primo figlio, altrimenti riferirà semplicemente HEAD.
Xerus,

@Xerus Perché usa una sintassi della shell "complessa" e git non la riconoscerà se non racchiusa f(). Sì, head -1è un'ipotesi coraggiosa.
debole l'

Bello! Ottimizzato il mio per: in nextref = "!f() { git log --reverse --ancestry-path --pretty=%H $1..HEAD | head -${2:-1} | tail -1; }; f"modo da poter scegliere fino a che punto, facoltativamente
Z. Khullah

2

Sono riuscito a trovare il bambino successivo nel modo seguente:

git log --reverse --children -n1 HEAD (where 'n' is the number of children to show)

1
per me questo non mostra il primo figlio, mostra l'attuale commit
Radon8472

2

Se i commit secondari si trovano tutti su un ramo, è possibile utilizzare gitk --all commit^.., dove "commit" è qualcosa che identifica il commit. Ad esempio, se SHA-1 abbreviato del commit è c6661c5, digitaregitk --all c6661c5^..

Probabilmente dovrai inserire l'intero SHA-1 nella cella "SHA1 ID:" di gitk. Sarà necessario l'intero SHA-1, che per questo esempio può essere ottenuto tramitegit rev-parse c6661c5

In alternativa, git rev-list --all --children | grep '^c6661c5883bb53d400ce160a5897610ecedbdc9d'produrrà una riga contenente tutti i figli di questo commit, presumibilmente indipendentemente dal fatto che sia coinvolto un ramo.


0

Ogni commit memorizza un puntatore al suo genitore (genitori, in caso di commit di fusione (standard)).

Quindi, non c'è modo di indicare un commit figlio (se ce n'è uno) da parte del genitore.


Commit non è in grado di memorizzare i puntatori ai propri figli, poiché ulteriori commit secondari (punti di diramazione) possono essere aggiunti in un secondo momento.
Jakub Narębski,

@Jakub Non lo seguo davvero. Non possono essere aggiunti in seguito?
Schwern,

1
Jakub: Questo è esattamente quello che ho detto. "Ogni commit memorizza i puntatori solo al suo genitore."
Lakshman Prasad,

@Schwern: i commit in git sono immutabili (il che ha una piacevole conseguenza della responsabilità), quindi i suggerimenti per i bambini non possono "essere aggiunti in seguito". Uno dei motivi è che l'identificatore del commit (utilizzato ad es. Nei collegamenti "parent") dipende dal contenuto del comit; questa è l'unica soluzione nel sistema distribuito, senza autorità di numerazione centrale. Anche i "figli" dei commit dipendono dai rami che hai, e questo a sua volta può essere diverso da repository a repository (e i commit sono gli stessi in ogni repository).
Jakub Narębski,

4
@becomingGuru L'ho votato. Può essere vero, ma non risponde alla mia domanda. La domanda è "come trovo il prossimo commit in git?" Non è "un commit git memorizza un puntatore ai suoi figli?"
Schwern,


0

Le risposte esistenti presuppongono che tu abbia un ramo contenente il commit che stai cercando.

Nel mio caso, il commit che stavo cercando non era attivo git rev-list --allpoiché nessun ramo lo conteneva.

Ho finito per guardare gitk --reflogmanualmente.

Se non riesci a trovare il commit anche nel reflog, prova anche:

  • git fsck --full elencare le commit penzolanti (cioè non in alcun ramo), o
  • git fsck --lost-found fare refs puntando a penzoloni si impegna ad applicare tecniche nelle altre risposte.
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.