Encontrar un punto de ramificación con Git?

Tengo un repository con twigs maestro y A y mucha actividad de fusión entre los dos. ¿Cómo puedo encontrar la confirmación en mi repository cuando se creó la twig A en base al maestro?

Mi repository 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.

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

Sin embargo, descubrí que las respuestas que veo aquí no parecen dar la respuesta que pediste (o que estaba buscando), parecen dar la confirmación G , en lugar de la confirmación A

Por lo tanto, he creado el siguiente tree (letras asignadas en order 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éndome a este gráfico, no al tuyo) B, pero no A (y no D o E). Aquí están las letras adjuntas a los prefijos SHA y los posts de confirmación (mi repository puede ser clonado desde aquí , si es interesante para cualquiera):

 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 objective: encontrar B. Aquí hay tres forms que encontré, después de un poco de retoques:


1. visualmente, con gitk:

Debería ver visualmente un tree como este (como se ve desde el maestro):

Captura de pantalla de Gitk del maestro

o aquí (como se ve desde el tema):

Captura de pantalla de gitk del tema

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


2. visualmente, pero desde la terminal:

git log --graph --oneline --all

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

salida de git log --graph --oneline --all

O, en text directo:

 * a9546a2 fusionar del tema al maestro
 | \  
 |  * 648ca35 fusionando master en el tema
 |  | \  
 |  * |  132ee2a primer compromiso en la twig temática
 * |  |  e7c863d confirmar en maestro después de que el maestro se fusionó con el tema
 |  | /  
 | / |   
 * |  37ad159 commit post-branch en master
 | /  
 * 6aafd7f second commit en master antes de ramificar
 * 4112403 confirmación inicial en el maestro

en cualquier caso, vemos que 6aafd7f se compromete 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 desea algo como el anterior, o un solo command que le proporcionará la única 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 como (nota: el guión final 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 cual se puede hacer a través de la siguiente línea de command (enrevesada con las comillas):

 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 podría haber sido simplemente bash , pero sh no funcionará; la syntax <() no existe en vainilla sh . (¡Gracias de nuevo, @conny, por informarme 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 caer cuando se comparan twigs idénticas y get una forma alternativa de diferencia que elimine la forma sed de la mezcla y lo haga "más seguro" (es decir, devuelve un resultado (es decir, el compromiso más reciente) incluso cuando se compara maestro con maestro:

Como una 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' - 

Desde el caparazón:

 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'\'' -' 

Por lo tanto, en mi tree de testing (que no estuvo disponible por un time, lo siento, está de vuelta), ahora funciona tanto en el tema principal como en el tema (lo que implica G y B, respectivamente). Gracias de nuevo, liori, por la forma alternativa.


Entonces, eso es lo que yo [y liori] se me ocurrió. Parece que funciona para mí. También permite un par adicional de alias que pueden ser ú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!

Usted puede estar buscando git merge-base :

git merge-base encuentra el mejor antecesor común entre dos commits para usar en una combinación de tres vías. Un ancestro común es mejor que otro ancestro común si este último es un antepasado del primero. Un ancestro común que no tiene ningún ancestro común mejor es el mejor ancestro común , es decir, una base de fusión . Tenga en count que puede haber más de una base de combinación para un par de confirmaciones.

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

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

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

He agregado ese command a mis alias en ~/.gitconfig como:

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

así que puedo llamarlo así:

 $ git diverges branch-a master 

Si te gustan los commands cortantes,

  git rev-list $ (git rev-list --primer padre ^ nombre de twig maestro | queue -n1) ^^! 

Aquí hay una explicación.

El siguiente command le proporciona la list de todas las confirmaciones en el maestro que ocurrieron después de que se creó el nombre de la twig

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

Como solo te importa la date más temprana de las confirmaciones, quieres la última línea del resultado:

  git rev-list ^ branch_name --primer padre maestro |  queue -n1 

El padre de la confirmación más antigua que no es un antecesor de "nombre_twig" está, por definición, en "nombre_twig", y está en "maestro" ya que es un antepasado de algo en "maestro". Así que tienes el compromiso más temprano que está en ambas twigs.

El command

  git rev-list commit ^^! 

es solo una forma de mostrar la reference de confirmación principal. Podrías usar

  git log -1 commit ^ 

o lo que sea.

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

 _C1___C2_______ master
   \ \ _XXXXX_ twig A (las X denotan cruces arbitrarios entre maestro y A)
    \ _____ / twig B

tiene mucho sentido dar salida a C2 como confirmación de "bifurcación". Esto es cuando el desarrollador se ramificó desde "maestro". ¡Cuando se ramificó, la twig "B" ni siquiera se fusionó en su twig! Esto es lo que ofrece la solución en esta publicación.

Si lo que desea es la última confirmación C de modo que todas las routes desde el origen hasta la última confirmación en la twig "A" pasen por C, entonces desea ignorar el order ascendente. Eso es puramente topológico y te da una idea de cuándo tienes dos versiones del código funcionando al mismo time. Ahí es cuando elegirías enfoques basados ​​en combinación y devolvería C1 en mi ejemplo.

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 guión que utilicé para replicar el repository dado en la pregunta.

El logging

Al crear un repository con la estructura dada, obtenemos el logging 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 label que lo hace explícito sobre el punto en el que creamos la twig y, por lo tanto, el compromiso que deseamos encontrar.

La solución que funciona

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

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

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

Si branch_A en master y luego fusionas master en branch_A sin commits intermedios, 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 a tener que seguir labelndo el punto de ramificación de las twigs de ejecución larga, ya que no puedo garantizar que se puedan encontrar de manera confiable más adelante.

Esto realmente se networkinguce a la falta de git de lo que hg llama twigs nombradas . El blogger jhw llama a estos linajes vs. familias en su artículo Why I Like Mercurial More Than Git y su artículo de seguimiento More On Mercurial vs. Git (with Graphs!) . Recomendaría a las personas que los lean para ver por qué algunos conversos mercuriales pierden el hecho de no tener twigs con nombre en git .

Las soluciones que no funcionan

La solución provista 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 devuelve I

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

La solución provista por Karl devuelve X :

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

La secuencia de commands

 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 una gran diferencia en esto, pero:

 $ git --version git version 1.7.1 

Gracias a Charles Bailey por mostrarme una forma más compacta de crear el repository de ejemplos.

En general, esto no es posible. En el historial de una sucursal, se ramificó una bifurcación y fusión antes de una sucursal con nombre, y una twig intermedia de dos twigs con nombre tiene el mismo aspecto.

En git, las twigs son solo los nombres actuales de las secciones de la historia. Ellos realmente no tienen una identidad fuerte.

Esto no suele ser un gran problema ya que la combinación (ver la respuesta de Greg Hewgill) de dos confirmaciones suele ser mucho más útil, ya que proporciona el compromiso más reciente que compartieron las dos sucursales.

Una solución que depende del order de los padres de un compromiso obviamente no funcionará en situaciones en las que una twig se haya integrado completamente en algún momento de la historia de la sucursal.

 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 se cae si se ha realizado una fusión de integración con los padres revertidos (por ejemplo, se usó una bifurcación temporal para realizar una fusión de testing en el maestro y luego se reenvió rápidamente a la twig de características para continuar).

 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" 

¿Qué tal algo así 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`^ 

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

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

En cambio, he seguido un enfoque diferente, basado en el hecho de que ambas twigs comparten mucha historia, exactamente toda la historia antes de que la bifurcación sea 100% la misma, así que en lugar de volver, mi propuesta es avanzar (a partir de la 1ra. commit), buscando la primera diferencia en ambas twigs. El punto de bifurcació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 que no están cubiertas pero … ciao 🙂

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

Funciona en todos los repositorys que se mencionan aquí, e incluso he abordado algunos de los más difíciles que aparecieron en la list de correo . También escribí testings 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 } 

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

Es por eso que escribí un par de parches que agregan el concepto de una twig de tail . Cada vez que se crea una twig, también se crea un puntero al punto original, la tail ref. Esta reference se actualiza cada vez que se reajusta la twig.

Para descubrir el punto de ramificación de la twig de desarrollo, todo lo que tienes que hacer es usar devel@{tail} , eso es todo.

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

Para encontrar compromisos desde el punto de bifurcación, puede usar esto.

 git log --ancestry-path master..topicbranch 

El siguiente command revelará SHA1 de Commit A

git merge-base --fork-point A

Parece que estoy disfrutando con

 git rev-list branch...master 

La última línea que obtienes es la primera confirmación en la twig, entonces es cuestión de get el padre de eso. Asi que

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

Parece que funciona para mí y no necesita diffs y demás (lo cual es útil ya que no tenemos esa versión de diff)

Corrección: Esto no funciona si estás en la twig principal, pero estoy haciendo esto en una secuencia de commands por lo que no es un problema

El problema parece ser encontrar el corte de compromiso único más reciente entre ambas twigs en un lado, y el ancestro común más temprano en el otro (probablemente el compromiso inicial del repository). Esto coincide con mi intuición de cuál es el punto de "bifurcación".

Teniendo esto en count, esto no es nada fácil de calcular con los commands normales de Git Shell, ya que git rev-list , nuestra herramienta más poderosa, no nos permite restringir el path por el que se llega a una confirmación. Lo más cerca que tenemos es git rev-list --boundary , que puede darnos un set de todos los commits que "bloquearon nuestro path". (Nota: git rev-list --ancestry-path es interesante, pero no sé cómo hacer que sea ú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 count 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 de la historia porque puede haber fusiones con cualquiera de los pedidos.

git rev-list --topo-order , por otro lado, es muy útil – para realizar commits en order topográfico – pero hacer diffs es frágil: hay múltiples posibles orderamientos topocharts para un gráfico dado, por lo que depende de una cierta estabilidad de los pedidos. Dicho eso, la solución de strongk7 probablemente funciona muy bien la mayor parte del time. Sin embargo, es más lento que el mío como resultado de tener que recorrer toda la historia del repository … dos veces. 🙂

A veces es efectivamente imposible (con algunas excepciones de dónde podrías tener suerte tener datos adicionales) y las soluciones aquí no funcionarán.

Git no conserva el historial de ref (que incluye twigs). Solo almacena la position actual para cada twig (la cabeza). Esto significa que puede perder algún historial de sucursal en git a lo largo del time. Cada vez que se bifurca, por ejemplo, se pierde de inmediato qué twig fue la original. Todo lo que hace una sucursal es:

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

Puede suponer que el primero al que se compromete es la twig. Esto tiende a ser el caso, pero no siempre es así. No hay nada que le impida comprometerse con cualquiera de las twigs primero después de la operación anterior. Además, no se garantiza que las marcas de time git sean confiables. No es hasta que te comprometas con ambos que realmente se convierten en twigs estructuralmente.

Mientras que en los diagtwigs tendemos a numerar los compromisos conceptualmente, git no tiene un concepto real estable de secuencia cuando se ramifica el tree de confirmación. En este caso, puede suponer que los numbers (indicar el order) están determinados por la timestamp (podría ser divertido ver cómo una IU de git maneja cosas cuando establece todas las marcas de time en la misma).

Esto es lo que un humano espera conceptualmente:

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

Esto es lo que realmente obtienes:

 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 twig original, pero de hecho podría ser simplemente una twig muerta (alguien hizo el check -b pero nunca se comprometió con ella). No es hasta que te comprometas con ambos que obtienes una estructura de sucursal legítima dentro de git:

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

Siempre se sabe que C1 llegó antes que C2 y C3, pero nunca se sabe con certeza si C2 llegó antes que C3 o C3 antes que C2 (porque puede establecer el time en su estación de trabajo para cualquier cosa, por ejemplo). B1 y B2 también son engañosos ya que no se puede saber qué twig fue la primera. En muchos casos puede hacer una muy buena y usualmente precisa suposition. Es un poco como una pista de carreras. En general, todas las cosas son iguales a los autos, entonces puedes asumir que un auto que viene en una vuelta atrás comenzó una vuelta atrás. También tenemos convenciones que son muy confiables, por ejemplo, el maestro casi siempre representará las twigs más longevas, aunque lamentablemente he visto casos en los que incluso este no es el caso.

El ejemplo que se da 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) 

Real aquí también es engañoso porque nosotros, los 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 does (A <-B o B-> A). Lo lee de la reference a la raíz. Los Refs pueden estar en cualquier lugar pero tienden a ser hojas, al less para twigs activas. Un ref señala a un commit y commits solo contiene un me gusta 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 siempre es el compromiso original que se fusionó en. Los otros padres son siempre commits que se combinaron en el commit 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, sino una expresión de todas las routes que git puede tomar de cada ref (B1 y B2).

El almacenamiento interno de Git se parece más a esto (no 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 en bruto, verás cero o más campos principales. Si hay cero, significa que no hay padres 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 una confirmación raíz. Si hay más de uno, significa que la confirmación es el resultado de una fusión y todos los padres después de la primera son confusiones 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 eso, 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. puede haber confusión aquí entre los términos commit, branch y ref. De hecho, puede fusionar un compromiso. Esto es lo que la fusión realmente hace. Una reference simplemente apunta a una confirmación y una bifurcación no es más que una reference en la carpeta .git / refs / heads, la location de la carpeta es lo que determina que una reference sea una bifurcación en lugar de otra cosa, como una label.

Donde pierdes historia es que fusionar hará una de dos cosas dependiendo de las circunstancias.

Considerar:

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

En este caso, una fusión en cualquier dirección creará una nueva confirmación con el primer elemento primario como la confirmación señalada por la twig actual desprotegida y el segundo elemento primario como la confirmación en la punta de la twig que se fusionó con la twig actual. It has to create a new commit as both branches have changes since their common ancestor that must be combined.

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

At this point D (B1) now has both sets of changes from both branches (itself and B2). However the second branch doesn't have the changes from B1. If you merge the changes from B1 into B2 so that they are syncronised then you might expect something that looks like this (you can force git merge to do it like this however with –no-ff):

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

You will get that even if B1 has additional commits. As long as there aren't changes in B2 that B1 doesn't have, the two branches will be merged. It does a fast forward which is like a rebase (rebases also eat or linearise history), except unlike a rebase as only one branch has a change set it doesn't have to apply a changeset from one branch on top of that from another.

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

If you cease work on B1 then things are largely fine for preserving history in the long run. Only B1 (which might be master) will advance typically so the location of B2 in B2's history successfully represents the point that it was merged into B1. This is what git expects you to do, to branch B from A, then you can merge A into B as much as you like as changes accumulate, however when merging B back into A, it's not expected that you will work on B and further. If you carry on working on your branch after fast forward merging it back into the branch you were working on then your erasing B's previous history each time. You're really creating a new branch each time after fast forward commit to source then commit to branch. You end up with when you fast forward commit is lots of branches/merges that you can see in the history and structure but without the ability to determine what the name of that branch was or if what looks like two separate branches is really the same branch.

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

1 to 3 and 5 to 8 are structural branches that show up if you follow the history for either 4 or 9. There's no way in git to know which of this unnamed and unreferenced structural branches belong to with of the named and references branches as the end of the structure. You might assume from this drawing that 0 to 4 belongs to B1 and 4 to 9 belongs to B2 but apart from 4 and 9 was can't know which branch belongs to which branch, I've simply drawn it in a way that gives the illusion of that. 0 might belong to B2 and 5 might belong to B1. There are 16 different possibilies in this case of which named branch each of the structural branches could belong to. This is assuming that none of these structural branches came from a deleted branch or as a result of merging a branch into itself when pulling from master (the same branch name on two repos is infact two branches, a separate repository is like branching all branches).

There are a number of git strategies that work around this. You can force git merge to never fast forward and always create a merge branch. A horrible way to preserve branch history is with tags and/or branches (tags are really recommended) according to some convention of your choosing. I realy wouldn't recommend a dummy empty commit in the branch you're merging into. A very common convention is to not merge into an integration branch until you want to genuinely close your branch. This is a practice that people should attempt to adhere to as otherwise you're working around the point of having branches. However in the real world the ideal is not always practical meaning doing the right thing is not viable for every situation. If what you're doing on a branch is isolated that can work but otherwise you might be in a situation where when multiple developers are working one something they need to share their changes quickly (ideally you might really want to be working on one branch but not all situations suit that either and generally two people working on a branch is something you want to avoid).

The following implements git equivalent of svn log –stop-on-copy and can also be used to find branch origin.

Enfoque

  1. Get head for all branches
  2. collect mergeBase for target branch each other branch
  3. git.log and iterate
  4. Stop at first commit that appears in the mergeBase list

Like all rivers run to the sea, all branches run to master and therefore we find merge-base between seemingly unrelated branches. As we walk back from branch head through ancestors, we can stop at the first potential merge base since in theory it should be origin point of this branch.

Notas

  • I haven't tried this approach where sibling and cousin branches merged between each other.
  • I know there must be a better solution.

details: http://sofes.miximages.com/a/35353202/9950

You could use the following command to return the oldest commit in branch_a, which is not reachable from master:

 git rev-list branch_a ^master | tail -1 

Perhaps with an additional sanity check that the parent of that commit is actually reachable from master…

You can examine the reflog of branch A to find from which commit it was created, as well as the full history of which commits that branch pointed to. Reflogs are in .git/logs .

I believe I've found a way that deals with all the corner-cases mentioned here:

 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 is quite right that solutions based on the order of ancestors have only limited value; at the end of the day you need some sort of record of "this commit came from branch X", but such record already exists; by default 'git merge' would use a commit message such as "Merge branch 'branch_A' into master", this tells you that all the commits from the second parent (commit^2) came from 'branch_A' and was merged to the first parent (commit^1), which is 'master'.

Armed with this information you can find the first merge of 'branch_A' (which is when 'branch_A' really came into existence), and find the merge-base, which would be the branch point 🙂

I've tried with the repositories of Mark Booth and Charles Bailey and the solution works; how couldn't it? The only way this wouldn't work is if you have manually changed the default commit message for merges so that the branch information is truly lost.

For usefulness:

 [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' 

Then you can do ' git branch-point branch_A '.

Disfrutar;)