¿Cómo reconoce git-rebase los commit "aliased"?

Estoy tratando de entender mejor la magia detrás de git-rebase. Hoy estuve gratamente sorprendido por el siguiente comportamiento, que no esperaba.

TLDR: volví a configurar una twig compartida, lo que hace que cambien todos los commit sha1s. A pesar de esto, una twig derivada fue capaz de identificar con precisión que sus confirmaciones originales se "aliasan" en nuevas confirmaciones con diferentes sha1s. La rebase no creó ningún desorder en absoluto.

Detalles

Toma una twig principal: M1

Bifurque en branch-X, con algunas confirmaciones adicionales agregadas: M1-A1-B1-C1 . Anote la salida de git-log.

Derive twig-X en twig-Y, con una confirmación adicional agregada: M1-A1-B1-C1-D1 . Anote la salida de git-log.

Agregue un nuevo compromiso a la punta de la twig principal: M1-M2

Rebase branch-X en el master actualizado: M1-M2-A2-B2-C2 . Tenga en count que A2-B2-C2, todos tienen el mismo post, contenido y date de autor que A1-B1-C1. Sin embargo, tienen valores sha1 completamente diferentes, así como dates de compromiso. De acuerdo con este informe , la razón por la que SHA1 es diferente es porque el padre del compromiso ha cambiado.

Rebase branch-Y en la twig-X actualizada. Resultado: M1-M2-A2-B2-C2-D2 .

En particular, solo se aplica el compromiso D1 (y se convierte en D2). Los compromisos A1-B1-C1 en la twig Y son completamente ignorados por git-rebase. Puedes ver esto en los loggings de salida.

Esto es maravilloso, pero ¿cómo sabe git-rebase ignorar A1-B1-C1? ¿Cómo sabe git-rebase que A2-B2-C2 es lo mismo que A1-B1-C1, y por lo tanto, se puede ignorar de forma segura? Siempre he supuesto que git hace un seguimiento de las confirmaciones utilizando el identificador sha1, pero a pesar de que las confirmaciones anteriores tienen diferentes sha1s, git todavía sabe de alguna manera que están unidas. ¿Como hace eso? Dado el comportamiento anterior, ¿cuándo es realmente peligroso volver a establecer una base de una twig compartida ?

Internamente, git rebase enumera los commits que deberían ser actualizados, y luego calcula un id de parche para estos commits. A diferencia de la identificación de confirmación, solo evalúa el contenido del parche , no el contenido del tree y los objects de confirmación. Entonces, A1 y A2, aunque tienen identificadores diferentes, tienen el mismo ID de parche. Entonces, git rebase salta parches cuyo ID de parche ya está presente.

Para get más información, busque patch-id aquí: https://git-scm.com/book/es/v2/Git-Branching-Rebasing


Sección relevante de arriba (falta diagtwigs):

Si alguien de la fuerza de su equipo impulsa cambios que sobrescriben el trabajo en el que ha basado su trabajo, su desafío es descubrir qué es lo suyo y qué han reescrito.

Resulta que, además de la sum de comprobación SHA-1, Git también calcula una sum de comprobación que se basa solo en el parche introducido con la confirmación. Esto se llama "id de parche".

Si extrae el trabajo que se reescribió y lo vuelve a establecer en la base de los nuevos compromisos de su compañero, Git a menudo puede descubrir con éxito lo que es exclusivamente suyo y aplicarlos nuevamente en la parte superior de la nueva twig.

Por ejemplo, en el escenario anterior, si en vez de hacer una fusión cuando estamos en Someone empuja las confirmaciones rebasadas, abandonando las confirmaciones en las que ha basado su trabajo, ejecutamos git rebase teamone / master, Git hará lo siguiente:

  • Determine qué trabajo es exclusivo de nuestra sucursal (C2, C3, C4, C6, C7)
  • Determine cuáles no son confusiones de fusión (C2, C3, C4)
  • Determine cuáles no se han reescrito en la twig de destino (solo C2 y C3, ya que C4 es el mismo parche que C4 ')
  • Aplica esos commits a la parte superior de teamone / master

Esto solo funciona si C4 y C4 'que hizo su compañero son casi exactamente el mismo parche. De lo contrario, la rebase no podrá decir que es un duplicado y agregará otro parche similar al C4 (que probablemente no se aplicará limpiamente, ya que los cambios ya estarían al less allí).

De hecho, hay varios methods diferentes de git rebase para eliminar copys networkingundantes.

Patch-ID

El primero, y el más seguro, es mediante el mismo método que usa git cherry para identificar las confirmaciones seleccionadas. Sin embargo, si lees la documentation vinculada, la única pista sobre cómo funciona esto es al final, donde la página del manual se vincula con la documentation de git patch-id . De git patch-id .

Leer esta segunda página de manual le dará una buena idea de cómo se establece la "equivalencia de compromiso": Git simplemente computa un git patch-id en la salida de, por ejemplo, git show de cualquier commit ordinario (sin fusión). En realidad, ejecuta git diff-tree lugar de git show orientado al usuario, pero el efecto es casi el mismo.

Pero todavía falta algo, y está muy mal documentado en git rebase o git cherry . Está documentado algo mejor en git rev-list , que es una página de manual algo desalentadora. Hay dos keys: la noción de diferencia simétrica , usando la syntax de tres puntos descrita en la documentation de gitrevisions , y las --left-right y --cherry-mark para git rev-list .

Una vez que comprenda cómo tomamos un DAGlet como por ejemplo:

 ...--o--o--L1--L2--L3 <-- left \ R1--R2--R3 <-- right 

y use left...right para seleccionar los tres compromisos L y R , la --left-right tiene mucho sentido: marca qué confirmaciones en el text salen del lado izquierdo de los tres puntos, y cuáles son el lado derecho se compromete

El segundo paso aquí es descubrir que git rev-list puede calcular el ID del parche para cada confirmación en cada "lado". Git puede comparar todas las ID de parche del lado izquierdo con todas las ID de parche del lado derecho. La opción --cherry-mark , y sus opciones relacionadas, las utilizan para marcar commits equivalentes o inequivalentes, o para omitir commits equivalentes.

La última pieza de este rompecabezas en particular es que git rebase no git rebase , como dice la documentation, <upstream>..HEAD . En cambio, usa el equivalente de git rev-list --cherry-pick --right-only --no-merges <upstream>...HEAD para get el set de confirmaciones para copyr. (A estas opciones también debemos agregar --topo-order y --reverse .)

Punto de horquilla

El segundo método que usa git rebase para elide commits es el --fork-point ahora integrado en git merge-base . Este mecanismo es particularmente difícil de describir y, además, se basa en las inputs de reflog para conocer las confirmaciones que se encontraban en una twig en el pasado, pero que ya no existen. También da un resultado indeseable a veces, y no es útil en este tipo particular de rebase.

Principalmente lo menciono aquí porque alguien que busca razones por las cuales git rebase algunos commit (s) podría haber encontrado un caso donde el mecanismo de la bifurcación ha fallado. Ver, por ejemplo:

  • ¿Por qué git rebase descarta mis commits?
  • ¿Por qué "rebase –onto ABC" es diferente a "rebase ABC"?
  • git pull –rebase pierde commits después del empuje git de compañero de trabajo –force
  • Git rebase – commit select en modo de punta de horquilla

Las confirmaciones de Y de twig están vacías en la segunda rebase

Realmente no hay magia escondida en el interior. Rebase busca el historial común y lo ignora (solo cometer M1 en este caso). Separa el historial de la twig rebasada (Y) e intenta seleccionarlo en la nueva base (branch-X).

El método de recolección deriva un parche de un compromiso previo y elegido. Como está vacío para A1, B1 y C1, simplemente omite estos commits. Solo se selecciona D1 y, por lo tanto, se crea un D2 (con SHA nuevo como el enlace principal en los cambios de encabezado, como se indica correctamente en la pregunta).