Git se duplican en la misma rama después de hacer un rebase


Entiendo el escenario presentado en Pro Git sobre los riesgos de git rebase. El autor básicamente te dice cómo evitar confirmaciones duplicadas:

No rebase las confirmaciones que ha enviado a un repositorio público.

Voy a contarte mi situación particular porque creo que no encaja exactamente en el escenario Git Pro y todavía termino con confirmaciones duplicadas.

Digamos que tengo dos ramas remotas con su local contrapartes:

origin/master    origin/dev
|                |
master           dev

Las cuatro ramas contienen los mismos commits y voy a comenzar el desarrollo en dev:

origin/master : C1 C2 C3 C4
master        : C1 C2 C3 C4

origin/dev    : C1 C2 C3 C4
dev           : C1 C2 C3 C4

Después de un par de confirmaciones, envio los cambios a origin/dev:

origin/master : C1 C2 C3 C4
master        : C1 C2 C3 C4

origin/dev    : C1 C2 C3 C4 C5 C6  # (2) git push
dev           : C1 C2 C3 C4 C5 C6  # (1) git checkout dev, git commit

Tengo que volver a master para hacer una solución rápida:

origin/master : C1 C2 C3 C4 C7  # (2) git push
master        : C1 C2 C3 C4 C7  # (1) git checkout master, git commit

origin/dev    : C1 C2 C3 C4 C5 C6
dev           : C1 C2 C3 C4 C5 C6

Y de vuelta a dev rebase los cambios para incluir la solución rápida en mi desarrollo real:

origin/master : C1 C2 C3 C4 C7
master        : C1 C2 C3 C4 C7

origin/dev    : C1 C2 C3 C4 C5 C6
dev           : C1 C2 C3 C4 C7 C5' C6'  # git checkout dev, git rebase master

Si muestro el historial de confirmaciones con GitX / gitk me doy cuenta de que origin/dev ahora contiene dos idénticos commits C5' y C6' que son diferentes a Git. Ahora si presiono los cambios a origin/dev este es el resultado:

origin/master : C1 C2 C3 C4 C7
master        : C1 C2 C3 C4 C7

origin/dev    : C1 C2 C3 C4 C5 C6 C7 C5' C6'  # git push
dev           : C1 C2 C3 C4 C7 C5' C6'

Tal vez no entiendo completamente la explicación en Pro Git, así que me gustaría saber dos cosas:

  1. ¿Por qué Git duplica estas confirmaciones al rebasarlas? ¿Hay una razón particular para hacer eso en lugar de simplemente aplicar C5 y C6 después de C7?
  2. ¿Cómo puedo evitar eso? ¿Sería prudente hacerlo?
Author: Whymarrh, 2012-02-13

4 answers

No deberías usar rebase aquí, una simple fusión será suficiente. El libro Git Pro que vinculaste básicamente explica esta situación exacta. El funcionamiento interno puede ser ligeramente diferente, pero así es como lo visualizo:

  • C5 y C6 se retiran temporalmente de dev
  • C7 se aplica a dev
  • C5 y C6 se reproducen encima de C7, creando nuevos diffs y, por lo tanto, nuevas confirmaciones

Entonces, en tu dev rama, C5 y C6 efectivamente ya no existen: ahora son C5' y C6'. Cuando envías a origin/dev, git ve C5' y C6' como nuevas confirmaciones y las añade al final del historial. De hecho, si observas las diferencias entre C5 y C5' en origin/dev, notarás que aunque el contenido es el mismo, los números de línea son probablemente diferentes which lo que hace que el hash de la confirmación sea diferente.

Repetiré la regla Pro Git: nunca rebase confirmaciones que hayan existido en cualquier lugar menos en su repositorio local. Usa combinar en su lugar.

 61
Author: Justin ᚅᚔᚈᚄᚒᚔ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-02-13 17:03:38

Respuesta corta

Se omite el hecho de que se ejecutó git push, tengo el siguiente error, y luego se procedió a ejecutar git pull:

To [email protected]:username/test1.git
 ! [rejected]        dev -> dev (non-fast-forward)
error: failed to push some refs to '[email protected]:username/test1.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

A pesar de que Git intenta ser útil, su consejo 'git pull' probablemente no es lo que quieres hacer.

Si usted es:

  • Trabajando en una" rama de características "o" rama de desarrollador " solo, entonces puede ejecutar git push --force para actualizar el control remoto con sus confirmaciones post-rebase ( según user4405677's respuesta).
  • Trabajando en una rama con varios desarrolladores al mismo tiempo, entonces probablemente no debería usar git rebase en primer lugar. Para actualizar dev con los cambios de master, debe, en lugar de ejecutar git rebase master dev, ejecutar git merge master mientras que en dev (según la respuesta de Justin ).

Una explicación un poco más larga

Cada hash de confirmación en Git se basa en una serie de factores, uno de los cuales es el hash de la confirmación que viene antes se.

Si reordenas commits cambiarás los hashes de commits; rebasing (cuando haga algo) cambiará los hashes de commits. Con eso, el resultado de ejecutar git rebase master dev, donde dev no está sincronizado con master, creará nuevas confirmaciones (y por lo tanto hashes) con el mismo contenido que las de dev pero con las confirmaciones de master insertadas antes de ellas.

Puedes terminar en una situación como esta de múltiples maneras. Se me ocurren dos maneras:

  • Usted podría haber commits en master que desea basar su dev trabajo en
  • Puede tener confirmaciones en dev que ya se han enviado a un control remoto, que luego procede a cambiar (reformular mensajes de confirmación, reordenar confirmaciones, squash confirmaciones, etc.).)

Vamos a entender mejor lo que pasó - aquí hay un ejemplo:{[50]]}

Tienes un repositorio:

2a2e220 (HEAD, master) C5
ab1bda4 C4
3cb46a9 C3
85f59ab C2
4516164 C1
0e783a3 C0

Conjunto inicial de confirmaciones lineales en un repositorio

Luego se procede a cambiar las confirmaciones.

git rebase --interactive HEAD~3 # Three commits before where HEAD is pointing

(Aquí es donde tendrás que tomar mi palabra para ello: hay varias formas de cambiar las confirmaciones en Git. En este ejemplo cambié la hora de C3, pero estás insertando nuevas confirmaciones, cambiando los mensajes de confirmación, reordenando confirmaciones, aplastando confirmaciones juntas, etc.)

ba7688a (HEAD, master) C5
44085d5 C4
961390d C3
85f59ab C2
4516164 C1
0e783a3 C0

Los mismos commits con nuevos hashes

Aquí es donde es importante notar que los hashes de confirmación son diferentes. Este es el comportamiento esperado ya que ha cambiado algo (cualquier cosa) sobre ellos. Esto está bien, PERO:

Un registro gráfico que muestra que master no está sincronizado con el control remoto

Tratando de empujar a will le mostrará un error (y le indicará que debe ejecutar git pull).

$ git push origin master
To [email protected]:username/test1.git
 ! [rejected]        master -> master (non-fast-forward)
error: failed to push some refs to '[email protected]:username/test1.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

Si ejecutamos git pull, vemos este registro:

7df65f2 (HEAD, master) Merge branch 'master' of bitbucket.org:username/test1
ba7688a C5
44085d5 C4
961390d C3
2a2e220 (origin/master) C5
85f59ab C2
ab1bda4 C4
4516164 C1
3cb46a9 C3
0e783a3 C0

O, mostrado de otra manera:

Un registro gráfico que muestra una confirmación de fusión

Y ahora tenemos confirmaciones duplicadas localmente. Si tuviéramos que ejecutar git push los enviaríamos al servidor.

Para evitar llegar a esta etapa, podríamos haber corrido git push --force (donde en cambio corrimos git pull). Esto habría enviado nuestras confirmaciones con los nuevos hashes al servidor sin problemas. Para solucionar el problema en esta etapa, podemos reiniciar de nuevo a antes de ejecutar git pull:

Mira el reflog (git reflog) para ver cuál era el hash de confirmación antes de que corriéramos git pull.

070e71d HEAD@{1}: pull: Merge made by the 'recursive' strategy.
ba7688a HEAD@{2}: rebase -i (finish): returning to refs/heads/master
ba7688a HEAD@{3}: rebase -i (pick): C5
44085d5 HEAD@{4}: rebase -i (pick): C4
961390d HEAD@{5}: commit (amend): C3
3cb46a9 HEAD@{6}: cherry-pick: fast-forward
85f59ab HEAD@{7}: rebase -i (start): checkout HEAD~~~
2a2e220 HEAD@{8}: rebase -i (finish): returning to refs/heads/master
2a2e220 HEAD@{9}: rebase -i (start): checkout refs/remotes/origin/master
2a2e220 HEAD@{10}: commit: C5
ab1bda4 HEAD@{11}: commit: C4
3cb46a9 HEAD@{12}: commit: C3
85f59ab HEAD@{13}: commit: C2
4516164 HEAD@{14}: commit: C1
0e783a3 HEAD@{15}: commit (initial): C0

Arriba vemos que ba7688a era el commit en el que estábamos antes de ejecutar git pull. Con ese hash de confirmación en la mano podemos volver a eso (git reset --hard ba7688a) y luego ejecutar git push --force.

Y hemos terminado.

Pero espera, continué basando el trabajo en las confirmaciones duplicadas{[48]]}

Si de alguna manera no se dio cuenta de que las confirmaciones fueron duplicadas y procedieron a continuar trabajando encima de confirmaciones duplicadas, realmente has hecho un desastre para ti mismo. El tamaño del lío es proporcional al número de confirmaciones que tiene encima de los duplicados.

Cómo se ve esto:

3b959b4 (HEAD, master) C10
8f84379 C9
0110e93 C8
6c4a525 C7
630e7b4 C6
070e71d (origin/master) Merge branch 'master' of bitbucket.org:username/test1
ba7688a C5
44085d5 C4
961390d C3
2a2e220 C5
85f59ab C2
ab1bda4 C4
4516164 C1
3cb46a9 C3
0e783a3 C0

Git log mostrando commits lineales encima de commits duplicados

O, mostrado de otra manera:

Un gráfico de registro que muestra confirmaciones lineales encima de confirmaciones duplicadas

En este escenario queremos eliminar las confirmaciones duplicadas, pero mantener las confirmaciones que hemos basado en ellas-queremos mantener C6 a C10. Como con la mayoría de las cosas, hay un número de maneras de hacer esto:

O bien:

  • Crear una nueva rama en la última confirmación duplicada1, cherry-pick cada commit (C6 a C10 inclusive) en esa nueva rama, y tratar esa nueva rama como canónica.
  • Ejecute git rebase --interactive $commit, donde $commit es la confirmación anterior a ambas confirmaciones duplicadas2. Aquí podemos borrar directamente las líneas para el duplicados.

1 No importa cuál de los dos elija, ya sea ba7688a o 2a2e220 funciona bien.

2 En el ejemplo sería 85f59ab.

TL; DR{[48]]}

Conjunto advice.pushNonFastForward a false:

git config --global advice.pushNonFastForward false
 63
Author: Whymarrh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-12-18 15:23:13

Creo que te saltaste un detalle importante al describir tus pasos. Más específicamente, su último paso, git push en dev, realmente le habría dado un error, ya que normalmente no puede empujar cambios no fastforward.

Así que lo hizo git pull antes del último push, lo que resultó en una confirmación de fusión con C6 y C6' como padres, por lo que ambos permanecerán listados en el registro. Un formato de registro más bonito podría haber hecho más obvio que son ramas combinadas de confirmaciones duplicadas.

O usted hecho un git pull --rebase (o sin --rebase explícito si está implícito por su configuración) en su lugar, que sacó el C5 original y C6 de nuevo en su desarrollo local (y más adelante re-re-basado los siguientes a nuevos hashes, C7' C5" C6").

Una forma de salir de esto podría haber sido git push -f forzar el empuje cuando dio el error y borrar C5 C6 de origin, pero si alguien más también los tenía tirados antes de que los borraras, estarías en muchos más problemas... básicamente todo el mundo que tiene C5 C6 tendría que hacer pasos especiales para deshacerse de ellos. Que es exactamente por lo que dicen que nunca debe rebase nada que ya está publicado. Sin embargo, todavía es factible si dicho "publicar" está dentro de un equipo pequeño.

 9
Author: user4405677,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-08 03:23:10

Descubrí que en mi caso, este problema es la consecuencia de un problema de configuración de Git. (Que implica tirar y fusionar)

Descripción del problema:

Sympthoms: Commits duplicados en la rama hija después de rebase, implicando numerosas fusiones durante y después de rebase.

Flujo de trabajo: Estos son los pasos del flujo de trabajo que estaba realizando:

  • Trabajar en la " Features-branch "(hija de"Develop-branch")
  • Confirmar y Empujar cambios en "Features-branch"
  • Comprueba "Develop-branch" (rama madre de Características) y trabaja con ella.
  • Commit y push cambios en "Develop-branch"
  • Checkout "Features-branch" y extrae los cambios del repositorio (En caso de que alguien más haya comprometido el trabajo)
  • Rebase " Features-branch "en"Develop-branch"
  • Fuerza de empuje de los cambios en "Feature-branch"

Como conséquencias de este flujo de trabajo, la duplicación de todas las confirmaciones de "Feature-branch" desde rebase... :-(

El problema se debió a la atracción de cambios de rama hijo antes de rebase. La configuración predeterminada de Git pull es "merge". Esto está cambiando los índices de confirmaciones realizadas en la rama secundaria.

La solución: en el archivo de configuración de Git, configure pull para que funcione en modo rebase:

...
[pull]
    rebase = preserve
...

Espero que pueda ayudar JN Grx

 1
Author: JN Gerbaux,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-05-26 13:14:24