¿Cómo puedo ver de qué rama se bifurcó otra rama?


Mi repositorio git tiene tres ramas, devel, stable y customers/acme_patches. Hace mucho tiempo, stable se bifurcó de devel, y todos la solución de bugs tiene lugar en stable. De vez en cuando, stable se fusiona de nuevo en devel. customers/acme_patches es una rama con algunos parches específicos del cliente. La rama no se fusionó con devel y stable.

Un poco de arte ASCII para ilustrar el escenario:

            o---o---o          customers/acme_patches?
           /
  o---o---1---o---o---o        stable
 /     \           \
o---o---o---2---o---o---o---o  devel
             \
              o---o---o        customers/acme_patches?

Ahora me pregunto:

¿De qué rama se bifurcó customers/acme_patches - devel o stable? Sólo sé que fue bifurcado de uno de ellos en el pasado, pero no se cual. Por ejemplo, podría haber sido commit 1 o 2 en el diagrama anterior.

He estado jugando con git log --oneline --graph y gitk pero desde que customers/acme_patches se bifurcó hace unos cientos de confirmaciones, es difícil seguir las líneas que se dibujan.

Hay tal vez un comando rápido (un pequeño script está bien, también) que de alguna manera puede seguir las confirmaciones en customers/acme_patches hacia atrás para encontrar la primera confirmación con dos hijos (el punto de bifurcación) y entonces determina si esa confirmación se hizo en stable o en devel?

En el mejor de los casos, podría simplemente ejecutar algo como (excuse the prompt, I'm on Windows):

C:\src> git fork-origin customers/acme_patches
stable
 24
Author: Frerich Raabe, 2011-01-26

4 answers

Bueno, probablemente no hay una solución perfecta para esta respuesta. Quiero decir que no hay fork-origin equivalente en git (que yo sepa). Debido a que la rama stable se fusiona en devel, su acme_patches (de 1) está en ambas ramas devel y stable.

Lo que podrías hacer es:

git branch --contains $(git merge-base customers/acme_patches devel stable)

Si tienes estable y no devel, o devel y no estable, entonces sabes de dónde viene.

Por ejemplo, en el caso 2, tendrías

$ git branch --contains $(git merge-base customers/acme_patches devel stable)
customers/acme_patches
devel

Mientras que en el caso 1 usted tendría

$ git branch --contains $(git merge-base customers/acme_patches devel stable)
customers/acme_patches
devel
stable

Como ahora está en ambas ramas (debido a la fusión de stable a dev)

 5
Author: Antoine Pelisse,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-01-26 11:27:24

Con git 1.9/2.0 (Q1 2014), puedes usar git merge-base --fork-point para pedir el mejor ancestro común según Git.

Puedes ver esa nueva opción:


Y desde commit ad8261dde John Keeping (johnkeeping), git rebase puede usar la misma nueva opción --fork-point, que puede ser útil si necesita rebase una rama como customers/acme_patches en devel.
(No estoy diciendo que esto tendría sentido en su escenario específico)


Nota: Git 2.16 (Q1 2018) aclara y mejora la documentación para "merge-base --fork-point", ya que estaba claro qué calculaba, pero no por qué/para qué.

See commit 6d1700b (09 Nov 2017) by Junio C Hamano (gitster).
(Merged by Junio C Hamano -- gitster -- in commit 022dd4a , 27 Nov 2017)

merge-base --fork-point doc: aclarar los modos de ejemplo y de falla

La historia ilustrada utilizada para explicar el modo --fork-point nombrado tres commits de keypoint B3, B2 y B1 de la más antigua a la más reciente, que era difícil de leer.
Reetiquetado a B0, B1, B2.
También ilustre la historia después de que se hizo la rebase usando la facilidad --fork-point.

El texto ya menciona el uso de reflog, pero la descripción no es clear qué beneficio estamos tratando de obtener usando reflog.
Aclarar que es encontrar el cometa que estaban en la punta de la rama de seguimiento remoto .
Esto a su vez requiere que los usuarios conozcan las ramificaciones de los supuestos subyacentes, a saber, la expiración de las entradas de reflog hará que sea imposible determinar qué confirmaciones estaban en la punta de las ramas de seguimiento remoto y fallamos en caso de duda (en lugar de dando un resultado aleatorio e incorrecto sin siquiera advertencia).
Otra limitación es que no será útil si no se bifurcó desde la punta de una rama de seguimiento remoto, sino desde el centro.
Descríbelos.

Así que la documentación ahora dice:

Después de trabajar en la rama topic creada con git checkout -b topic origin/master, el historial de la rama de seguimiento remoto origin/master puede haber sido rebobinado y reconstruido, lo que lleva a un historia de esto forma:

                 o---B2
                /
---o---o---B1--o---o---o---B (origin/master)
        \
         B0
          \
           D0---D1---D (topic)

Donde origin/master solía apuntar a las confirmaciones B0, B1, B2 y ahora puntos en B, y su topic rama se inició en la parte superior de la misma de nuevo cuando origin/master estaba en B0, y construiste tres commits, D0, D1, y D, encima.
Imagine que ahora quiere rebase el trabajo que hizo en el topic en la parte superior de la actualización origin/master.

En tal caso, git merge-base origin/master topic devolvería el padre de B0 en la imagen de arriba, pero B0^..D es no el rango de commits que querrías reproducir encima de B (incluye B0, que no es lo que escribiste; es un commit que el otro lado descartó cuando movió su punta de B0 a B1).

git merge-base --fork-point origin/master topic está diseñado para ayudar en tal caso.
No solo toma en cuenta B, sino también B0, B1 y B2 (es decir, consejos antiguos de las ramas de seguimiento remoto que el reflog de su repositorio conoce) para ver en qué commit se construyó su rama temática y encuentra B0, lo que le permite reproducir solo las confirmaciones en tu tema, excluyendo las confirmaciones del otro lado más tarde descartar.

Por lo tanto

$ fork_point=$(git merge-base --fork-point origin/master topic)

Encontrará B0, y

$ git rebase --onto origin/master $fork_point topic

Repetirá D0, D1 y D encima de B para crear una nueva historia de esto forma:

         o---B2
        /
---o---o---B1--o---o---o---B (origin/master)
    \                   \
     B0                  D0'--D1'--D' (topic - updated)
      \
       D0---D1---D (topic - old)

Una advertencia es que las entradas de reflog más antiguas en su repositorio pueden ser expirado por git gc.
Si B0 ya no aparece en el reflog de la rama de seguimiento remoto origin/master, el modo --fork-point obviamente no puede find it and fails, evitando dar un resultado aleatorio e inútil(como el padre de B0, como el mismo comando sin la opción --fork-point da).

Además, la rama de seguimiento remoto utiliza el modo --fork-point con debe ser el que su tema bifurcado de su punta.
Si se bifurcó desde una confirmación más antigua que la punta, este modo no encontraría el punto de bifurcación (imagine en el historial de ejemplo anterior que B0 no existía, origin/master comenzó en B1, se trasladó a B2 y luego B, y se bifurcó su tema en origin/master^ cuando origin/master era B1; la forma de el historial sería el mismo que el anterior, sin B0, y el padre de B1 es lo que git merge-base origin/master topic encuentra correctamente, pero el modo --fork-point no lo hará, porque no es uno de los commits que solían estar en la punta de origin/master).

 21
Author: VonC,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-29 22:09:44

Bien, git merge-base customers/acme_patches stable debería mostrar el ancestro común de esas dos ramas.

Puedes probar, por ejemplo, gitk --left-right customers/acme_patches...stable (¡nota tres puntos!). Esto mostrará todas las confirmaciones que están en esas ramas y no en la base de fusión. Usando --left-right marcará cada commit con una flecha izquierda o derecha según la rama en la que se encuentren - una flecha izquierda si están en customers/acme_patches y una flecha derecha si están en stable.

Posiblemente también añadir --date-order que he encontrado a veces ayuda a tener sentido de salida.

(Puede usar esta sintaxis con git log --graph en lugar de gitk, pero en mi humilde opinión este es un caso en el que la visualización del gráfico visual es una gran mejora).

 3
Author: araqnid,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-01-26 11:02:31

No estoy seguro si cubre todos los casos, pero aquí están las funciones que se me ocurrieron:

git_branch_contains() {
    local b=$1
    local c=$2
    IFS_=$IFS
    IFS=$'\n'
    local branches=($(git branch --contains "$c" | sed -E 's/^(\*| ) //'))
    IFS=$IFS_
    for b2 in "${branches[@]:+${branches[@]}}"; do
        if [ "$b2" = "$b" ]; then
            return 0
        fi
    done
    return 1
}

git_upstream_branch() {
    local b=$1
    local c1=$(git merge-base --fork-point master "$b") || local c1=
    local c2=$(git merge-base --fork-point dev "$b") || local c2=
    if ! [ "$c1" ]; then
        echo dev
        return
    fi
    if ! [ "$c2" ]; then
        echo master
        return
    fi
    local fp
    if git merge-base --is-ancestor "$c1" "$c2"; then
        fp=$c2
    else
        fp=$c1
    fi
    if git_branch_contains master "$fp" && ! git_branch_contains dev "$fp"; then
        echo master
    else
        echo dev
    fi
}

Y aquí está el script para probarlos (git-upstream-branch-test.sh):

#!/usr/bin/env bash
set -eu

. git-upstream-branch.sh

git_commit() {
    if ! [ "${commit_i:-}" ]; then
        commit_i=0
    fi
    (( commit_i++ )) || true
    echo "$commit_i" > "$commit_i"
    git add "$commit_i"
    git commit -qm "c$commit_i"
}

git_merge() {
    if ! [ "${merge_i:-}" ]; then
        merge_i=0
    fi
    (( merge_i++ )) || true
    git merge -m "$merge_i" $1
}

A_TOPOLOGY=${1:-}

mkdir git-upstream-branch-test-repo
cd git-upstream-branch-test-repo
git init -q
if [ "$A_TOPOLOGY" = 10 ]; then
    git_commit
    git_commit
    git checkout -qb dev
    git_commit
    git_commit
    git checkout -q master
    git_commit
    git_commit
    c=$(git rev-parse HEAD)
    git_commit
    git_commit
    git checkout -q dev
    git checkout -qb t1
    git_commit
    git_commit
    git checkout -q dev
    git_commit
    git_commit
    git rebase --onto "$c" dev t1
elif [ "$A_TOPOLOGY" = 11 ]; then
    git_commit
    git_commit
    git checkout -qb dev
    git_commit
    git_commit
    git checkout -q master
    git_commit
    git_commit
    git checkout -q dev
    c=$(git rev-parse HEAD)
    git_commit
    git_commit
    git checkout -q master
    git checkout -qb t1
    git_commit
    git_commit
    git checkout -q master
    git_commit
    git_commit
    git rebase --onto "$c" master t1
else
    git_commit
    git_commit
    git checkout -qb dev
    git_commit
    git_commit
    git checkout -q master
    git_commit
    git_commit
    if [ "$A_TOPOLOGY" = 4 ] || [ "$A_TOPOLOGY" = 5 ] || [ "$A_TOPOLOGY" = 6 ]; then
        git_merge dev
        git_commit
        git_commit
        git checkout -q dev
        git_commit
        git_commit
        git checkout -q master
    elif [ "$A_TOPOLOGY" = 7 ] || [ "$A_TOPOLOGY" = 8 ] || [ "$A_TOPOLOGY" = 9 ]; then
        git checkout -q dev
        git_merge master
        git_commit
        git_commit
        git checkout -q master
        git_commit
        git_commit
    fi
    git checkout -qb t1
    git_commit
    git_commit
    git checkout -q master
    git_commit
    git_commit
    if [ "$A_TOPOLOGY" = 2 ] || [ "$A_TOPOLOGY" = 5 ] || [ "$A_TOPOLOGY" = 8 ]; then
        git_merge dev
    elif [ "$A_TOPOLOGY" = 3 ] || [ "$A_TOPOLOGY" = 6 ] || [ "$A_TOPOLOGY" = 9 ]; then
        git checkout -q dev
        git_merge master
    fi
fi
git --no-pager log --oneline --graph --decorate --all
git_upstream_branch t1

Úsalo así,

$ rm -rf git-upstream-branch-test-repo && ./git-upstream-branch-test.sh NUMBER

Donde el NÚMERO es un número del 1 al 11 para especificar qué caso (topología) probar.

 2
Author: x-yuri,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-25 14:41:44