ramificacion - git tag name
¿Encontrar un punto de ramificación con Git? (21)
Algunas veces es efectivamente imposible (con algunas excepciones de donde podría tener suerte de tener datos adicionales) y las soluciones aquí no funcionan.
Git no conserva la historia de referencia (que incluye ramas). Solo almacena la posición actual para cada rama (la cabeza). Esto significa que puedes perder algo de historial de sucursales 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
Se podría asumir que el primer comprometido es la rama. Esto suele ser el caso, pero no siempre es así. No hay nada que le impida comprometerse con cualquiera de las dos ramas 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 los compromisos conceptualmente, git no tiene un concepto de secuencia realmente estable cuando el árbol de confirmación 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 IU de git maneja las cosas cuando configura todas las marcas de tiempo de la misma manera).
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)
Asumiría que B1 es la rama original, pero de hecho podría ser simplemente una rama muerta (alguien realizó la verificación -b pero nunca se comprometió con ella). No es hasta que se comprometa con ambos que obtiene una estructura de rama legítima dentro de git:
Either:
/ - C2 (B1)
-- C1
/ - C3 (B2)
Or:
/ - C3 (B1)
-- C1
/ - C2 (B2)
Siempre sabe que C1 vino antes que C2 y C3, pero nunca sabe de manera confiable si C2 vino antes que C3 o C3 antes que C2 (porque puede configurar la hora en su estación de trabajo a cualquier cosa, por ejemplo). B1 y B2 también son engañosos, ya que no se puede saber qué rama fue la primera. En muchos casos, puede hacer una suposición muy buena y generalmente precisa. Es un poco como una pista de carreras. Todas las cosas en general son iguales a los autos, entonces se puede suponer 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 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 que preserva 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 (A <-B o B-> A). 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 punto de referencia para un compromiso y un compromiso solo contienen un "Me gusta" a sus padres, no a sus hijos. Cuando una confirmación es una confirmación de combinación, tendrá más de un padre. El primer padre es siempre el compromiso original que se fusionó en. Los otros padres son siempre 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, sino 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 git sin formato verás cero o más campos padre. Si hay cero, significa que no hay padre y la confirmación es una raíz (en realidad puede tener múltiples raíces). Si hay uno, significa que no hubo fusión y no es una confirmación de raíz. Si hay más de uno, significa que la confirmación es el resultado de una combinación y todos los padres después de la primera son confirmaciones de combinació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 golpeen A, su cadena será la misma, antes de que su cadena sea completamente diferente. El primer compromiso que otros dos compromisos tienen en común es el antepasado común y de dónde se separaron. puede haber alguna confusión aquí entre los términos cometer, rama y ref. De hecho, puede fusionar un compromiso. Esto es lo que realmente hace la fusión. Una referencia simplemente apunta a una confirmación y una rama no es más que una referencia en la carpeta .git / refs / heads, la ubicación de la carpeta es lo que determina que una referencia es una rama en lugar de otra cosa como una etiqueta.
Donde pierdes la historia es que la fusión hará una de dos cosas dependiendo de las circunstancias.
Considerar:
/ - B (B1)
- A
/ - C (B2)
En este caso, una combinación en cualquier dirección creará una nueva confirmación con el primer padre como el compromiso señalado por la rama que se está prestando y el segundo padre como el compromiso en la punta de la rama que fusionó en su rama actual. Tiene que crear una nueva confirmación 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 combina los cambios de B1 a B2 para que estén sincronizados, puede esperar algo como este (puede forzar a Git Merge a hacerlo de esta forma con --no-ff):
Expected:
/ - B - D (B1)
- A / /
/ --- C - E (B2)
Reality:
/ - B - D (B1) (B2)
- A /
/ --- C
Obtendrá que 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 linealizan el historial), 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 otra.
From:
/ - B - D - E (B1)
- A /
/ --- C (B2)
To:
/ - B - D - E (B1) (B2)
- A /
/ --- C
Si dejas de trabajar en B1, las cosas están muy bien para preservar la historia a largo plazo. Solo B1 (que podría ser maestro) avanzará normalmente, por lo que la ubicación de B2 en la historia de B2 representa con éxito el punto en que se fusionó en B1. Esto es lo que git espera que hagas, para ramificar B desde A, luego puedes combinar A en B tanto como quieras a medida que se acumulan los cambios, sin embargo, al fusionar B de nuevo con A, no se espera que trabajes en B y más . Si continúa trabajando en su sucursal después de un rápido avance, vuelva a fusionarlo con la rama en la que estaba trabajando y luego borre el historial anterior de B cada vez. Realmente estás creando una nueva rama cada vez después de un rápido envío a la fuente y luego a la rama.Usted termina cuando la confirmación rápida es un montón de ramas / fusiones que puede 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 parecen 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 se muestran si sigue el historial de 4 o 9. No hay manera de saber cuál de estas ramas estructurales sin nombre ni referencias pertenece a las ramas con nombre y referencia como Fin de la estructura. Se puede suponer a partir de este dibujo que de 0 a 4 pertenece a B1 y de 4 a 9 que pertenece a B2, pero aparte de 4 y 9 no se puede saber qué rama pertenece a qué rama, simplemente la he dibujado de una manera que da la ilusión de eso. 0 podría pertenecer a B2 y 5 podría pertenecer a B1. Hay 16 posibilidades diferentes en este caso, de las cuales las ramas con nombre a las que podría pertenecer cada una de las ramas estructurales.Esto es asumiendo que ninguna de estas ramas estructurales provino de una rama eliminada o como resultado de fusionar una rama en sí misma cuando se extrae del maestro (el mismo nombre de rama en dos repos es infact dos ramas, un repositorio separado es como ramificar todas las ramas) .
Hay una serie de estrategias git que trabajan alrededor de esto. Puede forzar que git merge nunca avance rápido y siempre cree una rama de combinación. Una forma horrible de preservar el historial de sucursales 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 dummy commit en la rama en la que te estás fusionando. Una convención muy común es no fusionarse en una rama de integración hasta que realmente desee cerrarla. Esta es una práctica que las personas deberían tratar de cumplir, ya que, de lo contrario, estarían trabajando alrededor del punto de tener sucursales. Sin embargo, en el mundo real, el ideal no es siempre práctico, lo que significa que hacer lo correcto no es viable para todas las situaciones. Si lo que tulo que se está haciendo en una sucursal es un proceso aislado que puede funcionar pero, de lo contrario, podría estar en una situación en la que cuando varios desarrolladores estén trabajando, algo que necesitan para compartir sus cambios rápidamente (lo ideal es que realmente desee trabajar en una sucursal, pero no todas las situaciones son adecuadas). que cualquiera y generalmente dos personas que trabajan en una sucursal es algo que usted quiere evitar).
Tengo un repositorio con ramas maestro y A y mucha actividad de combinación entre los dos. ¿Cómo puedo encontrar la confirmación en mi repositorio cuando se creó la rama A basada en el maestro?
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 encuentra git merge-base (--all)
.
Aquí hay una versión mejorada de mi respuesta anterior respuesta anterior . Se basa en los mensajes de confirmación de las combinaciones para encontrar dónde se creó la rama por primera vez.
Funciona en todos los repositorios mencionados aquí, e incluso he abordado algunos de los que surgieron 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
}
Dado que muchas de las respuestas en este hilo no dan la respuesta que pedía la pregunta, aquí hay un resumen de los resultados de cada solución, junto con el script que usé para replicar el repositorio dado en la pregunta.
El registro
Al crear un repositorio con la estructura dada, obtenemos el registro 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 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 fusiona master
en branch_A
sin compromisos intermedios, la solución de lindes solo le ofrece la primera divergencia más reciente .
Eso significa que para mi flujo de trabajo, creo que voy a tener que seguir marcando el punto de ramificación de las ramas de larga ejecución, ya que no puedo garantizar que se puedan encontrar de manera confiable más adelante.
Esto realmente se reduce a la falta de lo que hg
llama sucursales . El blogger jhw llama a estos linajes contra 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 que las personas los leyeran para ver por qué algunos conversos mercuriales olvidan no tener sucursales 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 provista por Greg Hewgill vuelve 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 comandos
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 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 crear un script para el repositorio de ejemplos.
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.
Por eso escribí un par de parches que añaden el concepto de una rama de tail
. Cada vez que se crea una rama, también se crea un puntero al punto original, la referencia de la tail
. Esta referencia se actualiza cada vez que la sucursal es rebasada.
Para averiguar el punto de ramificación de la rama devel, todo lo que tienes que hacer es usar devel@{tail}
, eso es todo.
El siguiente comando revelará el SHA1 de Commit A
git merge-base --fork-point A
En general, esto no es posible. En el historial de una rama, una rama y combinació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 combinación de bases (ver la respuesta de Greg Hewgill) de dos confirmaciones suele ser mucho más útil, ya que proporciona la confirmación más reciente que las dos sucursales compartieron.
Obviamente, una solución basada en el orden de los padres de un compromiso no funcionará en situaciones donde una sucursal 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 combinación de integración con los padres invertidos (por ejemplo, se usó una rama temporal para realizar una combinación de prueba en el maestro y luego se avanzó rápidamente hacia la rama de la característica para seguir construyendo).
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"
Estaba buscando lo mismo, y encontré esta pregunta. ¡Gracias por preguntar!
Sin embargo, encontré que las respuestas que veo aquí no parecen dar la respuesta que me pediste (o que estaba buscando); parecen dar el compromiso G
, en lugar del compromiso A
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 al tuyo) B, pero no A (y no D o E). Aquí están las letras adjuntas a los prefijos de SHA y los mensajes de confirmación (mi repo se puede clonar desde here , si eso 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
Por lo tanto, el objetivo: encontrar B. Aquí hay tres formas que encontré, después de un poco de retoques:
1. visualmente, con gitk:
Debería ver visualmente un árbol como este (visto desde el maestro):
o aquí (como se ve desde el tema):
en ambos casos, he seleccionado la confirmación que es B
en mi gráfica. 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 la terminal:
git log --graph --oneline --all
(Edición / nota lateral: agregar --decorate
también puede ser interesante; agrega una indicación de nombres de bifurcaciones, etiquetas, etc. No agregar esto a la línea de comandos anterior ya que la salida de abajo no refleja su uso).
que muestra (asumiendo que git config --global color.ui auto
):
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 que 6aafd7f commit es el punto común más bajo, es decir, B
en mi gráfica, o A
en la tuya.
3. Con cáscara mágica:
No especifique en su pregunta si quería algo como el anterior, o un solo comando que solo le proporcionará la única revisión, y nada más. Bueno, aquí está el ú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 puede incluir en su ~ / .gitconfig como (nota: el seguimiento final es importante; gracias Brian por llamar la atención) :
[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 se podría hacer a través de la siguiente línea de comandos (complicada con 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 tan fácil como bash
, pero sh
no funcionará: la sintaxis <()
no existe en vanilla 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 caerse cuando se comparan ramas idénticas y obtener una forma de diferencias alternativa que elimine la forma sed de la mezcla y la haga "más segura" (es decir, devuelve un resultado (a saber, la la confirmación 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'' -
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''/''' -''
Por lo tanto, en mi árbol de prueba (que no estuvo disponible por un tiempo, lo siento; está de vuelta), ahora funciona tanto en el tema principal como en el tema (dando las confirmaciones G y B, respectivamente). Gracias de nuevo, liori, por la forma alternativa.
Entonces, eso es lo que yo [y liori] se nos ocurrió. Parece que funciona para mí. También permite un par de alias adicionales que pueden 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!
Hace poco también necesitaba resolver este problema y terminé escribiendo un script de Ruby para esto: https://github.com/vaneyckt/git-find-branching-point
He usado git rev-list
para este tipo de cosas. Por ejemplo, (note 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 ha fusionado el maestro en la rama A un par de veces, eso dividirá un par de puntos de ramificación posibles (básicamente, el punto de la rama original y luego cada punto en el que fusionó el maestro en la rama A). Sin embargo, al menos debería reducir las posibilidades.
He agregado ese comando a mis alias en ~/.gitconfig
como:
[alias]
diverges = !sh -c ''git rev-list --boundary $1...$2 | grep "^-" | cut -c2-''
así que puedo llamarlo como:
$ git diverges branch-a master
Para encontrar confirmaciones desde el punto de bifurcación, puede utilizar esto.
git log --ancestry-path master..topicbranch
Parece que estoy obteniendo algo de alegría con
git rev-list branch...master
La última línea que obtienes es la primera confirmación en la rama, entonces es cuestión de obtener 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, etc. (lo cual es útil ya que no tenemos esa versión de diff)
Corrección: esto no funciona si está en la rama maestra, pero estoy haciendo esto en una secuencia de comandos, por lo que es un problema menor
Que 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`^
Quizá git merge-base
buscando git merge-base
:
git merge-base encuentra el mejor (es) ancestro (s) común (es) entre dos confirmaciones para usar en una combinación de tres vías. Un antepasado común es mejor que otro antepasado común si este último es un antepasado del primero. Un antepasado común que no tiene un antepasado común mejor es el mejor antepasado común , es decir, una base de combinación . Tenga en cuenta que puede haber más de una base de fusión para un par de confirmaciones.
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 el maestro que ocurrieron después de que se creó branch_name
git rev-list --first-parent ^branch_name master
Ya que solo te preocupa 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 del primer compromiso que no es un antepasado de "branch_name" es, por definición, en "branch_name", y está en "master" ya que es un antepasado de algo en "master". Así que tienes el primer compromiso que hay en ambas ramas.
El comando
git rev-list commit^^!
Es solo una forma de mostrar la referencia de confirmación padre. Usted podría usar
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 el compromiso de "ramificación". Esto es cuando el desarrollador se separó de "maestro". Cuando se ramificó, la 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, de modo que todas las rutas desde el origen hasta la última confirmación en la rama "A" pasen por C, debe ignorar el orden de ascendencia. Eso es puramente topológico y le da una idea de cuándo tiene dos versiones del código funcionando al mismo tiempo. Ahí es cuando iría con enfoques basados en fusión y devolverá C1 en mi ejemplo.
seguramente me estoy perdiendo algo, pero en mi opinión, todos los problemas anteriores se deben a que siempre estamos tratando de encontrar el punto de ramificación en la historia, y eso causa todo tipo de problemas debido a las combinaciones de combinaciones disponibles.
En su lugar, he seguido un enfoque diferente, basado en el hecho de que ambas sucursales comparten mucha historia, exactamente toda la historia antes de la ramificación es 100% la misma, así que en lugar de retroceder, mi propuesta es avanzar (desde el 1er. comprometerse), buscando la 1ª diferencia en ambas ramas. El punto de derivació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. Claro que hay bordes no cubiertos pero ... ciao :-)
Creo que he encontrado una manera de lidiar con todos los casos 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 compromiso provino de la rama X", pero dicho registro ya existe; por defecto, ''git merge'' usaría un mensaje de confirmación como "Combinar rama ''branch_A'' en master", esto le dice que todas las confirmaciones del segundo padre (commit ^ 2) provinieron de ''branch_A'' y se fusionaron a la primera padre (commit ^ 1), que es ''master''.
Con esta información, puede encontrar la primera fusión de ''branch_A'' (que es cuando realmente surgió ''branch_A'') y encontrar la fusión de base, que sería el punto de ramificación :)
He intentado con los repositorios de Mark Booth y Charles Bailey y la solución funciona; como no pudo 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 rama se pierda realmente.
Para 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
''.
Disfrutar;)
No es una solución a la pregunta, pero pensé que valía la pena señalar el enfoque que utilizo 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 -init
sufijo, por ejemplo feature-branch
y feature-branch-init
.
(¡Es un poco extraño que esta sea una pregunta tan difícil de responder!)
Puede usar el siguiente comando para devolver el commit más antiguo en branch_a, que no es accesible desde el maestro:
git rev-list branch_a ^master | tail -1
Tal vez con un control de cordura adicional de que el padre de ese compromiso es realmente accesible desde el maestro ...
El problema parece ser encontrar el corte más reciente de un solo compromiso entre ambas ramas en un lado y el ancestro común más antiguo en el otro (probablemente el compromiso inicial del repositorio). Esto coincide con mi intuición de cuál es el punto de "ramificación".
Teniendo esto en cuenta, esto no es fácil de calcular con los comandos normales de git shell, ya que git rev-list
, nuestra herramienta más poderosa, no nos permite restringir la ruta por la que se llega a un compromiso. 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 idea general.
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 de la historia porque puede haber combinaciones con cualquiera de los pedidos.
git rev-list --topo-order
Por otro lado, es muy útil, para realizar caminatas en orden topográfico, pero hacer diffs es frágil: existen múltiples ordenamientos topográficos posibles para un gráfico dado, por lo que depende de una cierta estabilidad de los ordenamientos. Dicho esto, la solution 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 recorrer toda la historia del repositorio ... dos veces. :-)
Lo siguiente implementa git equivalente a svn log --stop-on-copy y también se puede usar para encontrar el origen de la rama.
Enfoque
- Obtener cabeza para todas las ramas
- recoger mergeBase para la rama de destino cada otra rama
- git.log y iterar
- Deténgase en la primera confirmación que aparece en la lista mergeBase
Como todos los ríos corren hacia el mar, todas las ramas corren para dominar y, por lo tanto, encontramos una base de fusión entre ramas aparentemente no relacionadas. A medida que retrocedemos desde la cabeza de 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 mejor solución.
detalles: https://.com/a/35353202/9950
Puede examinar el reflog de la rama A para encontrar a partir de qué comisión se creó, así como el historial completo de los que esa rama señaló. Reflogs están en .git/logs
.