¿Cómo se fusiona git después del trabajo de selección de cereza?

190

Imaginemos que tenemos una masterrama.

Entonces creamos un newbranch

git checkout -b newbranch

y hacer dos nuevos compromisos para newbranch: commit1 y commit2

Luego cambiamos a maestro y hacemos cherry-pick

git checkout master
git cherry-pick hash_of_commit1

Mirando hacia adentro gitk, vemos que commit1 y su versión seleccionada tienen diferentes hashes, por lo que técnicamente son dos commits diferentes.

Finalmente nos fusionamos newbranchen master:

git merge newbranch

y vea que estos dos commits con diferentes hashes se fusionaron sin problemas, aunque implican que los mismos cambios deberían aplicarse dos veces, por lo que uno de ellos debería fallar.

¿Realmente git hace un análisis inteligente del contenido de commit mientras se fusiona y decide que los cambios no deberían aplicarse dos veces o que estos commits se marcan internamente como vinculados?

Pablo
fuente

Respuestas:

131

Respuesta corta

No te preocupes, Git lo manejará.

Respuesta larga

A diferencia de, por ejemplo, SVN 1 , Git no almacena confirmaciones en formato delta, sino que se basa en instantáneas 2,3 . Si bien SVN intentaría ingenuamente aplicar cada confirmación combinada como un parche (y falla, por la razón exacta que describió), Git generalmente puede manejar este escenario.

Al fusionarse, Git intentará combinar las instantáneas de ambas confirmaciones HEAD en una nueva instantánea. Si una parte del código o un archivo es idéntica en ambas instantáneas (es decir, porque una confirmación ya fue seleccionada), Git no lo tocará.

Fuentes

1 Skip-Deltas en Subversion
2 Conceptos básicos de Git
3 El modelo de objetos Git

Helmbert
fuente
40
En realidad, diría que debería preocuparse por fusionarse y "git lo manejará" no es una buena regla general.
cregox
44
De hecho, la fusión PUEDE resultar en contenido duplicado en algunos casos. Git lo maneja a veces, pero a veces no lo hace.
donquixote
Esto está terriblemente mal. Got prácticamente almacena los archivos en todas las formas condensables. Y si no recuerdo mal, SVN solía almacenar instantáneas.
he_the_great
2
@he_the_great, no. El formato de almacenamiento skip-delta de SVN (! = Instantáneas) está bien documentado en el manual . Y realmente no entiendo lo que quieres decir con condensable . No soy un hablante nativo, pero estoy bastante seguro de que no es una palabra real.
Helmbert
2
@he_the_great Pero incluso cuando packfiles cualquier hash dado para un archivo da como resultado el archivo completo. Sí, se comprime usando deltas, pero no es un delta para los cambios en una confirmación, sino un delta entre hashes para un archivo. En lo que respecta al objeto de confirmación, hace referencia a un árbol que hace referencia al hash para un archivo completo. Que debajo del capó los datos se comprimen no afecta la forma en que funciona git. Git almacena archivos completos en lo que respecta a una confirmación, SVN almacena deltas para confirmaciones, según tengo entendido.
Peter Olson
44

Después de tal fusión, es posible que haya confirmados dos veces en la historia.

Solución para evitar esto, cito del artículo que recomienda que las ramas con confirmaciones duplicadas (seleccionadas con cereza) usen rebase antes de fusionar:

git merge after git cherry-pick: evitando confirmaciones duplicadas

Imagina que tenemos la rama maestra y una rama b:

   o---X   <-- master
    \
     b1---b2---b3---b4   <-- b

Ahora necesitamos urgentemente los commits b1 y b3 en master, pero no los commits restantes en b. Entonces, lo que hacemos es verificar la rama maestra y los compromisos de selección de cereza b1 y b3:

$ git checkout master
$ git cherry-pick "b1's SHA"
$ git cherry-pick "b3's SHA"

El resultado sería:

   o---X---b1'---b3'   <-- master
    \
     b1---b2---b3---b4   <-- b

Digamos que hacemos otro commit en master y obtenemos:

   o---X---b1'---b3'---Y   <-- master
    \
     b1---b2---b3---b4   <-- b

Si ahora fusionáramos la rama b en master:

$ git merge b

Obtendríamos lo siguiente:

   o---X---b1'---b3'---Y--- M  <-- master
     \                     /
      b1----b2----b3----b4   <-- b

Eso significa que los cambios introducidos por b1 y b3 aparecerían dos veces en la historia. Para evitar eso, podemos cambiar la base en lugar de fusionar:

$ git rebase master b

Lo que produciría:

   o---X---b1'---b3'---Y   <-- master
                        \
                         b2'---b4'   <-- b

Finalmente:

$ git checkout master
$ git merge b

Nos da:

   o---X---b1'---b3'---Y---b2'---b4'   <-- master, b

EDITAR Correcciones supuestas por el comentario de David Lemon

efemerr
fuente
1
gran pista sobre rebase! "saltará" todas las confirmaciones seleccionadas automáticamente.
iTake
2
Honestamente, suena demasiado bueno para ser verdad, tengo que verlo con mis ojos. También rebase modifica commits, su última línea de tiempo debería ser---Y---b2'---b4'
David Lemon
Funciona perfectamente. Muy útil si no quieres tener los commits seleccionados dos veces en la historia.
user2350644
1
No debería tenerse en cuenta que si bien el rebase es hermoso, el peligro de usarlo es que las horquillas o ramas creadas a partir del antiguo b no estarán sincronizadas, y los usuarios pueden necesitar recurrir a cosas como git reset --hard y git push -f?
JHH
1
@JHH es por eso que reforzamos la sucursal local aquí
ephemerr