¿Cómo identificar la última combinación git en master?

Intento identificar la última fusión en mi twig master git, pero el resultado de git log no tiene sentido.

Los últimos commands que ejecuté fueron git checkout master; git merge staging git checkout master; git merge staging , pero si ejecuto git log , la primera input dice:

 Merge branch 'staging' into DEVBRANCH-123 

Esto es bastante desconcertante. ¿Por qué la fusión en mi twig principal no menciona la twig principal, sino una twig completamente no relacionada? Buscando todos mis loggings de git, nada menciona realmente la twig principal. ¿Por qué es esto?

Si git merge hizo un "avance rápido" en lugar de hacer una fusión, esto no deja rastro en el gráfico. Deja huellas en reflogs, y puede usarlas para descubrirlas después del hecho, pero las inputs de reflog eventualmente caducan, borrando los rastros.

El command git log usa el gráfico para mostrar cada confirmación, por lo que si no hay rastros de la "fusión" de avance rápido, no muestra el avance rápido no fusión. unir". De forma pnetworkingeterminada, el git log ignora por completo los reflogs (por una buena razón: el reflog está destinado principalmente a la recuperación de errores y muestra que normalmente haría que la vista sea aún peor).

Probablemente deberías haber hecho git merge --no-ff ; ver la última sección a continuación. Todavía puedes hacer eso, encontrando (a través del reflog) donde el master señaló antes de que Git hiciera su operación de avance rápido.

Comprender fusiones reales

"Avance rápido" es una de las partes más confusas de Git. Para entender cuándo y por qué Git puede hacer un avance rápido, necesitamos ver cómo Git implementa la ramificación y qué nombres de twig son realmente / hacer, y cómo funciona la fusión.

El nombre de una twig, como el master o el staging , es solo un nombre corto, legible para el humano, que representa una única confirmación. Esta confirmación es la confirmación de una sucursal. Cuando hacemos una nueva confirmación, Git agrega la confirmación a la punta de la twig actual (cuyo nombre se almacena en HEAD ) y luego hace que ese nombre de twig apunte a la nueva sugerencia. El nuevo compromiso de sugerencia, a su vez, apunta a lo que era la sugerencia hace un momento, antes de que agreguemos el nuevo compromiso:

 ... <-F <-G <-- branch 

se convierte en:

 ... <-F <-G <-H <-- branch 

Cada compromiso "apunta hacia atrás" a su antecesor inmediato: su compromiso principal . Esto le da a Git la capacidad de seguir el historial de commits, que se compone de esos commits. Commit H contiene la ID de commit G ; commit G contiene la ID de F ; y así sucesivamente hasta el primer commit (probablemente A ), que no tiene padre, porque fue el primer commit.

Para comenzar en este seguimiento de la historia, Git necesita el nombre de la sucursal, que "apunta a" (contiene la ID de) la sugerencia actual.

Cuando crea una nueva twig y se desarrolla en ella, los commits mismos crean la parte de la twig-y del gráfico. Empecemos con:

 A--B--C <-- master 

y luego crea una nueva twig:

 A--B--C <-- master, branch (HEAD) 

y luego agrega una nueva confirmación:

 A--B--C 

A – B – C <- master \ D <- branch (HEAD)

Ahora que tenemos dos nombres de twig ( master y branch ) necesitamos saber cuál estamos usando , de ahí el (HEAD) . Esa es la twig que cambia, para apuntar a la nueva confirmación, cuando hacemos la nueva confirmación.

Ahora, supongamos que tenemos un montón de compromisos diferentes en dos twigs diferentes, comenzando desde un punto de partida común:

 ...--C--G--H <-- master (HEAD) \ D--E--F <-- branch 

y ejecutamos git merge branch . Git encontrará el punto de partida común C Esta es la base de fusión .

Git mirará ahora para ver "lo que hicimos" ( git diff --find-renames <commit-C> <commit-H> , y "lo que hicieron" ( git diff --find-renames <commit-C> <commit-F> . La acción merge, para fusionarse como un verbo, combina estos cambios .

Lo que sea que hayamos hecho con algunos files de C , Git intenta tomar nuestros cambios. Lo que sea que hayan hecho con los files de C , Git intenta tomar sus cambios. Si ninguno de nosotros cambia algunos files de C , Git solo lleva los files hacia adelante. Si ambos tocamos los mismos files, Git tiene que combinar esos cambios en esos files (y podríamos tener un conflicto de fusión). Si todo va bien, sin embargo, Git ahora hace un nuevo compromiso:

 ...--C--G--H---M <-- master (HEAD) \ / D--E--F <-- branch 

La nueva confirmación se realiza en la twig actual como siempre, de modo que el master apunte a la nueva sugerencia; pero este nuevo compromiso de punta tiene una propiedad especial: tiene dos padres, no solo el habitual. El primer padre es el habitual, el consejo anterior de master . El segundo padre es la punta de la otra twig.

Este compromiso es un compromiso de fusión . Es como cualquier otra confirmación: tiene un post de logging, por lo general algo inútil como la merge branch 'branch' , y un nombre de autor y correo electrónico y timestamp, y registra como una instantánea todos los files fusionados. Pero es un tipo especial de compromiso: un compromiso de dos padres. Este es un commit de fusión , usando la palabra fusionar como un adjetivo. A menudo lo acortamos aún más: es una combinación , fusión-como-un-nombre.

Entender el avance rápido

Ahora que sabemos cómo funciona una fusión normal o "real", veamos cuando Git puede hacer un avance rápido.

Supongamos que, en lugar de:

 ...--C--G--H <-- master (HEAD) \ D--E--F <-- branch 

solo tenemos:

 ...--C <-- master (HEAD) \ D--E--F <-- branch 

Si ahora ejecutamos la git merge branch , Git necesita combinar todos los cambios que realizamos en el master desde la confirmación C , con todos los cambios que hicieron en la branch desde la confirmación de C Pero espera, master es cometer C ¡La diferencia de C a C va a estar vacía! No hay necesidad de fusionar-como-un-verbo después de todo. ¡Podemos simplemente hacer que el nombre del punto master directamente para confirmar F !

 ...--C \ D--E--F <-- master (HEAD), branch 

Esta es una operación de avance rápido . Git no realiza ningún compromiso nuevo, solo mueve el master tags hacia adelante, lo más rápido posible, a lo largo de la cadena de confirmaciones hasta la nueva sugerencia de bifurcación. Ahora master y branch apuntan a la misma confirmación y podemos descartar el problema en el gráfico:

 ...--C--D--E--F <-- master (HEAD), branch 

Dado que git log va a mirar el gráfico, comenzando por la sugerencia de twig actual ( HEAD ), y no hicimos ninguna nueva confirmación, simplemente veremos las confirmaciones comenzando desde la branch , que después de todo apunta al mismo cometer.

(La razón por la que estás viendo una fusión en la staging es que la punta de la staging en staging es en sí misma una fusión: una confirmación con dos padres. Esta es la forma nominal de "fusión", hecha porque Git tuvo que hacer una).

¿Qué pasa si no quieres un avance rápido?

Puede prohibir que git merge use una operación de avance rápido sin fusión: simplemente dele --no-ff como una opción de línea de command. Git verá que podría hacer un avance rápido, pero no lo hará: hará una nueva fusión de confirmación, con el autor y la timestamp y el post de logging. El código fuente del nuevo compromiso vendrá del otro extremo de la sucursal, y el nuevo compromiso será un compromiso de fusión, con dos padres:

 ...--C---------M <-- master (HEAD) \ / D--E--F <-- branch 

En general, algunas fusiones se deben hacer con --no-ff . Las fusiones de una twig de etapas en una twig maestra o de publicación tienden a ser así.

Para deshacer un avance rápido, comience por ver si puede averiguar dónde apuntó el nombre de la twig antes del avance rápido. En este caso:

 git reflog master 

le mostrará dónde apuntó el master largo del time, desde el momento en que las inputs de reflog master transfieren de todos modos. (Las inputs de Reflog permanecen, de forma pnetworkingeterminada, durante al less 30 días, y al less 90 días en algunos casos, por lo que debe tener suficiente time aquí).

Tenga en count que es importante que no se hayan agregado nuevas confirmaciones a la sucursal desde el avance rápido, ya que vamos a "rebobinar" las cosas. Digamos, por ejemplo, que el reflog muestra que el master apuntó a cometer C antes.

Ahora puedes usar git reset --hard <hash-id-of-C> para mover la twig actual ("leer HEAD para get el nombre de la twig" => master ) para que apunte al mismo lugar al que apuntaba . El " --hard aquí significa "borrar el contenido del tree de trabajo y el área de ensayo, reemplazándolos con el compromiso al que nos hemos mudado" (así que no hagas esto a less que todos estén limpios también). El resultado final es que vuelves a la situación como estaba antes de que hicieras la fusión de avance rápido en primer lugar. (Bueno, podemos esperar que la staging no haya adquirido nuevas confirmaciones, o si lo hizo, hay otras soluciones).

Ahora puedes ejecutar git merge --no-ff staging y forzar una combinación real, y get el gráfico que dibujamos para el caso --no-ff .

Vea lo que sucedió usando este command:

 git reflog 

Y todo el tree de historia:

 git log --graph