¿Cómo y / o por qué la fusión en Git es mejor que en SVN?

400

He escuchado en algunos lugares que una de las razones principales por las que brillan los sistemas de control de versiones distribuidas es la fusión mucho mejor que en herramientas tradicionales como SVN. ¿Esto se debe realmente a diferencias inherentes en la forma en que funcionan los dos sistemas, o las implementaciones específicas de DVCS como Git / Mercurial solo tienen algoritmos de fusión más inteligentes que SVN?

Señor chico
fuente
Todavía no obtuve una respuesta completa al leer las grandes respuestas aquí. Reposted - stackoverflow.com/questions/6172037/…
ripper234
Depende de su modelo. en casos más simples, svn a menudo es mejor porque no llama accidentalmente fusiones de 2 vías. Combinaciones de 3 vías como git puede hacer si empuja / fusiona / jala / empuja en una sola rama de desarrollo. ver: svnvsgit.com
Erik Aronesty

Respuestas:

556

La afirmación de por qué la fusión es mejor en un DVCS que en Subversion se basó en gran medida en cómo la ramificación y la fusión funcionaron en Subversion hace un tiempo. Subversion anterior a 1.5.0 no almacenaba ninguna información sobre cuándo se fusionaron las sucursales, por lo tanto, cuando deseaba fusionar, tenía que especificar qué rango de revisiones debía fusionar.

Entonces, ¿por qué las fusiones de Subversion apestan ?

Medita este ejemplo:

      1   2   4     6     8
trunk o-->o-->o---->o---->o
       \
        \   3     5     7
b1       +->o---->o---->o

Cuando queremos fusionar los cambios de b1 en el tronco, emitimos el siguiente comando, mientras estamos parados en una carpeta que tiene el tronco extraído:

svn merge -r 2:7 {link to branch b1}

... que intentará fusionar los cambios b1en su directorio de trabajo local. Y luego confirma los cambios después de resolver cualquier conflicto y probar el resultado. Cuando confirme el árbol de revisión se vería así:

      1   2   4     6     8   9
trunk o-->o-->o---->o---->o-->o      "the merge commit is at r9"
       \
        \   3     5     7
b1       +->o---->o---->o

Sin embargo, esta forma de especificar rangos de revisiones se pierde rápidamente cuando el árbol de versiones crece, ya que Subversion no tenía metadatos sobre cuándo y qué revisiones se fusionaron. Medita sobre lo que sucede después:

           12        14
trunk  …-->o-------->o
                                     "Okay, so when did we merge last time?"
              13        15
b1     …----->o-------->o

Esto es en gran parte un problema por el diseño del repositorio que tiene Subversion, para crear una rama necesita crear un nuevo directorio virtual en el repositorio que albergará una copia del tronco pero no almacena ninguna información sobre cuándo y qué las cosas se fusionaron nuevamente. Eso conducirá a desagradables conflictos de fusión a veces. Lo que era aún peor es que Subversion usaba la fusión bidireccional por defecto, lo que tiene algunas limitaciones paralizantes en la fusión automática cuando dos cabezas de rama no se comparan con su antepasado común.

Para mitigar esta Subversión ahora almacena metadatos para ramificar y fusionar. Eso resolvería todos los problemas, ¿verdad?

Y, por cierto, Subversion todavía apesta ...

En un sistema centralizado, como la subversión, los directorios virtuales apestan. ¿Por qué? Porque todos tienen acceso para verlos ... incluso los de basura experimentales. La ramificación es buena si quieres experimentar pero no quieres ver la experimentación de todos y sus tías . Este es un ruido cognitivo grave. Cuantas más ramas agregue, más basura podrá ver.

Cuantas más ramas públicas tenga en un repositorio, más difícil será hacer un seguimiento de todas las diferentes ramas. Entonces, la pregunta que tendrá es si la rama aún está en desarrollo o si está realmente muerta, lo cual es difícil de distinguir en cualquier sistema de control de versiones centralizado.

La mayoría de las veces, por lo que he visto, una organización usará por defecto una gran sucursal de todos modos. Lo cual es una pena porque a su vez será difícil hacer un seguimiento de las versiones de prueba y lanzamiento, y cualquier otra cosa buena proviene de la ramificación.

Entonces, ¿por qué los DVCS, como Git, Mercurial y Bazaar, son mejores que Subversion en la ramificación y fusión?

Hay una razón muy simple: la ramificación es un concepto de primera clase . No hay directorios virtuales por diseño y las ramas son objetos duros en DVCS que deben ser tales para poder trabajar simplemente con la sincronización de repositorios (es decir, push and pull ).

Lo primero que debe hacer cuando trabaja con un DVCS es clonar repositorios (git's clone, hg's cloney bzr's branch). La clonación es conceptualmente lo mismo que crear una rama en el control de versiones. Algunos llaman a esto bifurcación o bifurcación (aunque este último también se usa a menudo para referirse a sucursales ubicadas conjuntamente), pero es exactamente lo mismo. Cada usuario ejecuta su propio repositorio, lo que significa que tiene una ramificación por usuario .

La estructura de la versión no es un árbol , sino un gráfico . Más específicamente, un gráfico acíclico dirigido (DAG, que significa un gráfico que no tiene ningún ciclo). Realmente no es necesario profundizar en los detalles de un DAG que no sea cada confirmación tiene una o más referencias principales (en lo que se basa la confirmación). Entonces, los siguientes gráficos mostrarán las flechas entre las revisiones en reversa debido a esto.

Un ejemplo muy simple de fusión sería este; imagine un repositorio central llamado originy una usuaria, Alice, clonando el repositorio en su máquina.

         a…   b…   c…
origin   o<---o<---o
                   ^master
         |
         | clone
         v

         a…   b…   c…
alice    o<---o<---o
                   ^master
                   ^origin/master

Lo que sucede durante un clon es que todas las revisiones se copian a Alice exactamente como estaban (lo que se valida por los hash-id identificables de forma única) y marca dónde están las ramas del origen.

Luego, Alice trabaja en su repositorio, se compromete en su propio repositorio y decide impulsar sus cambios:

         a…   b…   c…
origin   o<---o<---o
                   ^ master

              "what'll happen after a push?"


         a…   b…   c…   d…   e…
alice    o<---o<---o<---o<---o
                             ^master
                   ^origin/master

La solución es bastante simple, lo único que origindebe hacer el repositorio es tomar todas las revisiones nuevas y mover su rama a la revisión más reciente (que git llama "avance rápido"):

         a…   b…   c…   d…   e…
origin   o<---o<---o<---o<---o
                             ^ master

         a…   b…   c…   d…   e…
alice    o<---o<---o<---o<---o
                             ^master
                             ^origin/master

El caso de uso, que ilustré arriba, ni siquiera necesita fusionar nada . Entonces, el problema realmente no es con los algoritmos de fusión, ya que el algoritmo de fusión de tres vías es prácticamente el mismo entre todos los sistemas de control de versiones. El problema es más sobre la estructura que cualquier otra cosa .

Entonces, ¿qué tal si me muestras un ejemplo que tiene una fusión real ?

Es cierto que el ejemplo anterior es un caso de uso muy simple, así que hagamos uno mucho más retorcido, aunque más común. ¿Recuerdas que origincomenzó con tres revisiones? Bueno, el tipo que los hizo, vamos a llamarlo Bob , ha estado trabajando por su cuenta e hizo un compromiso en su propio repositorio:

         a…   b…   c…   f…
bob      o<---o<---o<---o
                        ^ master
                   ^ origin/master

                   "can Bob push his changes?" 

         a…   b…   c…   d…   e…
origin   o<---o<---o<---o<---o
                             ^ master

Ahora Bob no puede enviar sus cambios directamente al originrepositorio. La forma en que el sistema detecta esto es verificando si las revisiones de Bob descienden directamente de originlas de él, lo que en este caso no. Cualquier intento de presionar dará como resultado que el sistema diga algo parecido a " Uh ... me temo que no puedo dejar que hagas eso Bob ".

Entonces Bob tiene que ingresar y luego fusionar los cambios (con git pull; o hg's pully merge; o bzr's merge). Este es un proceso de dos pasos. Primero Bob tiene que buscar las nuevas revisiones, que las copiarán tal como están desde el originrepositorio. Ahora podemos ver que el gráfico diverge:

                        v master
         a…   b…   c…   f…
bob      o<---o<---o<---o
                   ^
                   |    d…   e…
                   +----o<---o
                             ^ origin/master

         a…   b…   c…   d…   e…
origin   o<---o<---o<---o<---o
                             ^ master

El segundo paso del proceso de extracción es fusionar los consejos divergentes y confirmar el resultado:

                                 v master
         a…   b…   c…   f…       1…
bob      o<---o<---o<---o<-------o
                   ^             |
                   |    d…   e…  |
                   +----o<---o<--+
                             ^ origin/master

Esperemos que la fusión no tenga conflictos (si los anticipa, puede hacer los dos pasos manualmente en git con fetchy merge). Lo que más adelante debe hacerse es introducir esos cambios nuevamente origin, lo que dará como resultado una fusión de avance rápido ya que la confirmación de fusión es un descendiente directo de lo último en el originrepositorio:

                                 v origin/master
                                 v master
         a…   b…   c…   f…       1…
bob      o<---o<---o<---o<-------o
                   ^             |
                   |    d…   e…  |
                   +----o<---o<--+

                                 v master
         a…   b…   c…   f…       1…
origin   o<---o<---o<---o<-------o
                   ^             |
                   |    d…   e…  |
                   +----o<---o<--+

Hay otra opción para fusionarse en git y hg, llamada rebase , que moverá los cambios de Bob a los cambios más recientes. Como no quiero que esta respuesta sea más detallada, te dejaré leer los documentos de git , mercurial o bazar sobre eso.

Como ejercicio para el lector, intente dibujar cómo funcionará con otro usuario involucrado. Se hace de manera similar al ejemplo anterior con Bob. La fusión entre repositorios es más fácil de lo que parece porque todas las revisiones / confirmaciones son identificables de forma exclusiva.

También está el problema de enviar parches entre cada desarrollador, que fue un gran problema en Subversion que se mitiga en git, hg y bzr mediante revisiones identificables de forma única. Una vez que alguien ha fusionado sus cambios (es decir, ha realizado una confirmación de fusión) y lo envía para que todos los demás en el equipo lo consuman, ya sea presionando a un repositorio central o enviando parches, entonces no tienen que preocuparse por la fusión, porque ya sucedió . Martin Fowler llama a esta forma de trabajar la integración promiscua .

Debido a que la estructura es diferente de Subversion, al emplear un DAG, permite que la ramificación y la fusión se realicen de una manera más fácil no solo para el sistema sino también para el usuario.

Spoike
fuente
66
No estoy de acuerdo con tus ramas == argumento de ruido. Muchas ramas no confunden a las personas porque el desarrollador principal debería decirle a las personas qué rama usar para grandes características ... por lo que dos desarrolladores podrían trabajar en la rama X para agregar "dinosaurios voladores", 3 podrían funcionar en Y para "dejar que arrojes coches a la gente "
Sr. Boy
16
John: Sí, para un pequeño número de sucursales hay poco ruido y es manejable. Pero regrese después de haber presenciado más de 50 ramas y etiquetas más o menos en subversión o en un caso claro donde la mayoría de ellas no puede saber si están activas o no. Problema de usabilidad de las herramientas a un lado; ¿Por qué tener toda esa basura en tu repositorio? Al menos en p4 (dado que el "espacio de trabajo" de un usuario es esencialmente una rama por usuario), git o hg tiene la opción de no informar a todos sobre los cambios que realiza hasta que los empuje hacia arriba, lo cual es una seguridad. vigilar cuándo los cambios son relevantes para otros.
Spoike
24
No entiendo tu "demasiadas ramas experimentales son argumentos de ruido tampoco, @Spoike. Tenemos una carpeta" Usuarios "donde cada usuario tiene su propia carpeta. Allí puede ramificar tantas veces como lo desee. Las ramas son económicas en Subversion y si ignoras las carpetas de los otros usuarios (¿por qué deberías preocuparte por ellos de todos modos?), entonces no ves ruido. Pero para mí, la fusión en SVN no apesta (y lo hago a menudo, y no, no es un problema pequeño) proyecto). Así que tal vez hago algo mal;) Sin embargo, la fusión de Git y Mercurial es superior y usted lo señaló muy bien. "
John Smithers
11
En svn es fácil matar ramas inactivas, solo las elimina. El hecho de que las personas no eliminen ramas no utilizadas, por lo tanto, crea desorden es solo una cuestión de limpieza. También podría terminar fácilmente con muchas ramas temporales en Git. En mi lugar de trabajo utilizamos un directorio de nivel superior "ramas temporales" además de los estándares: las ramas personales y las ramas experimentales entran allí en lugar de abarrotar el directorio de ramas donde se guardan las líneas de código "oficiales" (no usar ramas de características).
Ken Liu el
10
¿Significa esto, entonces, que desde v1.5 subversion puede al menos combinarse tan bien como git?
Sam
29

Históricamente, Subversion solo ha podido realizar una fusión bidireccional directa porque no almacena ninguna información de fusión. Esto implica tomar un conjunto de cambios y aplicarlos a un árbol. Incluso con la información de fusión, esta sigue siendo la estrategia de fusión más utilizada.

Git usa un algoritmo de fusión de 3 vías por defecto, que implica encontrar un antepasado común para las cabezas que se fusionan y hacer uso del conocimiento que existe en ambos lados de la fusión. Esto permite que Git sea más inteligente para evitar conflictos.

Git también tiene un código sofisticado para cambiar el nombre, lo que también ayuda. Que no almacenar conjuntos de cambios o almacenar cualquier información de seguimiento - sólo se almacena el estado de los archivos en cada confirmación y utiliza la heurística para localizar y renombrar los movimientos de código según sea necesario (el almacenamiento en disco es más complicado que esto, pero la interfaz se presenta a la capa lógica no expone seguimiento).

Andrew Aylett
fuente
44
¿Tienes un ejemplo de que svn tiene conflicto de fusión pero git no?
Gqqnbig
17

En pocas palabras, la implementación de fusión se realiza mejor en Git que en SVN . Antes de 1.5 SVN no registraba una acción de fusión, por lo que era incapaz de realizar futuras fusiones sin la ayuda del usuario que necesitaba proporcionar información que SVN no registró. Con 1.5 mejoró, y de hecho el modelo de almacenamiento SVN es ligeramente más capaz que el DAG de Git. Pero SVN almacenó la información de fusión en una forma bastante enrevesada que permite que las fusiones tomen masivamente más tiempo que en Git: he observado factores de 300 en el tiempo de ejecución.

Además, SVN afirma rastrear los cambios de nombre para ayudar a las fusiones de archivos movidos. Pero en realidad todavía los almacena como una copia y una acción de eliminación separada, y el algoritmo de fusión aún se topa con ellos en situaciones de modificación / cambio de nombre, es decir, cuando un archivo se modifica en una rama y cambia el nombre en la otra, y esas ramas son para ser fusionado Tales situaciones seguirán produciendo conflictos de fusión espurios y, en el caso de los cambios de nombre de directorio, incluso conducirán a una pérdida silenciosa de modificaciones. (Las personas SVN tienden a señalar que las modificaciones aún están en el historial, pero eso no ayuda mucho cuando no están en un resultado de fusión donde deberían aparecer.

Git, por otro lado, ni siquiera rastrea los cambios de nombre, sino que los resuelve después del hecho (en el momento de la fusión), y lo hace bastante mágicamente.

La representación de fusión SVN también tiene problemas; en 1.5 / 1.6 podría fusionarse de tronco a rama tantas veces como quisiera, automáticamente, pero era necesario anunciar una fusión en la otra dirección ( --reintegrate), y dejar la rama en un estado inutilizable. Mucho más tarde descubrieron que este no es realmente el caso, y que a) --reintegrate se puede resolver automáticamente, yb) son posibles fusiones repetidas en ambas direcciones.

Pero después de todo esto (que en mi humilde opinión muestra una falta de comprensión de lo que están haciendo), sería (OK, estoy) muy precavido para usar SVN en cualquier escenario de ramificación no trivial, e idealmente trataría de ver qué piensa Git El resultado de la fusión.

Otros puntos señalados en las respuestas, como la visibilidad global forzada de las sucursales en SVN, no son relevantes para fusionar capacidades (sino para usabilidad). Además, el 'Git almacena cambios mientras que las tiendas SVN (algo diferente)' están en su mayoría fuera del punto. Conceptualmente, Git almacena cada confirmación como un árbol separado (como un archivo tar ), y luego usa bastante heurística para almacenar eso de manera eficiente. Calcular los cambios entre dos confirmaciones es independiente de la implementación de almacenamiento. Lo que es cierto es que Git almacena el DAG histórico en una forma mucho más sencilla que SVN hace su mergeinfo. Cualquiera que intente entender esto último sabrá a qué me refiero.

En pocas palabras: Git usa un modelo de datos mucho más simple para almacenar revisiones que SVN y, por lo tanto, podría poner mucha energía en los algoritmos de fusión reales en lugar de tratar de hacer frente a la representación => fusión prácticamente mejor.

Andreas Krey
fuente
11

Una cosa que no se ha mencionado en las otras respuestas, y que realmente es una gran ventaja de un DVCS, es que puede comprometerse localmente antes de impulsar sus cambios. En SVN, cuando tuve algún cambio, quería registrarme y, mientras tanto, alguien ya había hecho una confirmación en la misma rama, esto significaba que tenía que hacer una svn updateantes de poder comprometerme. Esto significa que mis cambios, y los cambios de la otra persona ahora se mezclan, y no hay forma de abortar la fusión (como con git reseto hg update -C), porque no hay compromiso para volver. Si la fusión no es trivial, esto significa que no puede continuar trabajando en su función antes de haber limpiado el resultado de la fusión.

Pero entonces, tal vez eso sea solo una ventaja para las personas que son demasiado tontas para usar ramas separadas (si no recuerdo mal, solo teníamos una rama que se usó para el desarrollo en la compañía donde usé SVN).

daniel kullmann
fuente
10

EDITAR: Esto aborda principalmente esta parte de la pregunta:
¿Esto se debe realmente a diferencias inherentes en la forma en que funcionan los dos sistemas, o las implementaciones específicas de DVCS como Git / Mercurial solo tienen algoritmos de fusión más inteligentes que SVN?
TL; DR: esas herramientas específicas tienen mejores algoritmos. Ser distribuido tiene algunos beneficios de flujo de trabajo, pero es ortogonal a las ventajas de fusión.
EDICIÓN FINAL

Leí la respuesta aceptada. Simplemente está mal.

La fusión de SVN puede ser un dolor, y también puede ser engorroso. Pero, ignore cómo funciona realmente por un minuto. No hay información que Git conserve o pueda derivar que SVN no conserve o pueda derivar. Más importante aún, no hay ninguna razón por la cual mantener copias separadas (a veces parciales) del sistema de control de versiones le proporcionará más información real. Las dos estructuras son completamente equivalentes.

Suponga que quiere hacer "algo inteligente" Git es "mejor en". Y tu cosa está registrada en SVN.

Convierta su SVN en la forma equivalente de Git, hágalo en Git y luego verifique el resultado, tal vez usando confirmaciones múltiples, algunas ramas adicionales. Si puede imaginar una forma automatizada de convertir un problema SVN en un problema Git, entonces Git no tiene una ventaja fundamental.

Al final del día, cualquier sistema de control de versiones me permitirá

1. Generate a set of objects at a given branch/revision.
2. Provide the difference between a parent child branch/revisions.

Además, para fusionar también es útil (o crítico) saber

3. The set of changes have been merged into a given branch/revision.

Mercurial , Git y Subversion (ahora de forma nativa, anteriormente usando svnmerge.py) pueden proporcionar los tres datos. Para demostrar algo fundamentalmente mejor con DVC, señale una cuarta información que está disponible en Git / Mercurial / DVC no disponible en SVN / VC centralizado.

¡Eso no quiere decir que no sean mejores herramientas!

Peter
fuente
1
Sí, respondí la pregunta en los detalles, no en el titular. svn y git tienen acceso a la misma información (en realidad, típicamente svn tiene más), por lo que svn podría hacer lo que sea que haga git. Pero, tomaron decisiones de diseño diferentes, por lo que en realidad no lo hace. La prueba en el DVC / centralizado es que puede ejecutar git como un VC centralizado (tal vez con algunas reglas impuestas) y puede ejecutar svn distribuido (pero apesta totalmente). Sin embargo, esto es demasiado académico para la mayoría de las personas: git y hg se ramifican y fusionan mejor que svn. Eso es realmente lo que importa al elegir una herramienta :-).
Peter
55
Hasta la versión 1.5 Subversion no almacenaba toda la información necesaria. La información almacenada es diferente con SVN posterior a 1.5: Git almacena a todos los padres de una confirmación de fusión, mientras que Subversion almacena las revisiones que ya se fusionaron en una rama.
Jakub Narębski el
44
Una herramienta que es difícil de volver a implementar en un repositorio svn es git merge-base. Con git, puedes decir "ramas a y b divididas en la revisión x". Pero svn almacena "los archivos se copiaron de foo a bar", por lo que debe usar la heurística para determinar que la copia a barra estaba creando una nueva rama en lugar de copiar archivos dentro de un proyecto. El truco es que una revisión en svn se define por el número de revisión y la ruta base. Aunque es posible suponer "tronco" la mayor parte del tiempo, muerde si realmente hay ramas.
Douglas
2
Re: "No hay información que git guarde o pueda derivar que svn no guarde o pueda derivar". - Descubrí que SVN no recordaba cuándo se habían fusionado las cosas. Si desea llevar el trabajo desde el tronco a la rama e ir y venir, la fusión puede ser difícil. En Git, cada nodo en su gráfico de revisión sabe de dónde vino. Tiene hasta dos padres y algunos cambios locales. Confiaría en Git para poder fusionar más que SVN. Si se fusiona en SVN y elimina la rama, se pierde el historial de la rama. Si se fusiona en GIT y elimina la rama, el gráfico permanece, y con él el complemento "culpa".
Richard Corfield
1
Sin embargo, ¿no es el caso que git y mercurial tienen toda la información necesaria a nivel local, mientras que svn necesita mirar los datos locales y centrales para derivar la información?
Warren Dew
8

SVN rastrea archivos mientras que Git rastrea cambios de contenido . Es lo suficientemente inteligente como para rastrear un bloque de código que se refactorizó de una clase / archivo a otro. Utilizan dos enfoques diferentes completos para rastrear su fuente.

Todavía uso SVN en gran medida, pero estoy muy satisfecho con las pocas veces que he usado Git.

Una buena lectura si tienes tiempo: por qué elegí Git

usado2 podría
fuente
Eso es lo que leí también, y eso era con lo que contaba, pero no funciona, en la práctica.
Rolf
Git rastrea el contenido de los archivos, solo muestra el contenido como cambios
Ferrybig
6

Acabo de leer un artículo en el blog de Joel (lamentablemente el último). Este es sobre Mercurial, pero en realidad habla sobre las ventajas de los sistemas de VC distribuidos como Git.

Con el control de versión distribuido, la parte distribuida en realidad no es la parte más interesante. Lo interesante es que estos sistemas piensan en términos de cambios, no en términos de versiones.

Lee el artículo aquí .

Rubayeet
fuente
55
Ese fue uno de los artículos en los que estaba pensando antes de publicar aquí. Pero "piensa en términos de cambios" es un término muy vago que suena a marketing (recuerde que la compañía de Joel vende DVCS ahora)
Sr. Boy
2
También pensé que era vago ... Siempre pensé que los conjuntos de cambios eran una parte integral de las versiones (o más bien las revisiones), lo que me sorprende de que algunos programadores no piensen en términos de cambios.
Spoike
Para un sistema que realmente "piensa en términos de cambios", consulte Darcs
Max
@Max: claro, pero cuando se trata de empujar, Git entrega donde Darcs es básicamente tan doloroso como Subversion cuando se trata de fusionarse realmente.
tripleee
Las tres desventajas de Git son: a) no es tan bueno para los archivos binarios como la gestión de documentos, donde es muy poco probable que las personas quieran ramificarse y fusionarse b) se supone que desea clonar TODO c) almacena el historial de todo en el clon, incluso para binarios que cambian con frecuencia y que causan hinchazón de clones. Creo que un VCS centralizado es mucho mejor para esos casos de uso. Git es mucho mejor para el desarrollo regular, especialmente para la fusión y la ramificación.
locka