Encuentra la twig principal de una twig de Git

Digamos que tengo el siguiente repository local con un tree de confirmación como este:

master --> a \ \ develop c --> d \ \ feature f --> g --> h 

master es mi este es el último código de lanzamiento estable , develop es mi este es el 'próximo' código de lanzamiento , y la feature es una nueva característica que se está preparando para el develop .

Lo que deseo poder hacer en mi repository remoto usando ganchos, es que los empujes se feature rechazados a less que commit f sea ​​un descendiente directo de develop HEAD. es decir, el tree de confirmación se ve así porque la function ha sido git rebase en d .

 master --> a \ \ develop c --> d \ \ feature f --> g --> h 

Entonces, ¿es posible:

  • Identificar la twig padre de la feature ?
  • Identifica la confirmación en la twig padre de la cual f es un descendiente?

A partir de ahí, verificaría qué HEAD de la twig padre es y veré si f pnetworkingecesor coincide con HEAD de la twig padre, para determinar si la característica necesita ser actualizada.

Suponiendo que el repository remoto tenga una copy de la twig de desarrollo (su descripción inicial lo describe en un repository local, pero parece que también existe en el control remoto), usted debería poder lograr lo que yo creo que desea, pero el enfoque es un poco diferente de lo que has imaginado.

La historia de Git se basa en un DAG de commits. Las twigs (y los "refs" en general) son simplemente tags transitorias que apuntan a confirmaciones específicas en el DAG de compromiso continuamente creciente. Como tal, la relación entre las twigs puede variar con el time, pero la relación entre las confirmaciones no lo hace.

  ---o---1 foo \ 2---3---o bar \ 4 \ 5---6 baz 

Parece que baz está basado en (una versión antigua de) bar ? ¿Pero qué pasa si eliminamos la bar ?

  ---o---1 foo \ 2---3 \ 4 \ 5---6 baz 

Ahora parece que baz se basa en foo . Pero la ascendencia de baz no cambió, simplemente eliminamos una label (y el compromiso colgante resultante). ¿Y qué pasa si agregamos una nueva label a 4 ?

  ---o---1 foo \ 2---3 \ 4 quux \ 5---6 baz 

Ahora parece que baz se basa en quux . Aún así, la ascendencia no cambió, solo cambiaron las tags.

Sin embargo, si preguntáramos "¿es la confirmación 6 un descendiente de la confirmación 3 ?" (Asumiendo que 3 y 6 son nombres completos de confirmación SHA-1), la respuesta sería "sí", ya sea que las tags bar y quux estén presentes o no.

Por lo tanto, podría hacer preguntas como "¿el commit push es un descendiente del consejo actual de la twig de desarrollo ?", Pero no se puede preguntar "¿cuál es la twig padre de la confirmación push?".

Una pregunta más confiable que parece acercarse a lo que desea es:

Para todos los antepasados ​​de compromiso empujado (excluyendo el consejo actual de desarrollo y sus antepasados), que tienen el consejo actual de desarrollarse como padre:

  • ¿existe al less uno de estos compromisos?
  • ¿Todos estos commits se comprometen con un padre soltero?

Que podría implementarse como:

 pushedrev=... basename=develop if ! baserev="$(git rev-parse --verify refs/heads/"$basename" 2>/dev/null)"; then echo "'$basename' is missing, call for help!" exit 1 fi parents_of_children_of_base="$( git rev-list --pretty=tformat:%P "$pushedrev" --not "$baserev" | grep -F "$baserev" )" case ",$parents_of_children_of_base" in ,) echo "must descend from tip of '$basename'" exit 1 ;; ,*\ *) echo "must not merge tip of '$basename' (rebase instead)" exit 1 ;; ,*) exit 0 ;; esac 

Esto cubrirá algunas de las cosas que quieras restringir, pero quizás no todo.

Como reference, aquí hay una historia de ejemplo extendida:

  A master \ \ o-----J \ / \ \ | o---K---L \ |/ C--------------D develop \ |\ F---G---H | F'--G'--H' | |\ | | o---o---o---N \ \ \ \ \ \ o---o---P \ \ R---S 

El código anterior podría usarse para rechazar H y S al aceptar H' , J , K o N , pero también aceptaría L y P (implican fusiones, pero no fusionan la punta del desarrollo ).

Para rechazar también L y P , puede cambiar la pregunta y preguntar

Para todos los antepasados ​​de compromiso empujado (excluyendo el consejo actual de desarrollo y sus antepasados):

  • ¿Hay algún compromiso con dos padres?
  • si no, ¿al less uno de estos commits tiene el consejo actual de desarrollar su (único) padre?
 pushedrev=... basename=develop if ! baserev="$(git rev-parse --verify refs/heads/"$basename" 2>/dev/null)"; then echo "'$basename' is missing, call for help!" exit 1 fi parents_of_commits_beyond_base="$( git rev-list --pretty=tformat:%P "$pushedrev" --not "$baserev" | grep -v '^commit ' )" case "$parents_of_commits_beyond_base" in *\ *) echo "must not push merge commits (rebase instead)" exit 1 ;; *"$baserev"*) exit 0 ;; *) echo "must descend from tip of '$basename'" exit 1 ;; esac 

Un rephrasal

Otra forma de formular la pregunta es "¿Cuál es la confirmación más cercana que reside en una twig distinta de la twig actual, y qué twig es esa?"

Una solución

Puedes encontrarlo con un poco de magia de línea de command

 git show-branch -a \ | grep '\*' \ | grep -v `git rev-parse --abbrev-ref HEAD` \ | head -n1 \ | sed 's/.*\[\(.*\)\].*/\1/' \ | sed 's/[\^~].*//' 

Así es como funciona:

  1. Muestra un historial textual de todas las confirmaciones, incluidas las sucursales remotas.
  2. Los ancestros de la confirmación actual están indicados por una estrella. Filtra todo lo demás.
  3. Ignora todas las confirmaciones en la twig actual.
  4. El primer resultado será la twig ancestral más cercana. Ignora los otros resultados.
  5. Los nombres de las sucursales se muestran [entre paréntesis]. Ignora todo lo que esté fuera de los corchetes y los corchetes.
  6. Algunas veces, el nombre de la twig includeá un ~ # o ^ # para indicar cuántas confirmaciones se encuentran entre la confirmación referenceda y la sugerencia de twig. No nos importa Ingnóralos.

Y el resultado

Ejecutando el código de arriba en

  A---B---D <-master \ \ C---E---I <-develop \ \ F---G---H <-topic 

Te dará develop si lo ejecutas desde H y master si lo ejecutas desde I.

El código está disponible como una esencia

Tengo una solución para su problema general (determine si la feature desciende desde la punta del develop ), pero no funciona usando el método que describió.

Puede usar git branch --contains para enumerar todas las twigs descendientes de la punta de develop , luego use grep para asegurarse de que la feature esté entre ellas.

 git branch --contains develop | grep "^ *feature$" 

Si está entre ellos, imprimirá " feature" en la salida estándar y tendrá un código de retorno de 0. De lo contrario, no imprimirá nada y tendrá un código de retorno de 1.

También puedes probar:

 git log --graph --decorate 

Esto funciona bien para mí.

 git show-branch | grep '*' | grep -v "$(git rev-parse --abbrev-ref HEAD)" | head -n1 | sed 's/.*\[\(.*\)\].*/\1/' | sed 's/[\^~].*//' 

Respuestas de cortesía de: @droidbot y @Jistanidiot

Restring que, como se describe en "Git: Encontrar de qué twig proviene una confirmación" , no puedes identificar fácilmente la twig donde se ha realizado esa confirmación (se pueden renombrar, mover, eliminar …), aunque git branch --contains <commit> es un comienzo.

  • Puede volver de la confirmación a la confirmación hasta que git branch --contains <commit> no enumere la twig de la feature y la list develop twig,
  • comparar ese compromiso SHA1 a /refs/heads/develop

Si los dos commits coinciden, es bueno que vayas (eso significaría que la twig de feature tiene su origen en el HEAD of develop ).

Dado que ninguna de las respuestas anteriores funcionó en nuestro repository, quiero compartir mi propio path, utilizando las últimas fusiones en el git log :

 #!/bin/bash git log --oneline --merges "$@" | grep into | sed 's/.* into //g' | uniq --count | head -n 10 

Ponlo en un script llamado git-last-merges , que también acepta un nombre de twig como argumento (en lugar de la twig actual), así como otros arguments de git log

Desde la salida, podemos detectar manualmente la (s) twig (s) padre (s) basándose en las convenciones de ramificación propias y el número de fusiones de cada twig.

EDITAR: Si usas git rebase en las twigs secundarias a menudo (y las fusiones se reenvían rápidamente a menudo para que no haya demasiadas git rebase de fusión), esta respuesta no funcionará bien, así que escribí un guión para contar las confirmaciones (normal y merge) y detrás de commits (no debe haber ninguna fusión posterior en la twig principal) en todas las twigs en comparación con la twig actual. Simplemente ejecute este script y avíseme si funciona para usted o no

 #!/bin/bash HEAD="`git rev-parse --abbrev-ref HEAD`" echo "Comparing to $HEAD" printf "%12s %12s %10s %s\n" "Behind" "BehindMerge" "Ahead" "Branch" git branch | grep -v '^*' | sed 's/^\* //g' | while read branch ; do ahead_merge_count=`git log --oneline --merges $branch ^$HEAD | wc -l` if [[ $ahead_merge_count != 0 ]] ; then continue fi ahead_count=`git log --oneline --no-merges $branch ^$HEAD | wc -l` behind_count=`git log --oneline --no-merges ^$branch $HEAD | wc -l` behind_merge_count=`git log --oneline --merges ^$branch $HEAD | wc -l` behind="-$behind_count" behind_merge="-M$behind_merge_count" ahead="+$ahead_count" printf "%12s %12s %10s %s\n" "$behind" "$behind_merge" "$ahead" "$branch" done | sort -n 

La magia de la command-line de JoeChrysler se puede simplificar. Aquí está la lógica tal como está escrita:

 git show-branch -a | ack '\*' | # we want only lines that contain an asterisk ack -v "$current_branch" | # but also don't contain the current branch head -n1 | # and only the first such line sed 's/.*\[\(.*\)\].*/\1/' | # really, just the part of the line between [] sed 's/[\^~].*//' # and with any relative refs (^, ~n) removed 

Podemos lograr lo mismo que los cinco filters de command individuales en un command awk relativamente simple:

 git show-branch -a | awk -F'[]^~[]' '/\*/ && !/'"$current_branch"'/ {print $2;exit}' 

Eso se descompone así:

 -F'[]^~[]' 

divide la línea en campos en ] , ^ , ~ y [ caracteres.

 /\*/ 

Encuentre líneas que contengan un asterisco

 && !/'"$current_branch"'/ 

… pero no el nombre de la sucursal actual

 { print $2; 

Cuando encuentre una línea de este tipo, imprima su segundo campo (es decir, la parte entre la primera y la segunda aparición de nuestros caracteres separadores de campo). Para nombres de twigs simples, eso será exactamente lo que hay entre los corchetes; para refs con saltos relativos, será solo el nombre sin el modificador. Por lo tanto, nuestro set de separadores de campo maneja la intención de ambos commands sed .

  exit } 

Luego salga inmediatamente. Esto significa que solo procesa la primera línea coincidente, por lo que no es necesario canalizar la salida a través del head -n 1 .

@Mark Reed: debe agregar que la línea de confirmación no solo debe contener un asterisco, ¡sino que debe comenzar con un asterisco! De lo contrario, los posts de confirmación que contienen un asterisco también se incluyen en las líneas coincidentes. Entonces debería ser:

git show-branch -a | awk -F'[]^~[]' '/^\*/ && !/'"$current_branch"'/ {print $2;exit}'

o la versión larga:

 git show-branch -a | awk '^\*' | # we want only lines that contain an asterisk awk -v "$current_branch" | # but also don't contain the current branch head -n1 | # and only the first such line sed 's/.*\[\(.*\)\].*/\1/' | # really, just the part of the line between [] sed 's/[\^~].*//' # and with any relative refs (^, ~n) removed` 

Implementación multiplataforma con Ant

  <exec executable="git" outputproperty="currentBranch"> <arg value="rev-parse" /> <arg value="--abbrev-ref" /> <arg value="HEAD" /> </exec> <exec executable="git" outputproperty="showBranchOutput"> <arg value="show-branch" /> <arg value="-a" /> </exec> <loadresource property="baseBranch"> <propertyresource name="showBranchOutput"/> <filterchain> <linecontains> <contains value="*"/> </linecontains> <linecontains negate="true"> <contains value="${currentBranch}"/> </linecontains> <headfilter lines="1"/> <tokenfilter> <replaceregex pattern=".*\[(.*)\].*" replace="\1"/> <replaceregex pattern="[\^~].*" replace=""/> </tokenfilter> </filterchain> </loadresource> <echo message="${currentBranch} ${baseBranch}" /> 
 vbc=$(git rev-parse --abbrev-ref HEAD) vbc_col=$(( $(git show-branch | grep '^[^\[]*\*' | head -1 | cut -d* -f1 | wc -c) - 1 )) swimming_lane_start_row=$(( $(git show-branch | grep -n "^[\-]*$" | cut -d: -f1) + 1 )) git show-branch | tail -n +$swimming_lane_start_row | grep -v "^[^\[]*\[$vbc" | grep "^.\{$vbc_col\}[^ ]" | head -n1 | sed 's/.*\[\(.*\)\].*/\1/' | sed 's/[\^~].*//' 

Logra los mismos fines que la respuesta de Mark Reed, pero utiliza un enfoque mucho más seguro que no se comporta mal en varios escenarios:

  1. El último compromiso de la twig principal es una combinación, lo que hace que la columna muestre - no *
  2. El post de confirmación contiene el nombre de la twig
  3. El post de confirmación contiene *

Aquí hay una implementación de PowerShell de la solución de Mark Reed:

 git show-branch -a | where-object { $_.Contains('*') -eq $true} | Where-object {$_.Contains($branchName) -ne $true } | select -first 1 | % {$_ -replace('.*\[(.*)\].*','$1')} | % { $_ -replace('[\^~].*','') } 

Cualquiera que quiera hacer esto en estos días: la aplicación SourceTree de Atlassian le muestra una gran representación visual de cómo se relacionan sus twigs entre sí, es decir, dónde comenzaron y dónde se encuentran actualmente en la order de compromiso (por ejemplo, HEAD o 4 commits behind, etc.) .

Si usa el tree de fonts, mire los detalles de su confirmación> Padres> verá numbers de confirmación subrayados (enlaces)