tag - Realmente, ¿un ejemplo concreto de que fusionarse en Git es más fácil que SVN?
qué tipos de etiquetas existen en git (7)
El ejemplo más concreto que puedo pensar es la fusión más simple que no da como resultado conflictos de fusión. Sin embargo (TL; DR) con ese ejemplo, Git sigue siendo inherentemente un procedimiento más simple que con Subversion. Veamos por qué:
Subversión
Considere la siguiente situación en subversión; el tronco y la rama de características:
1 2 3
…--o--o--o trunk
/4 5
o--o branches/feature_1
Para fusionar puede usar el siguiente comando en subversión:
# thank goodness for the addition of the --reintegrate flag in SVN 1.5, eh?
svn merge --reintegrate central/repo/path/to/branches/feature_1
# build, test, and then... commit the merge
svn commit -m "Merged feature_1 into trunk!"
En la subversión, la fusión de los cambios requiere otra confirmación. Esto es para publicar los cambios que hizo la fusión con la aplicación de los cambios en el directorio virtual de la rama de características en el tronco. De esta forma, todos los que trabajen con el troncal ahora pueden usarlo y el gráfico de revisión se ve así:
1 2 3 6
…--o--o--o------o /trunk
/4 5/
o--o /branches/feature_1
Veamos cómo se hace esto en git.
Git
En Git, este compromiso de fusión realmente no es necesario ya que las ramas son marcadores glorificados en el gráfico de revisión. Entonces, con el mismo tipo de estructura de gráficos de revisión se ve así:
v-- master, HEAD
1 2 3
…--o--o--o
/4 5
o--o
^-- feature_branch
Con la cabecera actualmente en la rama principal, podemos realizar una fusión simple con la rama de características:
# Attempt a merge
git merge feature_branch
# build, test, and then... I am done with the merge
... y adelantará la rama a la confirmación a la que apunta la rama de características. Esto es posible porque Git sabe que el objetivo de la fusión es un descendiente directo y la sucursal actual solo necesita tomar en cuenta todos los cambios que ocurrieron. El gráfico de revisión terminará luciendo así:
1 2 3 4 5
…--o--o--o--o--o
^-- feature_branch, master, HEAD
Los cambios no necesitan una nueva confirmación, ya que todo lo que git ha hecho es mover las referencias de las sucursales más hacia adelante. Todo lo que queda es publicar esto en el repositorio público si tiene alguno:
# build, test, and then... just publish it
git push
Conclusión
Dado este escenario simple, puede afirmar dos cosas en la diferencia entre Subversion y Git:
- SVN requiere al menos un par de comandos para finalizar la fusión y te obliga a publicar tu fusión como una confirmación.
- Git solo requiere un comando y no te obliga a publicar tu combinación.
Dado que este es el escenario de fusión más simple, es difícil argumentar que la subversión es más fácil que git.
En escenarios de fusión más difíciles, git también le proporciona la capacidad de volver a establecer la base de la rama que produce un gráfico de revisión más simple a costa de la reescritura de la historia . Sin embargo, una vez que te familiarices con el tema y evites publicar rescripciones de historia de cosas ya publicadas, no es nada malo.
Las rebases interactivas están fuera del alcance de la pregunta, pero para ser honesto; le permite la capacidad de reorganizar, aplastar y eliminar compromisos. No quisiera cambiar de nuevo a Subversion porque la reescritura de la historia no es posible por diseño.
Pregunta de desbordamiento de pila ¿ Cómo y / o por qué la fusión en Git es mejor que en SVN? es una gran pregunta con algunas excelentes respuestas . Sin embargo, ninguno de ellos muestra un ejemplo simple donde la fusión en Git funciona mejor que SVN .
En caso de que esta pregunta se cierre como un duplicado, ¿qué es?
- Un escenario de fusión concreta
- ¿Cómo es difícil en SVN?
- ¿Cómo es que la misma fusión es más fácil en Git?
Algunos puntos:
- Sin filosofía ni explicaciones profundas de lo que es un DVCS por favor. Estos son geniales, de verdad, pero no quiero que sus detalles ofusquen la respuesta a este (IMHO).
- No me importa el "SVN histórico" en este momento. Por favor compare el Git moderno (1.7.5) con el SVN moderno (1.6.15).
- No cambia el nombre por favor, sé que Git detecta cambios de nombre y movimientos, mientras que SVN no lo hace. Esto es genial, pero estoy buscando algo más profundo, y un ejemplo que no implica cambios de nombre o movimientos.
- Sin rebase u otra operación ''avanzada'' de Git. Solo muéstrame la fusión, por favor.
Mantener la respuesta corta: en DVCS, ya que tienes un control de fuente local, si algo se arruina en el proceso de fusión (que probablemente ocurrirá en grandes fusiones), siempre puedes retroceder a una versión local anterior que tenga los cambios que realices. he hecho antes de la fusión, y luego intente de nuevo.
Entonces, básicamente, puedes fusionar sin miedo a que tus cambios locales se dañen durante el proceso.
No tengo ejemplos concretos, pero cualquier tipo de fusión repetida es difícil, en particular la llamada fusión entrecruzada .
a
/ /
b1 c1
|/ /|
| X |
|/ /|
b2 c2
fusionando b2 y c2
La página wiki en Subversion Wiki que describe las diferencias entre merge info based assymetric Subversion merge (con direcciones ''sync'' y ''reintegrate'') y merge tracking based simétrico merge en DVCS tiene una sección " Symmetric Merge with Criss-Cross Merge "
Parece que es un mito que fusionarse en Git es más fácil que en SVN ...
Por ejemplo, Git no puede fusionarse en un árbol de trabajo que tiene cambios, a diferencia de SVN.
Considere el siguiente escenario simple: tiene algunos cambios en su árbol de trabajo y desea integrar los cambios remotos sin cometer los propios .
SVN : update
, [resolver conflictos].
Git : stash
, fetch
, rebase
, stash pop
, [resolver conflictos], [ stash drop
si hubo conflictos].
¿O conoces una forma más fácil en Git?
Por cierto, este caso de uso parece ser tan importante que IntelliJ incluso implementó la funcionalidad faltante "Actualizar Proyecto" para Git (actualización de analogía a SVN) que puede automatizar los pasos manuales descritos anteriormente:
Si excluye el "Infierno refactorizado por fusión" , no obtendrá muestras justas , porque simplemente no existen.
Solo puedo contarte un pequeño experimento donde Git NO fue mejor que Subversion (los mismos problemas).
Me preguntaba sobre este caso: empiezas con dos ramas "mytest1" y "mytest2", ambas basadas en el mismo compromiso. Tienes un archivo C que contiene una función blub (). En la rama mytest1 mueve "blub ()" a una posición diferente en el archivo y confirma. En la rama mytest2 modifica blub () y commit. En la rama mytest2 intentas usar "git merge mytest1".
Parece dar un conflicto de fusión. Esperé que Git reconociera que "blub ()" se movió en mytest1 y luego pudo fusionar automáticamente la modificación en mytest2 con el movimiento en mytest1. Pero al menos cuando probé esto no funcionó automáticamente ...
Entonces, si bien entiendo completamente que Git es mucho mejor para rastrear qué se ha fusionado y qué no se ha fusionado aún, también me pregunto si hay un caso de fusión "puro" en el que Git es mejor que SVN ...
Ahora, debido a que esta pregunta me ha estado molestando durante mucho tiempo, realmente estaba tratando de crear un ejemplo concreto donde Git sea mejor , mientras que la fusión en SVN falla.
Encontré uno aquí https://.com/a/2486662/1917520 , pero esto incluye un cambio de nombre y la pregunta aquí fue para un caso sin cambio de nombre.
Entonces aquí hay un ejemplo de SVN que básicamente prueba esto:
bob +-----r3----r5---r6---+
/ / /
anna / +-r2----r4----+--+ /
/ / / /
trunk r1-+-------------------r7-- Conflict
La idea aquí es:
- Anna y Bob son ambos desarrolladores con sus propias sucursales (creadas en r2, r3).
- Anna hace algunas modificaciones (r4),
- Bob hace algunas modificaciones (r5).
- Bob fusiona las modificaciones de Anna en su rama; esto da conflictos, que Bob corrige y luego confirma (r6).
- Las modificaciones de Annas se fusionan de nuevo en el tronco (r7).
- Bob trata de fusionar su modificación de nuevo en el tronco y esto nuevamente da un conflicto.
Aquí hay un script Bash , que produce este conflicto (usando SVN 1.6.17 y también SVN 1.7.9):
#!/bin/bash
cd /tmp
rm -rf rep2 wk2
svnadmin create rep2
svn co file:///tmp/rep2 wk2
cd wk2
mkdir trunk
mkdir branches
echo -e "A/nA/nB/nB" > trunk/f.txt
svn add trunk branches
svn commit -m "Initial file"
svn copy ^/trunk ^/branches/anna -m "Created branch anna"
svn copy ^/trunk ^/branches/bob -m "Created branch bob"
svn up
echo -e "A/nMA/nA/nB/nB" > branches/anna/f.txt
svn commit -m "anna added text"
echo -e "A/nMB/nA/nB/nMB/nB" > branches/bob/f.txt
svn commit -m "bob added text"
svn up
svn merge --accept postpone ^/branches/anna branches/bob
echo -e "A/nMAB/nA/nB/nMB/nB" > branches/bob/f.txt
svn resolved branches/bob/f.txt
svn commit -m "anna merged into bob with conflict"
svn up
svn merge --reintegrate ^/branches/anna trunk
svn commit -m "anna reintegrated into trunk"
svn up
svn merge --reintegrate --dry-run ^/branches/bob trunk
El último "--dry-run" te dice que habrá un conflicto. Si primero intentas fusionar la reintegración de Anna en la sucursal de Bob, entonces también obtienes un conflicto; así que si reemplazas la última svn merge
con
svn merge ^/trunk branches/bob
esto también muestra un conflicto.
Aquí es lo mismo con Git 1.7.9.5:
#!/bin/bash
cd /tmp
rm -rf rep2
mkdir rep2
cd rep2
git init .
echo -e "A/nA/nB/nB" > f.txt
git add f.txt
git commit -m "Initial file"
git branch anna
git branch bob
git checkout anna
echo -e "A/nMA/nA/nB/nB" > f.txt
git commit -a -m "anna added text"
git checkout bob
echo -e "A/nMB/nA/nB/nMB/nB" > f.txt
git commit -a -m "bob added text"
git merge anna
echo -e "A/nMAB/nA/nB/nMB/nB" > f.txt
git commit -a -m "anna merged into bob with conflict"
git checkout master
git merge anna
git merge bob
El contenido de f.txt cambia de esta manera.
Versión inicial
A
A
B
B
Las modificaciones de Anna
A
MA
A
B
B
Modificaciones de Bob
A
MB
A
B
MB
B
Después de que la rama de Anna se fusionó con la rama de Bob
A
MAB
A
B
MB
B
Como mucha gente ya señaló: El problema es que esa subversión no puede recordar que Bob ya resolvió un conflicto. Entonces, cuando tratas de fusionar la rama de Bob en el tronco, debes volver a resolver el conflicto.
Git funciona completamente diferente. Aquí una representación gráfica de lo que está haciendo git
bob +--s1----s3------s4---+
/ / /
anna / +-s1----s2----+--+ /
/ / / /
master s1-+-------------------s2----s4
s1 / s2 / s3 / s4 son las instantáneas de las tomas de git del directorio de trabajo.
Notas:
- Cuando anna y bob crean sus ramas de desarrollo, NO creará ningún commit bajo git. git recordará que ambas ramas se refieren inicialmente al mismo objeto de confirmación que la rama principal. (Esta confirmación a su vez se referirá a la instantánea s1).
- Cuando anna implementa su modificación, creará una nueva instantánea "s2" + un objeto de confirmación. Un objeto de commit incluye:
- Una referencia a la instantánea (s2 aquí)
- Un mensaje de compromiso
- Información sobre antepasados (otros objetos de confirmación)
- Cuando Bob implementa su modificación, creará otra instantánea s3 + un objeto de confirmación
- Cuando bob fusione las modificaciones annas en su rama de desarrollo, esto creará otra instantánea s4 (que contiene una combinación de sus cambios y los cambios de anna) + otro objeto de confirmación
- Cuando anna fusiona sus cambios nuevamente en la rama maestra, esta será una fusión de "avance rápido" en el ejemplo mostrado, porque el maestro no ha cambiado mientras tanto. Lo que significa "avance rápido" aquí es que el maestro simplemente apuntará a la instantánea s2 desde anna sin fusionar nada. Con un "avance rápido" ni siquiera habrá otro objeto de compromiso. La rama "maestra" se referirá ahora directamente al último compromiso de la rama "anna"
- Cuando bob ahora fusione sus cambios en el tronco, ocurrirá lo siguiente:
- git descubrirá que la confirmación de anna que creó la instantánea s2 es un antecesor (directo) para bobs commit, que creó la instantánea s4.
- debido a esto, git volverá a "adelantar" rápidamente la rama principal al último compromiso de la rama "bob".
- de nuevo, esto ni siquiera creará un nuevo objeto de compromiso. La rama "principal" simplemente apunta al último compromiso de la rama "bob".
Aquí está la salida de "git ref-log" que muestra todo esto:
88807ab HEAD@{0}: merge bob: Fast-forward
346ce9f HEAD@{1}: merge anna: Fast-forward
15e91e2 HEAD@{2}: checkout: moving from bob to master
88807ab HEAD@{3}: commit (merge): anna merged into bob with conflict
83db5d7 HEAD@{4}: commit: bob added text
15e91e2 HEAD@{5}: checkout: moving from anna to bob
346ce9f HEAD@{6}: commit: anna added text
15e91e2 HEAD@{7}: checkout: moving from master to anna
15e91e2 HEAD@{8}: commit (initial): Initial file
Como puede ver en esto:
- cuando vamos a la rama de desarrollo de anna (HEAD @ {7}) no cambiamos a un commit diferente, guardamos el commit; git solo recuerda que ahora estamos en una rama diferente
- En HEAD @ {5} nos movemos a la rama inicial de bob; esto moverá la copia de trabajo al mismo estado que la rama principal, porque bob no ha cambiado nada aún
- En HEAD @ {2} volvemos a la rama principal, por lo que al mismo objeto de commit comenzó todo.
- Head @ {1}, HEAD @ {0} muestra las fusiones de "avance rápido", que no crean nuevos objetos de compromiso.
Con "git cat-file HEAD @ {8} -p" puede inspeccionar los detalles completos del objeto de confirmación inicial. Para el ejemplo anterior, obtuve:
tree b634f7c9c819bb524524bcada067a22d1c33737f
author Ingo <***> 1475066831 +0200
committer Ingo <***> 1475066831 +0200
Initial file
La línea "árbol" identifica la instantánea s1 (== b634f7c9c819bb524524bcada067a22d1c33737f) a la que hace referencia esta confirmación.
Si lo hago, "git cat-file HEAD @ {3} -p" obtengo:
tree f8e16dfd2deb7b99e6c8c12d9fe39eda5fe677a3
parent 83db5d741678908d76dabb5fbb0100fb81484302
parent 346ce9fe2b613c8a41c47117b6f4e5a791555710
author Ingo <***> 1475066831 +0200
committer Ingo <***> 1475066831 +0200
anna merged into bob with conflict
Esto muestra el objeto de confirmación, bob creado al fusionar la rama de desarrollo de anna. De nuevo, la línea "árbol" se refiere a la instantánea creada (s3 aquí). Además, tenga en cuenta las líneas "principales". El segundo que comienza con "padre 346ce9f" más tarde le dice a git, cuando intenta fusionar la rama de desarrollo de bob en la rama maestra, que este último compromiso de bob tiene el último compromiso de anna como ancestro. Esta es la razón por la cual git sabe que la fusión de la rama de desarrollo de bob en la rama principal es un "avance rápido".
Desde una perspectiva práctica , la fusión ha sido tradicionalmente "difícil" debido a lo que yo llamo el problema de la "fusión del Big Bang". Supongamos que un desarrollador ha estado trabajando en algún código por un tiempo y aún no ha comprometido su trabajo (tal vez el desarrollador está acostumbrado a trabajar en Subversion contra trunk
, y no comete un código inacabado). Cuando el desarrollador finalmente se comprometa, habrá una gran cantidad de cambios, todo en una confirmación. Para los otros desarrolladores que quieran fusionar su trabajo con este compromiso de "big bang", la herramienta VCS no va a tener suficiente información sobre cómo el primer desarrollador llegó al punto en que se comprometieron, por lo que solo obtendrá "aquí hay un conflicto gigante". en toda esta función, ve a arreglarlo ".
Por otro lado, el estilo habitual de trabajar con Git y otros DVCS que tienen sucursales locales baratas es comprometerse regularmente. Una vez que has hecho un poco de trabajo que tiene mucho sentido, lo comprometes. No tiene que ser perfecto, pero debería ser una unidad de trabajo coherente. Cuando vuelve a fusionarse, todavía tiene ese historial de confirmaciones más pequeñas que muestra cómo llegó del estado original al estado actual. Cuando el DVCS va a fusionar esto con el trabajo de otros, tiene mucha más información acerca de qué cambios se hicieron cuando, y terminas consiguiendo conflictos más pequeños y menos .
El punto es que todavía puedes fusionar un problema difícil con Git al hacer una única confirmación Big Bang solo después de que hayas terminado algo. Git lo alienta a realizar confirmaciones más pequeñas (haciéndolas lo más indoloras posible), lo que facilita la fusión futura.