Trovare da quale ramo proviene un commit Git


650

C'è un modo per scoprire da quale ramo proviene un commit dato il suo valore hash SHA-1 ?

Punti bonus se puoi dirmi come farlo usando Ruby Grit.


4
I diversi metodi seguenti sono modi pragmatici, utili e di lavoro per inferire una probabile risposta, ma notiamo che in realtà la domanda stessa è un malinteso, i commit non provengono da rami. I rami vanno e vengono, si muovono, solo i commit rappresentano la vera storia dei repository. Inoltre, questo non è un modo per dire che le soluzioni di seguito sono sbagliate. Sappi solo che nessuno di loro fornisce una risposta completamente affidabile, che è impossibile da ottenere con git. (Un caso semplice sono i rami eliminati: io ramo, mi impegno due volte, mi unisco in un altro ramo, cancello il primo ramo. Da dove viene il commit "provengono"?
RomainValeri

1
Ho un caso in cui estraggo esplicitamente un clone di profondità 1 da un tag. È economico, facile ed efficiente ... e fino ad ora ha fatto tutto ciò che desideravo magnificamente. Ora che mi piacerebbe sapere su quale ramo si trovava il tag, sono entusiasta, almeno per quel dettaglio. Non puoi sempre andare a casa, lol
Paul Hodges il

Risposte:


865

Mentre Dav ha ragione a dire che le informazioni non sono memorizzate direttamente, ciò non significa che non puoi mai scoprirlo. Ecco alcune cose che puoi fare.

Trova rami su cui è attivo il commit

git branch -a --contains <commit>

Questo ti dirà tutti i rami che hanno il proprio impegno nella loro storia. Ovviamente questo è meno utile se il commit è già stato unito.

Cerca nei reflog

Se si lavora nel repository in cui è stato eseguito il commit, è possibile cercare i reflog per la riga per quel commit. I reflog più vecchi di 90 giorni sono potati da git-gc, quindi se il commit è troppo vecchio, non lo troverai. Detto questo, puoi farlo:

git reflog show --all | grep a871742

per trovare commit a871742. Nota che DEVI utilizzare le prime 7 cifre abbreviate del commit. L'output dovrebbe essere qualcosa del genere:

a871742 refs/heads/completion@{0}: commit (amend): mpc-completion: total rewrite

indicando che il commit è stato eseguito sul ramo "completamento". L'output predefinito mostra hash di commit abbreviati, quindi assicurati di non cercare l'hash completo o non troverai nulla.

git reflog showè in realtà solo un alias per git log -g --abbrev-commit --pretty=oneline, quindi se vuoi giocherellare con il formato di output per rendere disponibili diverse cose per grep, questo è il tuo punto di partenza!

Se non stai lavorando nel repository in cui è stato eseguito il commit, il meglio che puoi fare in questo caso è esaminare i reflog e trovare quando il commit è stato introdotto per la prima volta nel tuo repository; con un po 'di fortuna, hai recuperato il ramo in cui era impegnato. Questo è un po 'più complesso, perché non è possibile eseguire contemporaneamente il commit dell'albero e il reflog. Si vorrebbe analizzare l'output reflog, esaminando ogni hash per vedere se contiene il commit desiderato o meno.

Trova un successivo commit di unione

Questo dipende dal flusso di lavoro, ma con buoni flussi di lavoro, i commit vengono effettuati sui rami di sviluppo che vengono poi uniti. È possibile effettuare ciò:

git log --merges <commit>..

per vedere i commit di unione che hanno il commit dato come antenato. (Se il commit è stato unito solo una volta, il primo dovrebbe essere l'unione che stai cercando; altrimenti dovresti esaminarne alcuni, suppongo.) Il messaggio di commit di unione dovrebbe contenere il nome del ramo che è stato unito.

Se si desidera poter contare su ciò, è possibile utilizzare l' --no-ffopzione git mergeper forzare la creazione del commit di unione anche nel caso di avanzamento rapido. (Non essere troppo ansioso, però. Ciò potrebbe diventare offuscato se abusato .) La risposta di VonC a una domanda correlata elabora utili su questo argomento.


5
+1. La semplice assenza di informazioni per quel particolare problema ti fa chiedere se si tratta effettivamente di un problema? I rami possono cambiare, essere rinominati o eliminati in qualsiasi momento. Può essere git describesufficiente, perché i tag (annotati) possono essere visualizzati come più significativi dei rami.
VonC,

9
Sono assolutamente d'accordo: le filiali sono pensate per essere leggere e flessibili. Se adotti un flusso di lavoro in cui tali informazioni diventano importanti, puoi utilizzare l' --no-ffopzione per assicurarti che sia sempre presente un commit di unione, in modo da poter sempre tracciare il percorso di un determinato commit man mano che viene unito al master.
Cascabel,

32
Cordiali saluti, se si desidera trovare commit che esistono solo sul telecomando, aggiungere la -abandiera al primo comando, ad es.git branch -a --contains <commit>
Jonathan Day

8
@JonathanDay: No, questo troverà commit su qualsiasi filiale. Se vuoi solo quelli sul telecomando, usa -r.
Cascabel,

7
@SimonTewsi Come ho detto, se è davvero un problema, utilizzare merge --no-ffper registrare in modo affidabile il nome del ramo quando si unisce. Altrimenti, pensa ai nomi delle filiali come etichette brevi temporanee e impegna le descrizioni come permanenti. "A quale breve nome ci siamo riferiti a questo durante lo sviluppo?" non dovrebbe essere una domanda tanto importante quanto "cosa fa questo commit?"
Cascabel,

155

Questo semplice comando funziona come un incantesimo:

git name-rev <SHA>

Ad esempio (dove test-branch è il nome del ramo):

git name-rev 651ad3a
251ad3a remotes/origin/test-branch

Anche questo funziona per scenari complessi, come:

origin/branchA/
              /branchB
                      /commit<SHA1>
                                   /commit<SHA2>

Qui git name-rev commit<SHA2>restituisce branchB .


3
Mi hai salvato la giornata. Questa è la soluzione perfetta.
Taco,

7
E ci sono voluti solo 8 anni per questa soluzione perfetta. Grazie!
S1J0

6
Ho trovato git name-rev --name-only <SHA>più utile per ottenere solo il nome del ramo. La mia domanda ... Può restituire più di un ramo in qualsiasi circostanza?
Dean Kayton,

1
Questa dovrebbe essere la risposta migliore.
JCollier,

1
Grazie @JCollier per le tue gentili parole.
khichar.anil,

47

Aggiornamento dicembre 2013:

commenti sschuberth

git-what-branch(Perl script, vedi sotto) non sembra più essere mantenuto. git-when-mergedè un'alternativa scritta in Python che funziona molto bene per me.

Si basa su " Trova commit unione che include un commit specifico ".

git when-merged [OPTIONS] COMMIT [BRANCH...]

Trova quando un commit è stato unito in uno o più rami.
Trova il commit di merge che ha portato COMMITai BRANCH specificati.

In particolare, cerca il commit più vecchio nella cronologia del primo genitore BRANCHche contiene l' COMMITantenato.


Risposta originale settembre 2010:

Sebastien Douche ha appena twittato (16 minuti prima di questa risposta SO):

git-what-branch : Scopri su quale ramo si trova un commit o come è arrivato a un ramo con nome

Questo è uno script Perl di Seth Robertson che sembra molto interessante:

SINOSSI

git-what-branch [--allref] [--all] [--topo-order | --date-order ]
[--quiet] [--reference-branch=branchname] [--reference=reference]
<commit-hash/tag>...

PANORAMICA

Indica (per impostazione predefinita) il primo percorso causale dei commit e delle fusioni per far sì che il commit richiesto sia entrato in un ramo denominato. Se un commit è stato effettuato direttamente su un ramo con nome, quello ovviamente è il primo percorso.

Per primo percorso causale, intendiamo il percorso che si è unito il più presto in un ramo denominato, per tempo di commit (a meno che non --topo-ordersia specificato).

PRESTAZIONE

Se molti rami (ad es. Centinaia) contengono il commit, il sistema potrebbe impiegare molto tempo (per un commit specifico nella struttura di Linux, ci sono voluti 8 secondi per esplorare un ramo, ma c'erano oltre 200 rami candidati) per rintracciare il percorso a ciascun commit.
La selezione di un particolare --reference-branch --reference tagda esaminare sarà centinaia di volte più veloce (se hai centinaia di rami candidati).

ESEMPI

 # git-what-branch --all 1f9c381fa3e0b9b9042e310c69df87eaf9b46ea4
 1f9c381fa3e0b9b9042e310c69df87eaf9b46ea4 first merged onto master using the following minimal temporal path:
   v2.6.12-rc3-450-g1f9c381 merged up at v2.6.12-rc3-590-gbfd4bda (Thu May  5 08:59:37 2005)
   v2.6.12-rc3-590-gbfd4bda merged up at v2.6.12-rc3-461-g84e48b6 (Tue May  3 18:27:24 2005)
   v2.6.12-rc3-461-g84e48b6 is on master
   v2.6.12-rc3-461-g84e48b6 is on v2.6.12-n
   [...]

Questo programma non tiene conto degli effetti della raccolta delle ciliegie sul commit degli interessi, ma solo unisce le operazioni.


4
git-what-branchnon sembra più essere mantenuto. git-when-merged è un'alternativa scritta in Python che funziona molto bene per me.
sschuberth,

@sschuberth grazie per questo aggiornamento. Ho incluso il tuo commento nella risposta per una maggiore visibilità.
VonC,

37

Ad esempio, per trovare quel c0118facommit proviene da redesign_interactions:

* ccfd449 (HEAD -> develop) Require to return undef if no digits found
*   93dd5ff Merge pull request #4 from KES777/clean_api
|\
| * 39d82d1 Fix tc0118faests for debugging debugger internals
| * ed67179 Move &push_frame out of core
| * 2fd84b5 Do not lose info about call point
| * 3ab09a2 Improve debugger output: Show info about emitted events
| *   a435005 Merge branch 'redesign_interactions' into clean_api
| |\
| | * a06cc29 Code comments
| | * d5d6266 Remove copy/paste code
| | * c0118fa Allow command to choose how continue interaction
| | * 19cb534 Emit &interact event

Dovresti eseguire:

git log c0118fa..HEAD --ancestry-path --merges

E scorrere verso il basso per trovare l'ultimo commit di unione . Che è:

commit a435005445a6752dfe788b8d994e155b3cd9778f
Merge: 0953cac a06cc29
Author: Eugen Konkov
Date:   Sat Oct 1 00:54:18 2016 +0300

    Merge branch 'redesign_interactions' into clean_api

Aggiornare

O solo un comando:

git log c0118fa..HEAD --ancestry-path --merges --oneline --color | tail -n 1

4
Questa è stata l'unica soluzione che mi ha dato il risultato desiderato. Ho provato il resto.
Crafter,

Nota che funziona solo se i riepiloghi del commit di unione contengono i nomi dei rami utilizzati nelle fusioni, cosa che di solito fanno normalmente. Tuttavia git merge -m"Any String Here", oscurerà le informazioni sul ramo di origine e di destinazione.
qneill,

@qneill: No, l'unione ha sempre informazioni su rami uniti: Merge: f6b70fa d58bdcb. Puoi nominare il tuo commit di unione come te. Non avrò importanza
Eugen Konkov, il

1
@EugenKonkov dopo che i rami hanno attraversato l'unione, la cronologia su quale percorso nel grafico da cui provengono viene lasciata nel reflog, ma non nel database degli oggetti git. Ho pubblicato una risposta cercando di spiegare quello che sto dicendo
qneill,

La versione UPD funziona per me, semplice comando che trova il commit originale
vpalmu

9

git branch --contains <ref>è il comando "porcellana" più ovvio per farlo. Se vuoi fare qualcosa di simile solo con i comandi "idraulici":

COMMIT=$(git rev-parse <ref>) # expands hash if needed
for BRANCH in $(git for-each-ref --format "%(refname)" refs/heads); do
  if $(git rev-list $BRANCH | fgrep -q $COMMIT); then
    echo $BRANCH
  fi
done

(crosspost da questa risposta SO )


6

khichar.anil ha coperto gran parte di questo nella sua risposta.

Sto solo aggiungendo la bandiera che rimuoverà i tag dall'elenco dei nomi delle revisioni. Questo ci dà:

git name-rev --name-only --exclude=tags/* $SHA

Sembra che manchi qualcosa nella seconda frase.
Peter Mortensen,

Ho aggiornato la descrizione. Grazie per il feedback.
Phyatt,

4

L'opzione di un uomo povero è usare lo strumento tig1 su HEAD, cercare il commit e quindi seguire visivamente la linea da quel commit di backup fino a quando non viene visualizzato un commit di unione. Il messaggio di unione predefinito dovrebbe specificare quale ramo viene unito a dove :)

1 Tig è un'interfaccia in modalità testo basata su ncurses per Git. Funziona principalmente come un browser di repository Git, ma può anche aiutare a mettere in scena le modifiche per il commit a livello di blocco e fungere da cercapersone per l'output da vari comandi Git.


3

Come esperimento, ho creato un hook post-commit che memorizza le informazioni sul ramo attualmente estratto nei metadati di commit. Ho anche leggermente modificato gitk per mostrare quelle informazioni.

Puoi verificarlo qui: https://github.com/pajp/branch-info-commits


1
Ma se vuoi aggiungere metadati, perché non usare git notes, invece di fare casini con le intestazioni di commit? Vedere stackoverflow.com/questions/5212957/... , stackoverflow.com/questions/7298749/... o stackoverflow.com/questions/7101158/...
VonC

Ad essere sincero, non sapevo che esistessero note git quando l'ho scritto. Sì, usare le note git per ottenere la stessa cosa è probabilmente un'idea migliore.
pajp,

3

Ho a che fare con lo stesso problema ( pipeline multibranch Jenkins ): avere solo informazioni di commit e cercare di trovare un nome di filiale da cui proviene originariamente questo commit. Deve funzionare per filiali remote, le copie locali non sono disponibili.

Questo è ciò con cui lavoro:

git rev-parse HEAD | xargs git name-rev

Opzionalmente è possibile eliminare l'output:

git rev-parse HEAD | xargs git name-rev | cut -d' ' -f2 | sed 's/remotes\/origin\///g'

0

Penso che qualcuno dovrebbe affrontare lo stesso problema che non è in grado di scoprire il ramo, anche se in realtà esiste in un ramo.

Faresti meglio a tirare tutto per primo:

git pull --all

Quindi cerca la filiale:

git name-rev <SHA>

o:

git branch --contains <SHA>

0

Se l'OP sta cercando di determinare la storia che è stato attraversato da un ramo in cui una particolare commit è stato creato ( "scoprire cosa ramo un commit viene da dato il suo valore di hash SHA-1"), quindi senza la reflog Non ci sono alcun record nel database degli oggetti Git che mostra quale ramo denominato era associato a quale cronologia di commit.

(L'ho pubblicato come risposta in risposta a un commento.)

Spero che questa sceneggiatura illustri il mio punto:

rm -rf /tmp/r1 /tmp/r2; mkdir /tmp/r1; cd /tmp/r1
git init; git config user.name n; git config user.email e@x.io
git commit -m"empty" --allow-empty; git branch -m b1; git branch b2
git checkout b1; touch f1; git add f1; git commit -m"Add f1"
git checkout b2; touch f2; git add f2; git commit -m"Add f2"
git merge -m"merge branches" b1; git checkout b1; git merge b2
git clone /tmp/r1 /tmp/r2; cd /tmp/r2; git fetch origin b2:b2
set -x;
cd /tmp/r1; git log --oneline --graph --decorate; git reflog b1; git reflog b2;
cd /tmp/r2; git log --oneline --graph --decorate; git reflog b1; git reflog b2;

L'output mostra la mancanza di alcun modo per sapere se il commit con 'Aggiungi f1' proviene dal ramo b1 o b2 dal clone remoto / tmp / r2.

(Le ultime righe dell'output qui)

+ cd /tmp/r1
+ git log --oneline --graph --decorate
*   f0c707d (HEAD, b2, b1) merge branches
|\
| * 086c9ce Add f1
* | 80c10e5 Add f2
|/
* 18feb84 empty
+ git reflog b1
f0c707d b1@{0}: merge b2: Fast-forward
086c9ce b1@{1}: commit: Add f1
18feb84 b1@{2}: Branch: renamed refs/heads/master to refs/heads/b1
18feb84 b1@{3}: commit (initial): empty
+ git reflog b2
f0c707d b2@{0}: merge b1: Merge made by the 'recursive' strategy.
80c10e5 b2@{1}: commit: Add f2
18feb84 b2@{2}: branch: Created from b1
+ cd /tmp/r2
+ git log --oneline --graph --decorate
*   f0c707d (HEAD, origin/b2, origin/b1, origin/HEAD, b2, b1) merge branches
|\
| * 086c9ce Add f1
* | 80c10e5 Add f2
|/
* 18feb84 empty
+ git reflog b1
f0c707d b1@{0}: clone: from /tmp/r1
+ git reflog b2
f0c707d b2@{0}: fetch origin b2:b2: storing head

E a cosa servono l'output git log 80c10e5..HEAD --ancestry-path --merges --oneline --color | tail -n 1e i git log 086c9ce..HEAD --ancestry-path --merges --oneline --color | tail -n 1comandi per entrambi i casi?
Eugen Konkov,

Ovviamente gli SHA cambiano ogni volta che vengono eseguiti i comandi, per rispondere ai tuoi comandi ho usato HEAD ^ 1 e HEAD ^ 2 su una nuova corsa. I comandi e l'output sono: $ git log HEAD^1..HEAD --ancestry-path --merges --oneline --color | tail -n 1quali sono i rendimenti 376142d merge branchese $ git log HEAD^2..HEAD --ancestry-path --merges --oneline --color | tail -n 1quali i rendimenti 376142d merge branches - che mostra il riepilogo del commit unione, che (come stavo affermando) può essere sovrascritto quando viene creata l'unione, probabilmente offuscando la cronologia del ramo dell'unione.
qneill

0

TL; DR:

Usa quanto segue se ti interessa lo stato di uscita della shell:

  • branch-current - il nome della filiale attuale
  • branch-names - nomi di rami puliti (uno per riga)
  • branch-name - Accertarsi che venga restituito solo un ramo branch-names

Entrambi branch-namee branch-namesaccettano un commit come argomento, e HEADse non ne viene fornito uno di default .


Alias ​​utili negli script

branch-current = "symbolic-ref --short HEAD"  # https://stackoverflow.com/a/19585361/5353461
branch-names = !"[ -z \"$1\" ] && git branch-current 2>/dev/null || git branch --format='%(refname:short)' --contains \"${1:-HEAD}\" #"  # https://stackoverflow.com/a/19585361/5353461
branch-name = !"br=$(git branch-names \"$1\") && case \"$br\" in *$'\\n'*) printf \"Multiple branches:\\n%s\" \"$br\">&2; exit 1;; esac; echo \"$br\" #"

Commit raggiungibile solo da un solo ramo

% git branch-name eae13ea
master
% echo $?
0
  • L'uscita è su STDOUT
  • Il valore di uscita è 0.

Commit raggiungibile da più filiali

% git branch-name 4bc6188
Multiple branches:
attempt-extract
master%
% echo $?
1
  • L'output è su STDERR
  • Il valore di uscita è 1.

A causa dello stato di uscita, questi possono essere costruiti in modo sicuro. Ad esempio, per ottenere il telecomando utilizzato per il recupero:

remote-fetch = !"branch=$(git branch-name \"$1\") && git config branch.\"$branch\".remote || echo origin #"

-1

Per trovare la filiale locale:

grep -lR YOUR_COMMIT .git/refs/heads | sed 's/.git\/refs\/heads\///g'

Per trovare il ramo remoto:

grep -lR $commit .git/refs/remotes | sed 's/.git\/refs\/remotes\///g'

1
alcune spiegazioni dei tuoi esempi saranno fantastiche
Eugen Konkov,

-4

Oltre a cercare in tutto l'albero fino a trovare un hash corrispondente, no.


7
Non voglio davvero sottovalutare questo, perché in senso stretto è vero che git non memorizza permanentemente tali informazioni, ma è molto spesso possibile scoprirlo comunque. Vedi la mia risposta
Cascabel,
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.