Encontrar de qué rama proviene un commit de Git

650

¿Hay alguna manera de averiguar de qué rama proviene un commit dado su valor hash SHA-1 ?

Puntos de bonificación si puedes decirme cómo lograr esto usando Ruby Grit.

Ethan Gunderson
fuente
44
Los diferentes métodos a continuación son formas pragmáticas, útiles, de trabajo para inferir una respuesta probable , pero tengamos en cuenta que en git la pregunta en sí es un malentendido, los commits no provienen de las ramas. Las ramas van y vienen, se mueven, solo los commits representan la verdadera historia del repositorio. Por otra parte, esta no es una manera de decir que las siguientes soluciones son malas. Solo sé que ninguno de ellos da una respuesta totalmente confiable, que no se puede obtener por diseño en git. (Un caso simple es ramas eliminadas: me bifurco, me comprometo dos veces, me uno a otra rama, borro la primera rama. ¿De dónde "viene" el commit?
RomainValeri
1
Tengo un caso en el que extraigo explícitamente un clon superficial de profundidad 1 de una etiqueta. Es barato, fácil y eficiente ... y hasta ahora hacía todo lo que quería maravillosamente. Ahora que me gustaría saber en qué rama estaba la etiqueta, me manché, al menos por ese detalle. No siempre puedes ir a casa, jajaja
Paul Hodges

Respuestas:

865

Si bien Dav tiene razón en que la información no se almacena directamente, eso no significa que nunca pueda averiguarlo. Aquí hay algunas cosas que puedes hacer.

Encuentra sucursales en las que se compromete

git branch -a --contains <commit>

Esto le dirá todas las ramas que tienen el compromiso dado en su historia. Obviamente, esto es menos útil si la confirmación ya se ha fusionado.

Buscar los registros

Si está trabajando en el repositorio en el que se realizó la confirmación, puede buscar en los reflogs la línea de esa confirmación. Los registros de más de 90 días son eliminados por git-gc, por lo que si la confirmación es demasiado antigua, no la encontrará. Dicho esto, puedes hacer esto:

git reflog show --all | grep a871742

para encontrar cometer a871742. Tenga en cuenta que DEBE utilizar los primeros 7 dígitos abreviados de la confirmación. La salida debería ser algo como esto:

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

indicando que la confirmación se realizó en la rama "finalización". El resultado predeterminado muestra hashes de confirmación abreviados, así que asegúrese de no buscar el hash completo o no encontrará nada.

git reflog show en realidad es solo un alias para git log -g --abbrev-commit --pretty=oneline , así que si quieres jugar con el formato de salida para hacer diferentes cosas disponibles para grep, ¡ese es tu punto de partida!

Si no está trabajando en el repositorio donde se realizó la confirmación, lo mejor que puede hacer en este caso es examinar los reflogs y encontrar cuándo la confirmación se introdujo por primera vez en su repositorio; con suerte, buscaste la rama a la que estaba comprometido. Esto es un poco más complejo, porque no puedes recorrer el árbol de confirmaciones y los registros a la vez. Desea analizar el resultado de reflog, examinando cada hash para ver si contiene el commit deseado o no.

Encuentre una confirmación de fusión posterior

Esto depende del flujo de trabajo, pero con buenos flujos de trabajo, se realizan confirmaciones en las ramas de desarrollo que luego se fusionan. Puede hacer esto:

git log --merges <commit>..

para ver los commits de fusión que tienen el commit dado como antepasado. (Si la confirmación solo se fusionó una vez, la primera debería ser la combinación que está buscando; de lo contrario, tendrá que examinar algunas, supongo). El mensaje de confirmación de combinación debe contener el nombre de la rama que se fusionó.

Si desea poder contar con esto, puede usar la --no-ffopción git mergepara forzar la creación de confirmación de fusión incluso en el caso de avance rápido. (Sin embargo, no se ponga demasiado ansioso. Eso podría volverse confuso si se usa en exceso). La respuesta de VonC a una pregunta relacionada elabora útilmente este tema.

Cascabel
fuente
55
+1. La pura ausencia de información para ese tema en particular te hace preguntarte si realmente es un problema. Las sucursales pueden cambiar, cambiar de nombre o eliminarse en cualquier momento. Puede ser git describesuficiente, ya que las etiquetas (anotadas) se pueden ver como más significativas que las ramas.
VonC
99
Definitivamente estoy de acuerdo: las ramas deben ser ligeras y flexibles. Si adopta un flujo de trabajo en el que esa información se vuelve importante, puede usar la --no-ffopción para asegurarse de que siempre haya una confirmación de fusión, de modo que siempre pueda rastrear la ruta de una confirmación dada a medida que se fusiona con el maestro.
Cascabel
32
Para su información, si desea encontrar confirmaciones que solo existen en el control remoto, agregue la -abandera al primer comando, por ejemplogit branch -a --contains <commit>
Jonathan Day,
8
@ JonathanDay: No, eso encontrará confirmaciones en cualquier rama. Si solo quieres unos en el control remoto, úsalo -r.
Cascabel
77
@SimonTewsi Como dije, si realmente es un problema, úselo merge --no-ffpara registrar de manera confiable el nombre de la sucursal cuando se fusione. Pero, de lo contrario, piense en los nombres de las ramas como etiquetas cortas temporales y confirme las descripciones como permanentes. "¿Con qué nombre corto nos referimos a esto durante el desarrollo?" realmente no debería ser una pregunta tan importante como "¿qué hace este compromiso?"
Cascabel
155

Este simple comando funciona como un encanto:

git name-rev <SHA>

Por ejemplo (donde test-branch es el nombre de la rama):

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

Incluso esto funciona para escenarios complejos, como:

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

Aquí git name-rev commit<SHA2>vuelve branchB .

khichar.anil
fuente
3
Salvaste mi día. Esta es la solución perfecta.
Taco
77
Y solo tomó 8 años para esta solución perfecta. ¡Gracias!
S1J0
66
Me pareció git name-rev --name-only <SHA>más útil para obtener solo el nombre de la sucursal. Mi pregunta ... ¿Puede devolver más de una rama en cualquier circunstancia?
Dean Kayton
1
Esta debería ser la mejor respuesta.
JCollier
1
Gracias @JCollier por tus amables palabras.
khichar.anil
47

Actualización de diciembre de 2013:

comentarios sschuberth

git-what-branch(El script de Perl, ver más abajo) ya no parece mantenerse. git-when-mergedes una alternativa escrita en Python que me funciona muy bien.

Se basa en " Buscar confirmación de fusión que incluye una confirmación específica ".

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

Encuentre cuándo una confirmación se fusionó en una o más ramas.
Encuentre la confirmación de fusión que se introdujo COMMITen las RAMAS especificadas.

Específicamente, busque el commit más antiguo en el historial del primer padre BRANCHque contiene el COMMITcomo ancestro.


Respuesta original de septiembre de 2010:

Sebastien Douche simplemente twitteó (16 minutos antes de esta respuesta SO):

git-what-branch : descubre en qué rama se encuentra un commit o cómo llegó a una rama con nombre

Este es un script de Perl de Seth Robertson que parece muy interesante:

SINOPSIS

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

VISIÓN GENERAL

Indíquenos (de manera predeterminada) la ruta causal más temprana de los commits y las fusiones para hacer que el commit solicitado llegue a una rama con nombre. Si se realizó una confirmación directamente en una rama con nombre, obviamente esa es la ruta más temprana.

Por ruta causal más temprana, nos referimos a la ruta que se fusionó en una rama con nombre la primera, por tiempo de confirmación (a menos que --topo-orderse especifique).

ACTUACIÓN

Si muchas ramas (por ejemplo, cientos) contienen la confirmación, el sistema puede tardar mucho tiempo (para una confirmación particular en el árbol de Linux, tardó 8 segundos en explorar una rama, pero había más de 200 ramas candidatas) para rastrear el camino a cada compromiso
La selección de un particular --reference-branch --reference tagpara examinar será cientos de veces más rápida (si tiene cientos de ramas candidatas).

EJEMPLOS

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

Este programa no tiene en cuenta los efectos de elegir el compromiso de interés, solo fusiona las operaciones.

VonC
fuente
44
git-what-branchParece que ya no se mantiene. git-when-merged es una alternativa escrita en Python que funciona muy bien para mí.
sschuberth
@sschuberth gracias por esta actualización. He incluido tu comentario en la respuesta para mayor visibilidad.
VonC
37

Por ejemplo, para encontrar que c0118facommit vino 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

Deberías correr:

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

Y desplácese hacia abajo para encontrar la última confirmación de fusión . Cual es:

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

    Merge branch 'redesign_interactions' into clean_api

Actualizar

O solo un comando:

git log c0118fa..HEAD --ancestry-path --merges --oneline --color | tail -n 1
Eugen Konkov
fuente
44
Esta fue la única solución que me dio el resultado deseado. Probé el resto.
crafter
Tenga en cuenta que esto solo funciona si los resúmenes de confirmación de fusión contienen los nombres de las ramas utilizados en las fusiones, que por defecto suelen contener. Sin embargo git merge -m"Any String Here", oscurecerá la información de la sucursal de origen y destino.
qneill
@qneill: No, la fusión siempre tiene información sobre ramas fusionadas: Merge: f6b70fa d58bdcb. Puede nombrar su compromiso de fusión como lo desee. No tendré materia
Eugen Konkov
1
@EugenKonkov una vez que las ramas han atravesado la fusión, el historial sobre el camino en el gráfico del que provienen se deja en el registro, pero no en la base de datos de objetos git.
Publiqué
UPD versión funciona para mí, simple comando que encuentra el original se comprometen
vpalmu
9

git branch --contains <ref>es el comando de "porcelana" más obvio para hacer esto. Si desea hacer algo similar con solo comandos de "plomería":

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 esta respuesta SO )

Jeff Bowman
fuente
6

khichar.anil cubrió la mayor parte de esto en su respuesta.

Solo estoy agregando la bandera que eliminará las etiquetas de la lista de nombres de revisión. Esto nos da:

git name-rev --name-only --exclude=tags/* $SHA
phyatt
fuente
Parece que falta algo en la segunda oración.
Peter Mortensen
Actualicé la descripción. Gracias por los comentarios.
phyatt
4

La opción de un hombre pobre es utilizar la herramienta tig1 sobre HEAD, buscar la confirmación, y luego seguir visualmente la línea desde que cometen una copia de seguridad hasta que una combinación de confirmación se ve. El mensaje de fusión predeterminado debe especificar en qué rama se fusiona y dónde :)

1 Tig es una interfaz de modo de texto basada en ncurses para Git. Funciona principalmente como un navegador de repositorio Git, pero también puede ayudar a organizar los cambios para confirmar a nivel de fragmento y actuar como un localizador para la salida de varios comandos Git.

usuario1338062
fuente
3

Como experimento, hice un enlace posterior a la confirmación que almacena información sobre la rama actualmente desprotegida en los metadatos de confirmación. También modifiqué ligeramente gitk para mostrar esa información.

Puede verificarlo aquí: https://github.com/pajp/branch-info-commits

pajp
fuente
1
Pero si desea agregar metadatos, ¿por qué no usar notas git, en lugar de jugar con los encabezados de commits? Ver stackoverflow.com/questions/5212957/… , stackoverflow.com/questions/7298749/… o stackoverflow.com/questions/7101158/…
VonC
Para ser honesto, no sabía que existían notas de git cuando escribí eso. Sí, usar notas git para lograr lo mismo es probablemente una mejor idea.
pajp
3

Trato con el mismo problema ( canalización de múltiples ramas de Jenkins ): tener solo información de confirmación e intentar encontrar un nombre de sucursal de donde proviene originalmente esta confirmación. Debe funcionar para sucursales remotas, las copias locales no están disponibles.

Esto es con lo que trabajo:

git rev-parse HEAD | xargs git name-rev

Opcionalmente, puede quitar la salida:

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

Creo que alguien debería enfrentar el mismo problema que no puede encontrar la rama, aunque en realidad existe en una rama.

Será mejor que hagas todo primero:

git pull --all

Luego haga la búsqueda de sucursal:

git name-rev <SHA>

o:

git branch --contains <SHA>
Yates Zhou
fuente
0

Si el OP está tratando de determinar el historial que atravesó una rama cuando se creó una confirmación particular ("averigüe de qué rama proviene una confirmación dado su valor hash SHA-1"), entonces sin el registro no hay ningún registros en la base de datos de objetos Git que muestra qué rama nombrada estaba vinculada a qué historial de confirmación.

(Publiqué esto como respuesta en respuesta a un comentario).

Esperemos que este script ilustre mi punto:

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;

El resultado muestra la falta de alguna forma de saber si el commit con 'Add f1' vino de la rama b1 o b2 del clon remoto / tmp / r2.

(Últimas líneas de la salida aquí)

+ 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
fuente
¿Y cuál es la salida git log 80c10e5..HEAD --ancestry-path --merges --oneline --color | tail -n 1y los git log 086c9ce..HEAD --ancestry-path --merges --oneline --color | tail -n 1comandos para ambos casos?
Eugen Konkov
Los SHA, por supuesto, cambian cada vez que se ejecutan los comandos, para respetar sus comandos, utilicé HEAD ^ 1 y HEAD ^ 2 en una nueva ejecución. Los comandos y la salida son: $ git log HEAD^1..HEAD --ancestry-path --merges --oneline --color | tail -n 1que produce376142d merge branches y $ git log HEAD^2..HEAD --ancestry-path --merges --oneline --color | tail -n 1qué rendimiento 376142d merge branches : que muestra el resumen de confirmación de fusión, que (como estaba afirmando) se puede sobrescribir cuando se crea la fusión, posiblemente ofuscando el historial de ramificación de la fusión.
qneill
0

TL; DR:

Use lo siguiente si le interesan los estados de salida de shell:

  • branch-current - el nombre de la sucursal actual
  • branch-names - nombres de sucursal limpios (uno por línea)
  • branch-name - Asegúrese de que solo se devuelva una rama de branch-names

Ambos branch-namey branch-namesaceptan un commit como argumento, y por defecto HEADsi no se da ninguno.


Alias ​​útiles en scripting

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

Comprometerse solo accesible desde una sola rama

% git branch-name eae13ea
master
% echo $?
0
  • La salida es STDOUT
  • El valor de salida es 0.

Compromiso accesible desde múltiples ramas

% git branch-name 4bc6188
Multiple branches:
attempt-extract
master%
% echo $?
1
  • La salida es a STDERR
  • El valor de salida es 1 .

Debido al estado de salida, estos se pueden construir de forma segura. Por ejemplo, para obtener el control remoto utilizado para buscar:

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

Para encontrar la sucursal local:

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

Para encontrar la rama remota:

grep -lR $commit .git/refs/remotes | sed 's/.git\/refs\/remotes\///g'
Gary Lyn
fuente
1
algunas explicaciones de sus ejemplos serán geniales
Eugen Konkov
-4

Además de buscar en todo el árbol hasta encontrar un hash que coincida, no.

Ámbar
fuente
77
Realmente no quiero desestimar esto, porque estrictamente hablando es cierto que git no almacena esa información permanentemente, pero de todos modos es muy posible averiguarlo. Mira mi respuesta.
Cascabel