Trouver de quelle branche provient un commit Git

651

Existe-t-il un moyen de savoir de quelle branche provient un commit étant donné son SHA-1 valeur de hachage ?

Des points bonus si vous pouvez me dire comment accomplir cela en utilisant Ruby Grit.

Ethan Gunderson
la source
4
Les différentes méthodes ci-dessous sont des méthodes pragmatiques, utiles et efficaces pour déduire une réponse probable , mais notons que dans git, la question elle-même est un malentendu, les commits ne viennent pas des branches. Les succursales vont et viennent, elles bougent, seuls les commits représentent la véritable histoire du repo. Là encore, ce n'est pas une façon de dire que les solutions ci-dessous sont mauvaises. Sachez simplement qu'aucun d'eux ne donne une réponse entièrement fiable, ce qui est impossible par conception dans git. (Un cas simple est celui des branches supprimées: je branche, je commets deux fois, je fusionne dans une autre branche, je supprime la première branche. D'où le commit "vient"?
RomainValeri
1
J'ai un cas où je tire explicitement un clone peu profond de profondeur 1 d'une balise. C'est bon marché et facile et efficace ... et jusqu'à présent, j'ai fait tout ce que je voulais magnifiquement. Maintenant que j'aimerais savoir sur quelle branche se trouvait la balise, je me suis fait arroser, du moins pour ce détail. Vous ne pouvez pas toujours rentrer à la maison, lol
Paul Hodges

Réponses:

865

Bien que Dav ait raison de dire que les informations ne sont pas directement stockées, cela ne signifie pas que vous ne pourrez jamais le découvrir. Voici quelques choses que vous pouvez faire.

Rechercher les branches sur lesquelles le commit est activé

git branch -a --contains <commit>

Cela vous indiquera toutes les branches qui ont le commit donné dans leur histoire. Évidemment, cela est moins utile si la validation a déjà été fusionnée.

Rechercher les reflogs

Si vous travaillez dans le référentiel dans lequel la validation a été effectuée, vous pouvez rechercher les reflogs pour la ligne de cette validation. Les reflogs de plus de 90 jours sont élagués par git-gc, donc si le commit est trop ancien, vous ne le trouverez pas. Cela dit, vous pouvez le faire:

git reflog show --all | grep a871742

pour trouver le commit a871742. Notez que vous DEVEZ utiliser les 7 premiers chiffres abrégés du commit. La sortie devrait ressembler à ceci:

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

indiquant que la validation a été effectuée sur la branche "achèvement". La sortie par défaut affiche des hachages de validation abrégés, alors assurez-vous de ne pas rechercher le hachage complet ou vous ne trouverez rien.

git reflog show est en fait juste un alias pour git log -g --abbrev-commit --pretty=oneline , donc si vous voulez jouer avec le format de sortie pour rendre différentes choses disponibles pour grep, c'est votre point de départ!

Si vous ne travaillez pas dans le référentiel où la validation a été effectuée, le mieux que vous puissiez faire dans ce cas est d'examiner les reflogs et de trouver quand la validation a été introduite pour la première fois dans votre référentiel; avec un peu de chance, vous êtes allé chercher la branche dans laquelle il s'était engagé. C'est un peu plus complexe, car vous ne pouvez pas parcourir à la fois l'arbre de validation et les reflogs. Vous souhaitez analyser la sortie reflog, en examinant chaque hachage pour voir s'il contient le commit souhaité ou non.

Rechercher un commit de fusion ultérieur

Cela dépend du flux de travail, mais avec de bons flux de travail, les validations sont effectuées sur les branches de développement qui sont ensuite fusionnées. Vous pouvez le faire:

git log --merges <commit>..

pour voir les validations de fusion qui ont le commit donné comme ancêtre. (Si la validation n'a été fusionnée qu'une seule fois, la première devrait être la fusion que vous recherchez; sinon, vous devrez en examiner quelques-unes, je suppose.) Le message de validation de la fusion doit contenir le nom de la branche qui a été fusionnée.

Si vous voulez pouvoir compter sur cela, vous pouvez utiliser l' --no-ffoption git mergepour forcer la création de commit de fusion même dans le cas d'avance rapide. (Ne soyez pas trop impatient, cependant. Cela pourrait devenir obscurcissant s'il est surutilisé.) La réponse de VonC à une question connexe développe utilement ce sujet.

Cascabel
la source
5
+1. L'absence totale d'informations sur ce problème particulier vous fait vous demander s'il s'agit réellement d'un problème? Les succursales peuvent changer, être renommées ou supprimées à tout moment. Cela peut git describesuffire, car les balises (annotées) peuvent être considérées comme plus importantes que les branches.
VonC
9
Je suis tout à fait d'accord - les branches sont conçues pour être légères et flexibles. Si vous adoptez un flux de travail dans lequel ces informations deviennent importantes, vous pouvez utiliser l' --no-ffoption pour vous assurer qu'il y a toujours un commit de fusion, afin que vous puissiez toujours tracer le chemin d'un commit donné lors de sa fusion vers master.
Cascabel
32
Pour info, si vous voulez trouver des validations qui n'existent que sur la télécommande, ajoutez le -adrapeau à la première commande, par exemplegit branch -a --contains <commit>
Jonathan Day
8
@JonathanDay: Non, cela trouvera des commits sur n'importe quelle branche. Si vous ne souhaitez en utiliser que sur la télécommande, utilisez -r.
Cascabel
7
@SimonTewsi Comme je l'ai dit, si c'est vraiment un problème, utilisez merge --no-ffpour enregistrer de manière fiable le nom de la branche lorsque vous fusionnez. Mais sinon, considérez les noms de branche comme des étiquettes courtes temporaires et validez les descriptions comme permanentes. "Par quel nom abrégé avons-nous fait référence à cela pendant le développement?" ne devrait vraiment pas être une question aussi importante que "que fait cet engagement?"
Cascabel
156

Cette commande simple fonctionne comme un charme:

git name-rev <SHA>

Par exemple (où test-branch est le nom de la branche):

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

Même cela fonctionne pour des scénarios complexes, comme:

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

Ici, git name-rev commit<SHA2>renvoie branchB .

khichar.anil
la source
3
Tu m'as sauvé la journée. C'est la solution parfaite.
Taco
8
Et il n'a fallu que 8 ans pour cette solution parfaite. Merci!
S1J0
6
J'ai trouvé git name-rev --name-only <SHA>plus utile pour obtenir uniquement le nom de la branche. Ma question ... Peut-elle retourner plus d'une succursale en toute circonstance?
Dean Kayton
1
Ce devrait être la meilleure réponse.
JCollier
1
Merci @JCollier pour vos aimables paroles.
khichar.anil
47

Mise à jour de décembre 2013:

Commentaires de sschuberth

git-what-branch(Script Perl, voir ci-dessous) ne semble plus être maintenu. git-when-mergedest une alternative écrite en Python qui fonctionne très bien pour moi.

Il est basé sur " Rechercher un commit de fusion qui inclut un commit spécifique ".

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

Trouvez quand un commit a été fusionné dans une ou plusieurs branches.
Recherchez le commit de fusion qui a été introduit COMMITdans les BRANCHs spécifiés.

Plus précisément, recherchez le commit le plus ancien dans l'historique du premier parent BRANCHqui contient le COMMITcomme ancêtre.


Réponse originale septembre 2010:

Sébastien Douche vient de twitter (16 minutes avant cette réponse SO):

git-what-branch : Découvrez sur quelle branche se trouve un commit, ou comment il est arrivé à une branche nommée

Voici un script Perl de Seth Robertson qui semble très intéressant:

SYNOPSIS

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

APERÇU

Indiquez-nous (par défaut) le chemin causal le plus ancien des validations et des fusions pour que la validation demandée atteigne une branche nommée. Si une validation a été effectuée directement sur une branche nommée, c'est évidemment le premier chemin.

Par chemin causal le plus ancien, nous entendons le chemin qui a fusionné en une branche nommée le plus tôt, par temps de validation (sauf si --topo-orderspécifié).

PERFORMANCE

Si de nombreuses branches (par exemple des centaines) contiennent la validation, le système peut prendre beaucoup de temps (pour une validation particulière dans l'arborescence Linux, il a fallu 8 secondes pour explorer une branche, mais il y avait plus de 200 branches candidates) pour suivre le chemin à chaque validation.
Sélection d'un particulier--reference-branch --reference tag à examiner sera des centaines de fois plus rapide (si vous avez des centaines de branches candidates).

EXEMPLES

 # 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
   [...]

Ce programme ne prend pas en compte les effets de la sélection à la volée de l'engagement d'intérêt, mais uniquement des opérations de fusion.

VonC
la source
4
git-what-branchne semble plus se maintenir. git-when-merged est une alternative écrite en Python qui fonctionne très bien pour moi.
sschuberth
@sschuberth merci pour cette mise à jour. J'ai inclus votre commentaire dans la réponse pour plus de visibilité.
VonC
37

Par exemple, pour trouver que la c0118favalidation est venue de 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

Vous devez exécuter:

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

Faites défiler vers le bas pour trouver la dernière validation de fusion . Lequel est:

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

    Merge branch 'redesign_interactions' into clean_api

Mise à jour

Ou juste une commande:

git log c0118fa..HEAD --ancestry-path --merges --oneline --color | tail -n 1
Eugen Konkov
la source
4
C'était la seule solution qui m'a donné le résultat souhaité. J'ai essayé le reste.
artisan
Notez que cela ne fonctionne que si les résumés de validation de fusion contiennent les noms de branche utilisés dans les fusions, ce qu'ils font généralement par défaut. Cependant git merge -m"Any String Here", les informations sur les branches source et cible seront masquées.
qneill
@qneill: Non, la fusion a toujours plus sur les branches fusionnées: Merge: f6b70fa d58bdcb. Vous pouvez nommer votre commit de fusion comme vous. Je n'aurai pas d'importance
Eugen Konkov
1
@EugenKonkov une fois que les branches ont traversé la fusion, l'historique du chemin dans le graphique d'où elles proviennent est laissé dans le reflog, mais pas dans la base de données d'objets git. J'ai posté une réponse en essayant d'expliquer ce que je dis
qneill
La version UPD fonctionne pour moi, une commande simple qui trouve le commit d'origine
vpalmu
9

git branch --contains <ref>est la commande "porcelaine" la plus évidente pour ce faire. Si vous voulez faire quelque chose de similaire avec seulement des commandes "plomberie":

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 de cette réponse SO )

Jeff Bowman
la source
6

khichar.anil a couvert la plupart de cela dans sa réponse.

J'ajoute juste le drapeau qui supprimera les balises de la liste des noms de révision. Cela nous donne:

git name-rev --name-only --exclude=tags/* $SHA
phyatt
la source
Quelque chose semble manquer dans la deuxième phrase.
Peter Mortensen
J'ai mis à jour la description. Merci pour la rétroaction.
phyatt
4

L'option d'un pauvre est d'utiliser l'outil tig1 sur HEAD, recherchez le commettras, puis suivre visuellement la ligne de ce commit arrière jusqu'à une fusion validation est vu. Le message de fusion par défaut doit spécifier quelle branche est fusionnée à où :)

1 Tig est une interface en mode texte basée sur ncurses pour Git. Il fonctionne principalement comme un navigateur de référentiel Git, mais il peut également aider à organiser les modifications pour la validation au niveau du bloc et agir comme un pager pour la sortie de diverses commandes Git.

user1338062
la source
3

À titre expérimental, j'ai créé un hook post-commit qui stocke des informations sur la branche actuellement extraite dans les métadonnées de commit. J'ai également légèrement modifié gitk pour afficher ces informations.

Vous pouvez le vérifier ici: https://github.com/pajp/branch-info-commits

pajp
la source
1
Mais si vous voulez ajouter des métadonnées, pourquoi ne pas utiliser des notes git, au lieu de jouer avec les en-têtes des commits? Voir stackoverflow.com/questions/5212957/… , stackoverflow.com/questions/7298749/… ou stackoverflow.com/questions/7101158/…
VonC
Pour être honnête, je ne savais pas que des notes git existaient lorsque j'ai écrit cela. Oui, utiliser git notes pour réaliser la même chose est probablement une meilleure idée.
pajp
3

Je fais face au même problème ( Jenkins multibranch pipeline) - n'avoir que des informations de commit et essayer de trouver un nom de branche d'où ce commit est originaire. Il doit fonctionner pour les succursales distantes, les copies locales ne sont pas disponibles.

Voici ce avec quoi je travaille:

git rev-parse HEAD | xargs git name-rev

En option, vous pouvez supprimer la sortie:

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

Je pense que quelqu'un devrait faire face au même problème qui ne peut pas trouver la branche, bien qu'il existe en fait dans une branche.

Vous feriez mieux de tirer tout d'abord:

git pull --all

Effectuez ensuite la recherche de branche:

git name-rev <SHA>

ou:

git branch --contains <SHA>
Yates Zhou
la source
0

Si l'OP essaie de déterminer l' historique qui a été traversé par une branche lors de la création d'un commit particulier ("découvrez de quelle branche provient un commit en fonction de sa valeur de hachage SHA-1"), alors sans le reflog il n'y a pas de enregistrements dans la base de données d'objets Git qui montre quelle branche nommée était liée à quel historique de validation.

(J'ai posté cela comme réponse en réponse à un commentaire.)

J'espère que ce script illustre mon point:

rm -rf /tmp/r1 /tmp/r2; mkdir /tmp/r1; cd /tmp/r1
git init; git config user.name n; git config user.email [email protected]
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;

La sortie montre l'absence de tout moyen de savoir si la validation avec 'Add f1' provenait de la branche b1 ou b2 du clone / tmp / r2 distant.

(Dernières lignes de la sortie ici)

+ 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
qneill
la source
Et quelle est la sortie de git log 80c10e5..HEAD --ancestry-path --merges --oneline --color | tail -n 1et les git log 086c9ce..HEAD --ancestry-path --merges --oneline --color | tail -n 1commandes pour les deux cas?
Eugen Konkov
Les SHA changent bien sûr à chaque fois que les commandes sont exécutées, pour répéter vos commandes, j'ai utilisé HEAD ^ 1 et HEAD ^ 2 lors d'une nouvelle exécution. Les commandes et la sortie sont: $ git log HEAD^1..HEAD --ancestry-path --merges --oneline --color | tail -n 1qui donne 376142d merge brancheset $ git log HEAD^2..HEAD --ancestry-path --merges --oneline --color | tail -n 1qui donne 376142d merge branches - qui montre le résumé de la validation de la fusion, qui (comme je l'ai affirmé) peut être écrasé lors de la création de la fusion, masquant éventuellement l'historique des branches de la fusion.
qneill
0

TL; DR:

Utilisez ce qui suit si vous vous souciez des états de sortie du shell:

  • branch-current - le nom de la branche actuelle
  • branch-names - noms de branche propres (un par ligne)
  • branch-name - Assurez-vous qu'une seule branche est renvoyée de branch-names

Les deux branch-nameet branch-namesacceptent un commettras comme argument, et par défaut à HEADsi aucune est donnée.


Alias ​​utiles dans les scripts

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\" #"

Valider uniquement accessible à partir d'une seule branche

% git branch-name eae13ea
master
% echo $?
0
  • La sortie est vers STDOUT
  • La valeur de sortie est 0.

Commit accessible depuis plusieurs succursales

% git branch-name 4bc6188
Multiple branches:
attempt-extract
master%
% echo $?
1
  • La sortie est vers STDERR
  • La valeur de sortie est 1.

En raison du statut de sortie, ceux-ci peuvent être construits en toute sécurité. Par exemple, pour obtenir la télécommande utilisée pour la récupération:

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

Pour trouver la succursale locale:

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

Pour trouver la branche distante:

grep -lR $commit .git/refs/remotes | sed 's/.git\/refs\/remotes\///g'
Gary Lyn
la source
1
quelques explications de vos exemples seront formidables
Eugen Konkov
-4

Mis à part la recherche dans tout l'arbre jusqu'à ce que vous trouviez un hachage correspondant, non.

ambre
la source
7
Je ne veux pas vraiment déprécier cela, car à proprement parler, il est vrai que git ne stocke pas en permanence ces informations, mais il est très souvent possible de le savoir néanmoins. Voir ma réponse.
Cascabel