Encontrar un punto de ramificación con Git?


Tengo un repositorio con branches master y un montón de actividad de fusión entre los dos. ¿Cómo puedo encontrar la confirmación en mi repositorio cuando se creó la rama A basada en master?

Mi repositorio básicamente se ve así:

-- X -- A -- B -- C -- D -- F  (master) 
          \     /   \     /
           \   /     \   /
             G -- H -- I -- J  (branch A)

Estoy buscando la revisión A, que no es lo que git merge-base (--all) encuentra.

 366
Author: Matt Ball, 2009-10-06

21 answers

Estaba buscando lo mismo, y encontré esta pregunta. Gracias por preguntar!

Sin embargo, encontré que las respuestas que veo aquí no parecen bastante dar la respuesta que pediste (o que estaba buscando) they parecen dar el G commit, en lugar del A commit.

Entonces, he creado el siguiente árbol (letras asignadas en orden cronológico), para poder probar las cosas:

A - B - D - F - G   <- "master" branch (at G)
     \   \     /
      C - E --'     <- "topic" branch (still at E)

Esto se ve un poco diferente al tuyo, porque quería asegurarme de que obtuve (refiriéndose a este gráfico, no el tuyo) B, pero no A (y no D o E). Aquí están las letras adjuntas a los prefijos SHA y los mensajes de confirmación (mi repositorio se puede clonar desde aquí, si eso es interesante para alguien):

G: a9546a2 merge from topic back to master
F: e7c863d commit on master after master was merged to topic
E: 648ca35 merging master onto topic
D: 37ad159 post-branch commit on master
C: 132ee2a first commit on topic branch
B: 6aafd7f second commit on master before branching
A: 4112403 initial commit on master

Entonces, el objetivo: encontrar B. Aquí hay tres maneras que encontré, después de un poco de retoques:


1. visualmente, con gitk:

Debe ver visualmente un árbol como este (visto desde maestro):

gitk captura de pantalla de master

O aquí (visto desde el tema):

gitk captura de pantalla desde el tema

En ambos casos, he seleccionado la confirmación que es B en mi gráfico. Una vez que haga clic en él, su SHA completo se presenta en un campo de entrada de texto justo debajo del gráfico.


2. visualmente, pero desde el terminal:

git log --graph --oneline --all

(Edit / side-note: agregar --decorate también puede ser interesante; agrega una indicación de nombres de ramas, etiquetas, etc. No añadir esto a la línea de comandos anterior ya que la salida de abajo no refleja su uso.)

Que muestra (asumiendo git config --global color.ui auto):

salida de git log --graph --oneline --todos

O, en texto recto:

*   a9546a2 merge from topic back to master
|\  
| *   648ca35 merging master onto topic
| |\  
| * | 132ee2a first commit on topic branch
* | | e7c863d commit on master after master was merged to topic
| |/  
|/|   
* | 37ad159 post-branch commit on master
|/  
* 6aafd7f second commit on master before branching
* 4112403 initial commit on master

En cualquier caso, vemos el commit 6aafd7f como el punto común más bajo, es decir, B en mi gráfico, o A en el tuyo.


3. Con magia de concha:

No especifica en su pregunta si quería algo como lo anterior, o un solo comando que solo le dará el revisión, y nada más. Bueno, aquí está lo último:

diff -u <(git rev-list --first-parent topic) \
             <(git rev-list --first-parent master) | \
     sed -ne 's/^ //p' | head -1
6aafd7ff98017c816033df18395c5c1e7829960d

Que también puedes poner en tu~/.gitconfig as (nota: trailing dash es importante; gracias Brian por llamar la atención sobre eso):

[alias]
    oldest-ancestor = !zsh -c 'diff -u <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | sed -ne \"s/^ //p\" | head -1' -

Lo que podría hacerse a través de la siguiente línea de comandos:

git config --global alias.oldest-ancestor '!zsh -c '\''diff -u <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | sed -ne "s/^ //p" | head -1'\'' -'

Nota: zsh fácilmente podría haber sido bash, pero sh no funcionará the la sintaxis <() no existe en vanilla sh. (Gracias usted de nuevo, @conny, por hacerme consciente de ello en un comentario sobre otra respuesta en esta página!)

Nota: Versión alternativa de lo anterior:

Gracias a liori por señalar que lo anterior podría caerse al comparar ramas idénticas, y llegar a una forma diff alternativa que elimina la forma sed de la mezcla, y hace que esto sea "más seguro" (es decir, devuelve un resultado (es decir, la confirmación más reciente) incluso cuando se compara master con maestro):

Como a .línea git-config:

[alias]
    oldest-ancestor = !zsh -c 'diff --old-line-format='' --new-line-format='' <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | head -1' -

De la cáscara:

git config --global alias.oldest-ancestor '!zsh -c '\''diff --old-line-format='' --new-line-format='' <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | head -1'\'' -'

Así que, en mi árbol de pruebas (que no estaba disponible por un tiempo, lo siento; ha vuelto), eso ahora funciona tanto en master como en topic (dando confirmaciones G y B, respectivamente). Gracias de nuevo, liori, por la forma alternativa.


Entonces, eso es lo que se me ocurrió a mí [y a liori]. Parece que funciona para mí. También permite un par de alias adicionales que podrían resultar útiles:

git config --global alias.branchdiff '!sh -c "git diff `git oldest-ancestor`.."'
git config --global alias.branchlog '!sh -c "git log `git oldest-ancestor`.."'

Feliz ¡git-ing!

 415
Author: lindes,
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
2018-02-03 03:12:53

Usted puede estar buscando git merge-base:

git merge-base encuentra el(los) mejor (s) ancestro (s) común (es) entre dos confirmaciones para usar en una fusión a tres bandas. Un ancestro común es mejor que otro ancestro común si este último es un ancestro del primero. Un ancestro común que no tiene ningún ancestro común mejor es un mejor ancestro común, es decir, un base de fusión. Tenga en cuenta que puede haber más de una base de fusión para un par de confirmaciones.

 114
Author: Greg Hewgill,
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
2013-09-26 12:22:52

He usado git rev-list para este tipo de cosas. Por ejemplo, (tenga en cuenta la 3 puntos)

$ git rev-list --boundary branch-a...master | grep "^-" | cut -c2-

Escupirá el punto de ramificación. Ahora, no es perfecto; ya que has fusionado master en la rama A un par de veces, eso dividirá un par posibles puntos de rama (básicamente, el punto de rama original y luego cada punto en el que fusionaste master en la rama A). Sin embargo, al menos debería reducir las posibilidades.

He añadido ese comando a mis alias en ~/.gitconfig as:

[alias]
    diverges = !sh -c 'git rev-list --boundary $1...$2 | grep "^-" | cut -c2-'

Así que puedo llamarlo como:

$ git diverges branch-a master
 31
Author: mipadi,
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-08-25 03:00:38

Si te gustan los comandos concisos,

git rev-list $(git rev-list --first-parent ^branch_name master | tail -n1)^^! 

Aquí hay una explicación.

El siguiente comando le da la lista de todas las confirmaciones en master que ocurrieron después de que branch_name fuera creado

git rev-list --first-parent ^branch_name master 

Dado que solo te importa la primera de esas confirmaciones, quieres la última línea de la salida:

git rev-list ^branch_name --first-parent master | tail -n1

El padre de la primera confirmación que no es un ancestro de "branch_name" es, por definición, en "branch_name", y está en "master" ya que es un ancestro de algo en "maestro."Así que tienes el primer commit que está en ambas ramas.

El comando

git rev-list commit^^!

Es solo una forma de mostrar la referencia de confirmación padre. Usted podría utilizar

git log -1 commit^

O lo que sea.

PD: No estoy de acuerdo con el argumento de que el orden de los antepasados es irrelevante. Depende de lo que quieras. Por ejemplo, en este caso

_C1___C2_______ master
  \    \_XXXXX_ branch A (the Xs denote arbitrary cross-overs between master and A)
   \_____/ branch B

Tiene mucho sentido generar C2 como commit de "ramificación". Esto es cuando el desarrollador se ramificó de " master."Cuando él ramificado, rama " B " ni siquiera se fusionó en su rama! Esto es lo que da la solución en este post.

Si lo que desea es la última confirmación C tal que todas las rutas desde el origen hasta la última confirmación en la rama "A" pasen por C, entonces desea ignorar el orden de ancestry. Eso es puramente topológico y te da una idea de desde cuando tienes dos versiones del código yendo al mismo tiempo. Eso es cuando irías con enfoques basados en merge-base, y devolverá C1 en mi ejemplo.

 25
Author: Lionel,
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
2013-07-31 13:49:59

Dado que muchas de las respuestas en este hilo no dan la respuesta que la pregunta estaba pidiendo, aquí hay un resumen de los resultados de cada solución, junto con el script que utilicé para replicar el repositorio dado en la pregunta.

El registro

Creando un repositorio con la estructura dada, obtenemos el registro de git de:

$ git --no-pager log --graph --oneline --all --decorate
* b80b645 (HEAD, branch_A) J - Work in branch_A branch
| *   3bd4054 (master) F - Merge branch_A into branch master
| |\  
| |/  
|/|   
* |   a06711b I - Merge master into branch_A
|\ \  
* | | bcad6a3 H - Work in branch_A
| | * b46632a D - Work in branch master
| |/  
| *   413851d C - Merge branch_A into branch master
| |\  
| |/  
|/|   
* | 6e343aa G - Work in branch_A
| * 89655bb B - Work in branch master
|/  
* 74c6405 (tag: branch_A_tag) A - Work in branch master
* 7a1c939 X - Work in branch master

Mi única adición, es la etiqueta que lo hace explícito sobre el punto en el que creamos la rama y por lo tanto el commit que deseamos encontrar.

La solución que funciona

La única solución que funciona es la proporcionada por lindes devuelve correctamente A:

$ diff -u <(git rev-list --first-parent branch_A) \
          <(git rev-list --first-parent master) | \
      sed -ne 's/^ //p' | head -1
74c6405d17e319bd0c07c690ed876d65d89618d5

Como Charles Bailey señala, sin embargo, esta solución es muy frágil.

Si branch_A entras en master y luego combinas master en branch_A sin intervenir commits, entonces la solución de lindes solo te da la primera divergencia más reciente.

Eso significa que para mi flujo de trabajo, creo que voy para tener que seguir con el etiquetado del punto de ramificación de ramas de larga duración, ya que no puedo garantizar que se puedan encontrar de manera confiable más tarde.

Esto realmente se reduce a la falta de git de lo que hgllama ramas con nombre. El blogger jhw llama a estoslinajes vs. familias en su artículo Por Qué Me Gusta Mercurial Más Que Git y su artículo de seguimiento Más Sobre Mercurial vs. Git (con Gráficos!). Recomendaría a la gente leerlos a vea por qué algunos conversos mercuriales no tienen ramas nombradas en git.

Las soluciones que no funcionan

La solución proporcionada por mipadi devuelve dos respuestas, I y C:

$ git rev-list --boundary branch_A...master | grep ^- | cut -c2-
a06711b55cf7275e8c3c843748daaa0aa75aef54
413851dfecab2718a3692a4bba13b50b81e36afc

La solución proporcionada por Greg Hewgill return I

$ git merge-base master branch_A
a06711b55cf7275e8c3c843748daaa0aa75aef54
$ git merge-base --all master branch_A
a06711b55cf7275e8c3c843748daaa0aa75aef54

La solución proporcionada por Karl devuelve X:

$ diff -u <(git log --pretty=oneline branch_A) \
          <(git log --pretty=oneline master) | \
       tail -1 | cut -c 2-42
7a1c939ec325515acfccb79040b2e4e1c3e7bbe5

El script

mkdir $1
cd $1
git init
git commit --allow-empty -m "X - Work in branch master"
git commit --allow-empty -m "A - Work in branch master"
git branch branch_A
git tag branch_A_tag     -m "Tag branch point of branch_A"
git commit --allow-empty -m "B - Work in branch master"
git checkout branch_A
git commit --allow-empty -m "G - Work in branch_A"
git checkout master
git merge branch_A       -m "C - Merge branch_A into branch master"
git checkout branch_A
git commit --allow-empty -m "H - Work in branch_A"
git merge master         -m "I - Merge master into branch_A"
git checkout master
git commit --allow-empty -m "D - Work in branch master"
git merge branch_A       -m "F - Merge branch_A into branch master"
git checkout branch_A
git commit --allow-empty -m "J - Work in branch_A branch"

Dudo que la versión de git haga mucha diferencia en esto, pero:

$ git --version
git version 1.7.1

Gracias a Charles Bailey por mostrarme una forma más compacta de escribir el repositorio de ejemplo.

 18
Author: Mark Booth,
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-05-23 12:10:54

En general, esto no es posible. En un historial de ramas, una rama y fusión antes de una rama con nombre se ramificó y una rama intermedia de dos ramas con nombre se ve igual.

En git, las ramas son solo los nombres actuales de los consejos de las secciones de la historia. Realmente no tienen una identidad fuerte.

Esto no suele ser un gran problema ya que la base de fusión (ver la respuesta de Greg Hewgill) de dos commits suele ser mucho más útil, dando la confirmación más reciente que los dos ramas compartidas.

Una solución basada en el orden de los padres de un commit obviamente no funcionará en situaciones en las que una rama se haya integrado completamente en algún momento de la historia de la rama.

git commit --allow-empty -m root # actual branch commit
git checkout -b branch_A
git commit --allow-empty -m  "branch_A commit"
git checkout master
git commit --allow-empty -m "More work on master"
git merge -m "Merge branch_A into master" branch_A # identified as branch point
git checkout branch_A
git merge --ff-only master
git commit --allow-empty -m "More work on branch_A"
git checkout master
git commit --allow-empty -m "More work on master"

Esta técnica también cae si se ha realizado una fusión de integración con los padres invertidos (por ejemplo, se usó una rama temporal para realizar una fusión de prueba en master y luego se reenvió rápidamente a la rama de características para construir más).

git commit --allow-empty -m root # actual branch point
git checkout -b branch_A
git commit --allow-empty -m  "branch_A commit"
git checkout master
git commit --allow-empty -m "More work on master"
git merge -m "Merge branch_A into master" branch_A # identified as branch point
git checkout branch_A
git commit --allow-empty -m "More work on branch_A"

git checkout -b tmp-branch master
git merge -m "Merge branch_A into tmp-branch (master copy)" branch_A
git checkout branch_A
git merge --ff-only tmp-branch
git branch -d tmp-branch

git checkout master
git commit --allow-empty -m "More work on master"
 10
Author: CB Bailey,
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
2012-04-03 21:58:00

¿Qué tal algo como

git log --pretty=oneline master > 1
git log --pretty=oneline branch_A > 2

git rev-parse `diff 1 2 | tail -1 | cut -c 3-42`^
 5
Author: Karl,
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
2009-11-05 10:30:49

Recientemente necesitaba resolver este problema también y terminé escribiendo un script Ruby para esto: https://github.com/vaneyckt/git-find-branching-point

 4
Author: Reck,
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
2012-07-29 14:57:25

Seguramente me falta algo, pero IMO, todos los problemas anteriores son causados porque siempre estamos tratando de encontrar el punto de ramificación que se remonta a la historia, y eso causa todo tipo de problemas debido a las combinaciones de fusión disponibles.

En su lugar, he seguido un enfoque diferente, basado en el hecho de que ambas ramas comparten mucha historia, exactamente toda la historia antes de ramificar es 100% igual, así que en lugar de retroceder, mi propuesta es sobre seguir adelante (desde 1st commit), buscando la 1ra diferencia en ambas ramas. El punto de ramificación será, simplemente, el padre de la primera diferencia encontrada.

En la práctica:

#!/bin/bash
diff <( git rev-list "${1:-master}" --reverse --topo-order ) \
     <( git rev-list "${2:-HEAD}" --reverse --topo-order) \
--unified=1 | sed -ne 's/^ //p' | head -1

Y está resolviendo todos mis casos habituales. Seguro que hay fronteras no cubiertas pero... ciao: -)

 3
Author: stronk7,
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
2012-06-01 11:47:13

Después de muchas investigaciones y discusiones, está claro que no hay una solución mágica que funcione en todas las situaciones, al menos no en la versión actual de Git.

Es por eso que escribí un par de parches que añaden el concepto de una rama tail. Cada vez que se crea una rama, también se crea un puntero al punto original, el tail ref. Esta referencia se actualiza cada vez que se rebasa la rama.

Para averiguar el punto de rama de la rama devel, todo lo que tienes que hacer es usar devel@{tail}, eso es.

Https://github.com/felipec/git/commits/fc/tail

 3
Author: FelipeC,
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
2013-10-01 17:12:21

El siguiente comando revelará el SHA1 de Commit A

git merge-base --fork-point A

 3
Author: Gayan Pathirage,
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-05-03 12:01:02

Aquí hay una versión mejorada de mi respuesta anterior respuesta anterior. Se basa en los mensajes de confirmación de las fusiones para encontrar dónde se creó la rama por primera vez.

Funciona en todos los repositorios mencionados aquí, e incluso he abordado algunos complicados que generaron en la lista de correo. También escribí pruebas para esto.

find_merge ()
{
    local selection extra
    test "$2" && extra=" into $2"
    git rev-list --min-parents=2 --grep="Merge branch '$1'$extra" --topo-order ${3:---all} | tail -1
}

branch_point ()
{
    local first_merge second_merge merge
    first_merge=$(find_merge $1 "" "$1 $2")
    second_merge=$(find_merge $2 $1 $first_merge)
    merge=${second_merge:-$first_merge}

    if [ "$merge" ]; then
        git merge-base $merge^1 $merge^2
    else
        git merge-base $1 $2
    fi
}
 2
Author: FelipeC,
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-05-23 11:33:26

Parece que estoy recibiendo algo de alegría con

git rev-list branch...master

La última línea que obtienes es la primera confirmación en la rama, así que entonces es cuestión de obtener el padre de eso. So

git rev-list -1 `git rev-list branch...master | tail -1`^

Parece funcionar para mí y no necesita diferencias y así sucesivamente (que es útil ya que no tenemos esa versión de diff)

Corrección: Esto no funciona si estás en la rama master, pero estoy haciendo esto en un script por lo que es menos problemático

 2
Author: Tom Tanner,
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
2013-03-21 09:45:50

Para encontrar commits desde el punto de ramificación, puedes usar esto.

git log --ancestry-path master..topicbranch
 2
Author: Andor,
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
2014-07-11 16:32:01

A veces es efectivamente imposible (con algunas excepciones de donde podría tener la suerte de tener datos adicionales) y las soluciones aquí no funcionarán.

Git no conserva el historial ref (que incluye ramas). Solo almacena la posición actual de cada rama (la cabeza). Esto significa que puedes perder parte del historial de ramas en git con el tiempo. Cada vez que se bifurca, por ejemplo, se pierde inmediatamente qué rama era la original. Todo lo que hace una rama es:

git checkout branch1    # refs/branch1 -> commit1
git checkout -b branch2 # branch2 -> commit1

Usted podría supongamos que el primer commited a es la rama. Este suele ser el caso, pero no siempre es así. No hay nada que te impida comprometerte a cualquiera de las ramas primero después de la operación anterior. Además, no se garantiza que las marcas de tiempo de git sean confiables. No es hasta que te comprometes con ambos que realmente se convierten en ramas estructuralmente.

Mientras que en los diagramas tendemos a numerar confirmaciones conceptualmente, git no tiene un concepto estable real de secuencia cuando el árbol de confirmaciones se ramifica. En este caso puede asumir que los números (que indican el orden) están determinados por la marca de tiempo (puede ser divertido ver cómo una interfaz de usuario de git maneja las cosas cuando estableces todas las marcas de tiempo en la misma).

Esto es lo que un humano espera conceptualmente: {[14]]}

After branch:
       C1 (B1)
      /
    -
      \
       C1 (B2)
After first commit:
       C1 (B1)
      /
    - 
      \
       C1 - C2 (B2)

Esto es lo que realmente obtienes:{[14]]}

After branch:
    - C1 (B1) (B2)
After first commit (human):
    - C1 (B1)
        \
         C2 (B2)
After first commit (real):
    - C1 (B1) - C2 (B2)

Usted asumiría que B1 es la rama original, pero de hecho podría ser simplemente una rama muerta (alguien hizo checkout-b pero nunca se comprometió con ella). No es hasta que usted se compromete a ambos que usted consigue un legítimo estructura de rama dentro de git:

Either:
      / - C2 (B1)
    -- C1
      \ - C3 (B2)
Or:
      / - C3 (B1)
    -- C1
      \ - C2 (B2)

Siempre se sabe que C1 vino antes de C2 y C3, pero nunca se sabe de forma fiable si C2 vino antes de C3 o C3 vino antes de C2 (porque se puede establecer la hora en su estación de trabajo a cualquier cosa, por ejemplo). B1 y B2 también es engañoso, ya que no puede saber qué rama llegó primero. Usted puede hacer una muy buena y por lo general precisa conjetura en muchos casos. Es un poco como una pista de carreras. Todas las cosas en general son iguales con los coches, entonces usted puede asumir que un coche que viene en una vuelta atrás comenzó una vuelta atrás. También tenemos convenciones que son muy confiables, por ejemplo, master casi siempre representará las ramas más longevas, aunque lamentablemente he visto casos en los que incluso este no es el caso.

El ejemplo dado aquí es un ejemplo de preservación de la historia:

Human:
    - X - A - B - C - D - F (B1)
           \     / \     /
            G - H ----- I - J (B2)
Real:
            B ----- C - D - F (B1)
           /       / \     /
    - X - A       /   \   /
           \     /     \ /
            G - H ----- I - J (B2)

Lo real aquí también es engañoso porque nosotros como humanos lo leemos de izquierda a derecha, de raíz a hoja (ref.). Git no hace eso. Donde hacemos (A->B) en nuestras cabezas git hace (AA). Lo lee de ref a root. Las referencias pueden estar en cualquier lugar, pero tienden a ser hojas, al menos para las ramas activas. Un ref apunta a un commit y los commits solo contienen un like para sus padres, no para sus hijos. Cuando un commit es un commit de fusión tendrá más de un padre. El primer padre es siempre el commit original en el que se fusionó. Los otros padres siempre son confirmaciones que se fusionaron en la confirmación original.

Paths:
    F->(D->(C->(B->(A->X)),(H->(G->(A->X))))),(I->(H->(G->(A->X))),(C->(B->(A->X)),(H->(G->(A->X)))))
    J->(I->(H->(G->(A->X))),(C->(B->(A->X)),(H->(G->(A->X)))))

Esta no es una representación muy eficiente, más bien una expresión de todas las rutas que git puede tomar de cada ref (B1 y B2).

El almacenamiento interno de Git se parece más a esto (no es que A como padre aparezca dos veces):

    F->D,I | D->C | C->B,H | B->A | A->X | J->I | I->H,C | H->G | G->A

Si vuelcas un commit de git sin procesar verás cero o más campos principales. Si hay cero, significa que no hay padre y la confirmación es una raíz (en realidad puede tener varias raíces). Si hay uno, significa que no hubo fusión y no es un commit de root. Si hay más de uno significa que el commit es el resultado de una fusión y todos los padres después de la primera son confirmaciones de fusión.

Paths simplified:
    F->(D->C),I | J->I | I->H,C | C->(B->A),H | H->(G->A) | A->X
Paths first parents only:
    F->(D->(C->(B->(A->X)))) | F->D->C->B->A->X
    J->(I->(H->(G->(A->X))) | J->I->H->G->A->X
Or:
    F->D->C | J->I | I->H | C->B->A | H->G->A | A->X
Paths first parents only simplified:
    F->D->C->B->A | J->I->->G->A | A->X
Topological:
    - X - A - B - C - D - F (B1)
           \
            G - H - I - J (B2)

Cuando ambos golpean A su cadena será la misma, antes de que su cadena será completamente diferente. El primer commit que otros dos commits tienen en común es el ancestro común y de donde divergieron. podría haber cierta confusión aquí entre los términos commit, branch y ref. De hecho, puede combinar una confirmación. Esto es lo que realmente hace merge. Un ref simplemente apunta a un commit y una rama no es más que un ref en la carpeta .git / refs / heads, la ubicación de la carpeta es lo que determina que una ref es una rama en lugar de otra cosa como una etiqueta.

Donde se pierde la historia es que la fusión hará una de dos cosas dependiendo de las circunstancias.

Considere:

      / - B (B1)
    - A
      \ - C (B2)

En este caso, una fusión en cualquier dirección creará un nuevo commit con el primer padre como el commit señalado por la rama checked out actual y el segundo padre como el commit en la punta de la rama que fusionado en su rama actual. Tiene que crear un nuevo commit ya que ambas ramas tienen cambios desde su ancestro común que deben combinarse.

      / - B - D (B1)
    - A      /
      \ --- C (B2)

En este punto D (B1) ahora tiene ambos conjuntos de cambios de ambas ramas (en sí y B2). Sin embargo, la segunda rama no tiene los cambios de B1. Si fusiona los cambios de B1 a B2 para que estén sincronizados, entonces podría esperar algo que se vea así (puede forzar a git merge a hacerlo de esta manera, sin embargo, con --no-ff):

Expected:
      / - B - D (B1)
    - A      / \
      \ --- C - E (B2)
Reality:
      / - B - D (B1) (B2)
    - A      /
      \ --- C

Obtendrá eso incluso si B1 tiene confirmaciones adicionales. Mientras no haya cambios en B2 que B1 no tenga, las dos ramas se fusionarán. Hace un avance rápido que es como una rebase (las rebases también comen o linearizan la historia), excepto que a diferencia de una rebase, ya que solo una rama tiene un conjunto de cambios, no tiene que aplicar un conjunto de cambios de una rama encima de la de otra.

From:
      / - B - D - E (B1)
    - A      /
      \ --- C (B2)
To:
      / - B - D - E (B1) (B2)
    - A      /
      \ --- C

Si dejas de trabajar en B1 entonces las cosas están en gran medida bien para preservar historia a largo plazo. Solo B1 (que podría ser maestro) avanzará típicamente por lo que la ubicación de B2 en la historia de B2 representa con éxito el punto que se fusionó en B1. Esto es lo que git espera que hagas, ramificar B desde A, luego puedes fusionar A en B tanto como quieras a medida que se acumulan los cambios, sin embargo, al fusionar B de nuevo en A, no se espera que trabajes en B y más. Si continúa trabajando en su rama después de fusionarla rápidamente en la rama que estaba trabajando en borrar el historial previo de B cada vez. Realmente estás creando una nueva rama cada vez después del envío rápido a la fuente y luego al envío a la rama. Terminas con cuando haces cambios rápidos hay muchas ramas / merges que puedes ver en el historial y la estructura, pero sin la capacidad de determinar cuál era el nombre de esa rama o si lo que parece ser dos ramas separadas es realmente la misma rama.

         0   1   2   3   4 (B1)
        /-\ /-\ /-\ /-\ /
    ----   -   -   -   -
        \-/ \-/ \-/ \-/ \
         5   6   7   8   9 (B2)

1 a 3 y 5 a 8 son ramas estructurales que muestran subir si sigues el historial de 4 o 9. No hay forma en git de saber a cuál de estas ramas estructurales sin nombre y sin referencia pertenecen con las ramas con nombre y referencias como el final de la estructura. Usted podría asumir de este dibujo que 0 a 4 pertenece a B1 y 4 a 9 pertenece a B2 pero aparte de 4 y 9 no se puede saber qué rama pertenece a qué rama, simplemente he dibujado de una manera que da la ilusión de que. 0 podría pertenecer a B2 y 5 podría pertenecer a B1. Alli son 16 posibilidades diferentes en este caso de las cuales la rama nombrada a cada una de las ramas estructurales podría pertenecer. Esto es asumiendo que ninguna de estas ramas estructurales provino de una rama eliminada o como resultado de fusionar una rama en sí misma al extraer de master (el mismo nombre de rama en dos repositorios es de hecho dos ramas, un repositorio separado es como ramificar todas las ramas).

Hay una serie de estrategias de git que funcionan alrededor de esto. Puedes forzar git merge para que nunca sea rápido reenviar y siempre crear una rama merge. Una forma horrible de preservar el historial de ramas es con etiquetas y / o ramas (las etiquetas son realmente recomendables) de acuerdo con alguna convención de su elección. Realmente no recomendaría un commit vacío ficticio en la rama en la que estás fusionando. Una convención muy común es no fusionarse en una rama de integración hasta que realmente desee cerrar su rama. Esta es una práctica que la gente debe tratar de adherirse a como de lo contrario se está trabajando alrededor del punto de tener ramas. Sin embargo, en el mundo real, el ideal no siempre es práctico, lo que significa que hacer lo correcto no es viable para todas las situaciones. Si lo que está haciendo en una rama está aislado que puede funcionar, pero de lo contrario podría estar en una situación en la que cuando varios desarrolladores están trabajando en una cosa, necesitan compartir sus cambios rápidamente (idealmente, es posible que realmente desee trabajar en una rama, pero no todas las situaciones se adaptan a que cualquiera y generalmente dos personas que trabajan en una rama usted quiere evitar).

 2
Author: jgmjgm,
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-09 16:15:55

El problema parece ser encontrar el más reciente, single-commit corte entre ambas ramas en un lado, y el más antiguo ancestro común en el otro (probablemente el commit inicial del repositorio). Esto coincide con mi intuición de lo que es el punto de "ramificación".

Eso en mente, esto no es nada fácil de calcular con comandos normales de git shell, ya que git rev-list our nuestra herramienta más poderosa't no nos permite restringir la ruta por la que se llega a una confirmación. El lo más cercano que tenemos es git rev-list --boundary, que puede darnos un conjunto de todas las confirmaciones que "bloquearon nuestro camino". (Nota: git rev-list --ancestry-path es interesante, pero no sé cómo hacerlo útil aquí.)

Aquí está el script: https://gist.github.com/abortz/d464c88923c520b79e3d . Es relativamente simple, pero debido a un bucle es lo suficientemente complicado como para justificar una esencia.

Tenga en cuenta que la mayoría de las otras soluciones propuestas aquí no pueden funcionar en todas las situaciones por una simple razón: git rev-list --first-parent no es confiable en la linealización historia porque puede haber fusiones con cualquiera de los pedidos.

git rev-list --topo-order, por otro lado, es muy útil walking para caminar commits en orden topográfico but pero hacer diffs es frágil: hay múltiples ordenamientos topográficos posibles para un gráfico dado, por lo que está dependiendo de una cierta estabilidad de los ordenamientos. Dicho esto, la solución de strongk7 probablemente funciona muy bien la mayor parte del tiempo. Sin embargo, es más lento que el mío como resultado de tener que caminar toda la historia de la repo... doblemente. :-)

 0
Author: Andrew Bortz,
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-05-23 12:34:59

Lo siguiente implementa el equivalente de git de svn log {stop-on-copy y también se puede usar para encontrar el origen de la rama.

Enfoque

  1. Obtener la cabeza para todas las ramas
  2. recoger mergeBase para la rama de destino cada otra rama
  3. git.registro e iteración
  4. Detener la primera confirmación que aparece en la lista mergeBase

Como todos los ríos corren hacia el mar, todas las ramas corren hacia el amo y por lo tanto encontramos fusión-base entre aparentemente ramas no relacionadas. A medida que caminamos de regreso desde la cabeza de la rama a través de los antepasados, podemos detenernos en la primera base de fusión potencial, ya que en teoría debería ser el punto de origen de esta rama.

Notas

  • No he probado este enfoque donde las ramas de hermanos y primos se fusionaron entre sí.
  • Sé que debe haber una solución mejor.

Detalles: https://stackoverflow.com/a/35353202/9950

 0
Author: Peter Kahn,
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-05-23 12:18:36

No es una solución a la pregunta, pero pensé que valía la pena señalar el enfoque que uso cuando tengo una rama de larga vida:

Al mismo tiempo que creo la rama, también creo una etiqueta con el mismo nombre pero con un sufijo -init, por ejemplo feature-branch y feature-branch-init.

(¡Es un poco extraño que esta sea una pregunta tan difícil de responder!)

 0
Author: Stephen Darlington,
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
2018-01-31 13:14:22

Podría usar el siguiente comando para devolver el commit más antiguo en branch_a, que no es accesible desde master:

git rev-list branch_a ^master | tail -1

Quizás con una cordura adicional compruebe que el padre de ese commit es realmente accesible desde master...

 -1
Author: Marc Richarme,
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
2012-04-16 13:05:32

Puede examinar el reflog de la rama A para encontrar a partir de qué commit se creó, así como el historial completo de las confirmaciones a las que apuntaba esa rama. Los Reflogs están en .git/logs.

 -2
Author: Paggas,
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
2009-10-06 23:42:46

Creo que he encontrado una manera de tratar con todos los casos de esquina mencionados aquí:

branch=branch_A
merge=$(git rev-list --min-parents=2 --grep="Merge.*$branch" --all | tail -1)
git merge-base $merge^1 $merge^2

Charles Bailey tiene razón en que las soluciones basadas en el orden de los ancestros tienen un valor limitado; al final del día necesita algún tipo de registro de "este commit vino de la rama X", pero dicho registro ya existe; por defecto 'git merge' usaría un mensaje de confirmación como "Merge branch' branch_A 'into master", esto le dice que todas las confirmaciones del segundo padre (commit^2) vinieron de ' branch_A 'y se fusionó con el primer padre (commit^1), que es'master'.

Armado con esta información, puede encontrar la primera fusión de ' branch_A '(que es cuando' branch_A ' realmente surgió), y encontrar la base de fusión, que sería el punto de rama:)

He probado con los repositorios de Mark Booth y Charles Bailey y la solución funciona; ¿cómo no? La única forma en que esto no funcionaría es si ha cambiado manualmente el mensaje de confirmación predeterminado para las fusiones para que la información de la sucursal se pierda realmente.

Para la utilidad:

[alias]
    branch-point = !sh -c 'merge=$(git rev-list --min-parents=2 --grep="Merge.*$1" --all | tail -1) && git merge-base $merge^1 $merge^2'

Entonces puedes hacer 'git branch-point branch_A'.

Disfruta;)

 -2
Author: FelipeC,
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
2012-05-27 11:50:16