tutorial stackoverflow git git-rebase

stackoverflow - git rebase--continue



¿Qué hace exactamente(y por qué?) El "reajuste de Git-fusiones de reserva" de git (2)

Al igual que con una gase rebase normal, git con --preserve-merges primero identifica una lista de confirmaciones hechas en una parte del gráfico de confirmaciones, y luego reproduce esas confirmaciones sobre otra parte. Las diferencias con --preserve-merges preocupan qué confirmaciones se seleccionan para la repetición y cómo funciona la repetición para las confirmaciones de combinación.

Para ser más explícitos acerca de las principales diferencias entre la reorganización normal y la preservación de la fusión:

  • La reorganización de fusión de la fusión está dispuesta a reproducir (algunas) combinaciones de fusión, mientras que la rebase normal ignora completamente las combinaciones de fusión.
  • Debido a que está dispuesta a reproducir las combinaciones de fusión, la rebase de preservación de la fusión tiene que definir lo que significa reproducir una confirmación de combinación y lidiar con algunas arrugas adicionales
    • La parte más interesante, conceptualmente, es tal vez elegir lo que deberían ser los padres de la combinación del nuevo compromiso.
    • La reproducción de las confirmaciones de combinación también requiere la comprobación explícita de confirmaciones particulares ( git checkout <desired first parent> ), mientras que el rebase normal no tiene que preocuparse por eso.
  • La fusión de preservación de la fusión considera un conjunto menos profundo de confirmaciones para la reproducción:
    • En particular, solo considerará la repetición de las confirmaciones realizadas desde la (s) base (s) más reciente (es decir, la última vez que las dos ramas divergieron), mientras que la rebase normal podría reproducir las devoluciones desde la primera vez que las dos divergieron.
    • Para ser provisional y poco claro, creo que esto es, en última instancia, un medio para eliminar las "confirmaciones antiguas" que ya se han "incorporado" en una confirmación de fusión.

Primero trataré de describir "con suficiente exactitud" lo que hace la rebase --preserve-merges , y luego habrá algunos ejemplos. Por supuesto, uno puede comenzar con los ejemplos, si eso parece más útil.

El algoritmo en "breve"

Si realmente quieres meterte en la maleza, descarga la fuente de git y explora el archivo git-rebase--interactive.sh . (Rebase no es parte del núcleo C de Git, sino que está escrito en bash. Y, detrás de la escena, comparte código con "rebase interactivo").

Pero aquí esbozaré lo que creo que es la esencia de ello. Para reducir la cantidad de cosas en que pensar, me he tomado algunas libertades. (Por ejemplo, no trato de capturar con una precisión del 100% el orden exacto en el que se realizan los cálculos, e ignoro algunos temas que parecen menos centrales, por ejemplo, qué hacer con respecto a las confirmaciones que ya han sido elegidas entre las ramas).

Primero, tenga en cuenta que una rebase que no conserve la fusión es bastante simple. Es más o menos:

Find all commits on B but not on A ("git log A..B") Reset B to A ("git reset --hard A") Replay all those commits onto B one at a time in order.

Rebase --preserve-merges reservas en --preserve-merges son comparativamente complicadas. Aquí es tan simple como he podido hacerlo sin perder cosas que parecen bastante importantes:

Find the commits to replay: First find the merge-base(s) of A and B (i.e. the most recent common ancestor(s)) This (these) merge base(s) will serve as a root/boundary for the rebase. In particular, we''ll take its (their) descendants and replay them on top of new parents Now we can define C, the set of commits to replay. In particular, it''s those commits: 1) reachable from B but not A (as in a normal rebase), and ALSO 2) descendants of the merge base(s) If we ignore cherry-picks and other cleverness preserve-merges does, it''s more or less: git log A..B --not $(git merge-base --all A B) Replay the commits: Create a branch B_new, on which to replay our commits. Switch to B_new (i.e. "git checkout B_new") Proceeding parents-before-children (--topo-order), replay each commit c in C on top of B_new: If it''s a non-merge commit, cherry-pick as usual (i.e. "git cherry-pick c") Otherwise it''s a merge commit, and we''ll construct an "equivalent" merge commit c'': To create a merge commit, its parents must exist and we must know what they are. So first, figure out which parents to use for c'', by reference to the parents of c: For each parent p_i in parents_of(c): If p_i is one of the merge bases mentioned above: # p_i is one of the "boundary commits" that we no longer want to use as parents For the new commit''s ith parent (p_i''), use the HEAD of B_new. Else if p_i is one of the commits being rewritten (i.e. if p_i is in R): # Note: Because we''re moving parents-before-children, a rewritten version # of p_i must already exist. So reuse it: For the new commit''s ith parent (p_i''), use the rewritten version of p_i. Otherwise: # p_i is one of the commits that''s *not* slated for rewrite. So don''t rewrite it For the new commit''s ith parent (p_i''), use p_i, i.e. the old commit''s ith parent. Second, actually create the new commit c'': Go to p_1''. (i.e. "git checkout p_1''", p_1'' being the "first parent" we want for our new commit) Merge in the other parent(s): For a typical two-parent merge, it''s just "git merge p_2''". For an octopus merge, it''s "git merge p_2'' p_3'' p_4'' ...". Switch (i.e. "git reset") B_new to the current commit (i.e. HEAD), if it''s not already there Change the label B to apply to this new branch, rather than the old one. (i.e. "git reset --hard B")

Rebase con un argumento --onto C debe ser muy similar. En lugar de iniciar la reproducción de confirmación en la HEAD de B, en su lugar, inicie la reproducción de confirmación en la HEAD de C. (Y usa C_new en lugar de B_new.)

Ejemplo 1

Por ejemplo, tomar gráfico de compromiso

B---C <-- master / A-------D------E----m----H <-- topic / / F-------G

m es una combinación de compromiso con los padres E y G.

Supongamos que volvemos a basar el tema (H) en la parte superior del maestro (C) utilizando una rebase normal que no preserva la fusión. (Por ejemplo, tema de pago; maestro de rebase ). En ese caso, git seleccionaría las siguientes confirmaciones para la reproducción:

  • recoger D
  • recoger E
  • recoger F
  • recoger G
  • recoger H

y luego actualice el gráfico de confirmación de la siguiente manera:

B---C <-- master / / A D''---E''---F''---G''---H'' <-- topic

(D ''es el equivalente reproducido de D, etc.)

Tenga en cuenta que la combinación de compromiso m no se selecciona para la reproducción.

Si en su lugar --preserve-merges una --preserve-merges merges de H encima de C. (Por ejemplo, tema de pago; rebase - master de preserve-merges ). En este nuevo caso, git seleccionará las siguientes confirmaciones para la reproducción:

  • recoger D
  • recoger E
  • Elija F (en D ''en la rama'' subtema '')
  • Elija G (en F ''en la rama'' subtema '')
  • elige Combinar rama ''subtema'' en tema
  • recoger H

Ahora m fue elegido para la repetición. También tenga en cuenta que los padres de fusión E y G se seleccionaron para su inclusión antes de combinar m.

Aquí está el gráfico de confirmación resultante:

B---C <-- master / / A D''-----E''----m''----H'' <-- topic / / F''-------G''

Nuevamente, D ''es una versión seleccionada de Cherry (es decir, recreada) de D. Igual para E'', etc. Se han reproducido todas las confirmaciones no en el master. Tanto E como G (los padres de fusión de m) se han recreado como E ''y G'' para que sirvan como padres de m ''(después de la rebase, la historia del árbol sigue siendo la misma).

Ejemplo 2

A diferencia de la rebase normal, la fusión de preservación de mezcla puede crear varios hijos de la cabecera ascendente.

Por ejemplo, considere:

B---C <-- master / A-------D------E---m----H <-- topic / | ------- F-----G--/

Si cambiamos H (tema) en la parte superior de C (maestro), entonces las confirmaciones elegidas para rebase son:

  • recoger D
  • recoger E
  • recoger F
  • recoger G
  • recoger m
  • recoger H

Y el resultado es así:

B---C <-- master / | / A | D''----E''---m''----H'' <-- topic / | F''----G''---/

Ejemplo 3

En los ejemplos anteriores, tanto el compromiso de fusión como sus dos padres son compromisos de repetición, en lugar de los padres originales que tiene el compromiso de fusión original. Sin embargo, en otras bases, una confirmación de combinación repetida puede terminar con los padres que ya estaban en el gráfico de confirmación antes de la combinación.

Por ejemplo, considere:

B--C---D <-- master / / A---E--m------F <-- topic

Si cambiamos el tema al maestro (conservando las fusiones), entonces las confirmaciones para volver a jugar serán

  • pick merge commit m
  • recoger F

El gráfico de confirmación reescrito se verá así:

B--C--D <-- master / / A-----E---m''--F''; <-- topic

Aquí, la repetición de la combinación de m m obtiene los padres que preexistían en el gráfico de comillas, a saber, D (la CABEZA del maestro) y E (uno de los padres de la mega cometa de compilación original).

Ejemplo 4

La fusión de preservación de la fusión puede confundirse en ciertos casos de "confirmación vacía". Al menos esto es cierto solo en algunas versiones anteriores de git (por ejemplo, 1.7.8.)

Toma este gráfico de compromiso:

A--------B-----C-----m2---D <-- master / / / E--- F--/--G----/ / / ---m1--H <--topic

Tenga en cuenta que tanto commit m1 como m2 deberían haber incorporado todos los cambios de B y F.

Si intentamos hacer git rebase --preserve-merges of H (topic) en D (master), se seleccionan las siguientes confirmaciones:

  • recoger m1
  • recoger H

Tenga en cuenta que los cambios (B, F) unidos en m1 ya deberían estar incorporados en D. (Esos cambios ya deberían estar incorporados en m2, porque m2 combina los hijos de B y F.) Por lo tanto, conceptualmente, repitiendo m1 encima de D probablemente debería ser un no-op o crear un compromiso vacío (es decir, uno donde la diferencia entre revisiones sucesivas está vacía).

Sin embargo, en cambio, git puede rechazar el intento de reproducir m1 sobre D. Puedes obtener un error como este:

error: Commit 90caf85 is a merge but no -m option was given. fatal: cherry-pick failed

Parece que uno olvidó pasar una bandera a git, pero el problema subyacente es que a git no le gusta crear confirmaciones vacías.

La documentación de Git para el comando rebase es bastante breve:

--preserve-merges Instead of ignoring merges, try to recreate them. This uses the --interactive machinery internally, but combining it with the --interactive option explicitly is generally not a good idea unless you know what you are doing (see BUGS below).

Entonces, ¿qué sucede realmente cuando usas --preserve-merges ? ¿En qué se diferencia del comportamiento predeterminado (sin esa bandera)? ¿Qué significa "recrear" una fusión, etc.?


Git 2.18 (Q2 2018) mejorará considerablemente la opción --preserve-merge al agregar una nueva opción.

" git rebase " git rebase " --rebase-merges " para trasplantar la topología completa del gráfico de compromiso en otro lugar .

Consulte commit 25cff9f , commit 7543f6f , commit 1131ec9 , commit 7ccdf65 , commit 537e7d6 , commit a9be29c , commit 8f6aed7 , commit 1644c73 , commit d1e8b01pacido de la prenda de la mesa de la mesa de la mesa por favor . Por Johannes Schindelin ( dscho ) .
Ver commit f431d73 (25 de abril de 2018) por Stefan Beller ( stefanbeller ) .
Ver commit 2429335 (25 de abril de 2018) por Phillip Wood ( phillipwood ) .
(Combinado por Junio ​​C Hamano - gitster - in commit 2c18e6a , 23 de mayo de 2018)

pull : acepta --rebase=merges para recrear la topología de la rama

Similar al modo preserve simplemente pasando la opción --preserve-merges rebase comando rebase , el modo merges simplemente pasa la opción --rebase-merges .

Esto permitirá a los usuarios reorganizar convenientemente las topologías de compromiso no triviales al extraer nuevos compromisos, sin aplanarlos.

git rebase página de git rebase ahora tiene una sección completa dedicada a rebasar el historial con fusiones .

Extraer:

Hay razones legítimas por las que un desarrollador puede querer volver a crear confirmaciones de combinación: para mantener la estructura de la sucursal (o "topología de confirmación") cuando se trabaja en múltiples sucursales relacionadas entre sí.

En el siguiente ejemplo, el desarrollador trabaja en una rama de tema que refacta la forma en que se definen los botones, y en otra rama de tema que usa esa refactorización para implementar un botón de "Informar de un error".
La salida del git log --graph --format=%s -5 de git log --graph --format=%s -5 puede verse así:

* Merge branch ''report-a-bug'' |/ | * Add the feedback button * | Merge branch ''refactor-button'' |/ / | |/ | * Use the Button class for all buttons | * Extract a generic Button class from the DownloadButton one

Es posible que el desarrollador desee volver a asignar esos compromisos a un master más reciente mientras mantiene la topología de la rama, por ejemplo, cuando se espera que la primera rama del tema se integre en el master mucho antes que la segunda, por ejemplo, para resolver los conflictos de combinación con los cambios en el DownloadButton clase que lo hizo en master .

Esta rebase se puede realizar utilizando la opción --rebase-merges .

Ver commit 1644c73 para un pequeño ejemplo:

rebase-helper --make-script : introduce una bandera para reagrupar las fusiones

El secuenciador acaba de aprender nuevos comandos destinados a recrear la estructura de la rama ( similar en espíritu a --preserve-merges , pero con un diseño sustancialmente menos roto ).

rebase--helper el rebase--helper genere listas de rebase--helper haciendo uso de estos comandos, activados por la nueva opción --rebase-merges .
Para una topología de compromiso como esta (donde el HEAD apunta a C):

- A - B - C (HEAD) / / D

La lista de tareas generadas se vería así:

# branch D pick 0123 A label branch-point pick 1234 D label D reset branch-point pick 2345 B merge -C 3456 D # C

¿Cuál es la diferencia con --preserve-merge ?
Commit 8f6aed7 explica:

Érase una vez, este desarrollador pensó aquí: ¿no sería agradable si, por ejemplo, los parches de Git para Windows en la parte superior del núcleo, Git pudiera representarse como un matorral de sucursales, y se volviera a basar en la parte superior del núcleo de Git para ¿Mantener un conjunto de parches cherry-pick''able de la serie?

El intento original de responder esto fue: git rebase --preserve-merges .

Sin embargo, ese experimento nunca fue pensado como una opción interactiva, y solo se git rebase --interactive en git rebase --interactive porque la implementación de ese comando ya parecía muy, muy familiar: fue diseñada por la misma persona que diseñó --preserve-merges : Atentamente.

Y por "tuyo de verdad", el autor se refiere a sí mismo: Johannes Schindelin ( dscho ) , que es la razón principal (con algunos otros héroes: Hannes, Steffen, Sebastian, ...) que tenemos Git para Windows (incluso aunque en el pasado - 2009 - eso no fue fácil ).
¡Ahora trabaja en Microsoft, ya que Microsoft administra el repositorio de Git más grande del planeta !

Puedes ver a Johannes hablar en este video de Git Merge 2018 en abril de 2018.

Algún tiempo después, otro desarrollador (te estoy mirando, Andreas! ;-)) decidió que sería una buena idea permitir que se --preserve-merges --interactive con --interactive (con advertencias!) Y el Git mantenedor (bueno, el mantenedor interino de Git durante la ausencia de Junio, eso es) estuvo de acuerdo, y fue entonces cuando el glamour del diseño de --preserve-merges comenzó a desmoronarse con bastante rapidez y sin glamour.

Aquí Jonathan está hablando de Andreas Schwab de Suse.
Puedes ver algunas de sus discusiones en 2012 .

¿La razón? En el modo --preserve-merges , los padres de una confirmación de fusión (o, para el caso, de cualquier confirmación) no se indicaron explícitamente, sino que estaban implícitos por el nombre de confirmación que se pasó al comando pick .

Esto hizo imposible, por ejemplo, reordenar los compromisos .
Por no hablar de mover compromisos entre ramas o, deidad prohibida, dividir ramas de temas en dos.

Lamentablemente, estas deficiencias también impidieron que ese modo (cuyo propósito original era satisfacer las necesidades de Git para Windows, con la esperanza adicional de que también podría ser útil para otros), atendiera las necesidades de Git para Windows.

Cinco años más tarde, cuando se volvió realmente insostenible tener una serie de parches de Hodge-podge grandes y difíciles de manejar en parches relacionados, en parte no relacionados, en Git para Windows, que se rebasaban de vez en cuando en las etiquetas del núcleo de Git (ganando la ira que no merecía el desarrollador de la desafortunada serie git-remote-hg que primero abandonó el enfoque competitivo de Git para Windows, que luego se abandonó sin un mantenedor más tarde) fue realmente insostenible, nacieron las " tijeras de jardín Git ": un guión, con un piggy backing en la parte superior de la rebase interactiva, que primero determinaría la topología de la rama de los parches a rebasar, crearía una lista de pseudo tareas para su posterior edición, transformará el resultado en una lista de tareas reales (haciendo un uso intensivo del comando exec para "implementar" lo que falta) comandos de lista de tareas pendientes) y, finalmente, volver a crear la serie de parches en la parte superior de la nueva base de confirmación.

(En el parche de confirmación 9055e40 se hace referencia al script Git garden shears)

Eso fue en 2013.
Y se demoraron aproximadamente tres semanas en idear el diseño e implementarlo como un script fuera de árbol. No hace falta decir que la implementación necesitó bastantes años para estabilizarse, mientras que el diseño mismo demostró ser sólido.

Con este parche, la bondad de las tijeras de jardín Git viene a git rebase -i .
Pasar la opción --rebase-merges generará una lista de --rebase-merges pendientes que puede entenderse fácilmente, y donde es obvio cómo reordenar las confirmaciones .
Se pueden introducir nuevas ramas insertando comandos de label y llamando a merge <label> .
Y una vez que este modo se haya vuelto estable y aceptado universalmente, podemos desaprobar el error de diseño que fue --preserve-merges .

Git 2.19 (Q3 2018) mejora la nueva opción --rebase-merges haciendo que funcione con --exec .

La opción " --exec " para " git rebase --rebase-merges " colocó los comandos exec en lugares incorrectos, lo que se ha corregido.

Consulte commit 1ace63b (09 de agosto de 2018) y commit f0880f7 (06 de agosto de 2018) de Johannes Schindelin ( dscho ) .
(Combinado por Junio ​​C Hamano - gitster - in commit 750eb11 , 20 de agosto de 2018)

rebase --exec : haz que funcione con --rebase-merges

La idea de --exec es agregar una llamada exec después de cada pick .

Desde la introducción de la fixup! / s quash! confirma, esta idea se extendió para aplicarse a "selección, posiblemente seguida de una cadena de reparación / squash", es decir, no se insertaría un exec entre una pick y cualquiera de sus líneas de fixup o de squash correspondientes.

La implementación actual utiliza un truco sucio para lograrlo: asume que solo hay comandos pick / fixup / squash, y luego inserta las líneas exec antes de cualquier pick excepto la primera, y agrega una final.

Con las listas de git rebase --rebase-merges generadas por git rebase --rebase-merges , esta sencilla implementación muestra sus problemas: produce exactamente lo incorrecto cuando hay comandos de label , reset y merge .

Cambiemos la implementación para hacer exactamente lo que queremos: buscar líneas de pick , omitir cualquier reparación / cadenas de squash, y luego insertar la línea exec . Hacer espuma, enjuagar, repetir.

Nota: nos esforzamos por insertar antes de las líneas de comentarios siempre que sea posible, ya que las confirmaciones vacías están representadas por líneas de selección comentadas (y queremos insertar una línea exec de selección anterior antes de dicha línea, no después).

Mientras lo hace, también agregue líneas exec después de los comandos de merge , ya que son similares en espíritu a los comandos de pick : agregan nuevos compromisos.