what vcs turtle software manager framework control git svn mercurial merge dvcs

vcs - Fusionando: Hg/Git vs. SVN



vcs git (6)

Antes de la subversión 1.5 (si no me equivoco), la subversión tenía una desventaja significativa ya que no recordaba el historial de fusión.

Veamos el caso descrito por VonC:

- x - x - x (v2) - x - x - x (v2.1) |/ | x - A - x (v2-only) / x - B - x (wss)

Observe las revisiones A y B. Supongamos que fusionó los cambios de la revisión A en la rama "wss" a la rama "v2-only" en la revisión B (por el motivo que sea), pero continuó usando ambas ramas. Si intentaba fusionar las dos ramas nuevamente usando mercurial, solo fusionaría los cambios después de las revisiones A y B. Con la subversión, tendría que fusionar todo, como si no hubiera realizado una fusión antes.

Este es un ejemplo de mi propia experiencia, donde la fusión de B a A tomó varias horas debido al volumen de código: eso habría sido un verdadero dolor para pasar de nuevo , que hubiera sido el caso con la subversión anterior a la 1.5.

Otra diferencia, probablemente más relevante, en el comportamiento de fusión de Hginit: reeducación de Subversion :

Imagine que usted y yo estamos trabajando en algún código, y ramificamos ese código, y cada uno entra en nuestros espacios de trabajo separados y realiza muchos cambios en ese código por separado, por lo que se han separado bastante.

Cuando tenemos que fusionarnos, Subversion intenta ver ambas revisiones, mi código modificado y tu código modificado, e intenta adivinar cómo unirlas en un gran desastre profano. Generalmente falla, produciendo páginas y páginas de "conflictos de fusión" que no son realmente conflictos, simplemente lugares en los que Subversion no pudo descifrar lo que hicimos.

Por el contrario, mientras trabajábamos por separado en Mercurial, Mercurial estaba ocupado manteniendo una serie de conjuntos de cambios. Entonces, cuando queremos fusionar nuestro código, Mercurial en realidad tiene mucha más información: sabe lo que cada uno de nosotros cambió y puede volver a aplicar esos cambios, en lugar de solo mirar el producto final e intentar adivinar cómo expresarlo. juntos.

En resumen, la forma en que Mercurial analiza las diferencias es (¿era?) Superior a la de la subversión.

A menudo leo que Hg (y Git y ...) son mejores para fusionarse que SVN, pero nunca he visto ejemplos prácticos de dónde Hg / Git pueda fusionar algo donde SVN falla (o donde SVN necesita intervención manual). ¿Podría publicar algunas listas paso a paso de las operaciones de bifurcación / modificación / confirmación / ... que muestran dónde fallaría SVN mientras Hg / Git avanza felizmente? Casos prácticos, no muy excepcionales, por favor ...

Algunos antecedentes: tenemos unas pocas docenas de desarrolladores trabajando en proyectos que usan SVN, con cada proyecto (o grupo de proyectos similares) en su propio repositorio. Sabemos cómo aplicar ramas de lanzamiento y funciones para no tener problemas muy a menudo (es decir, hemos estado allí, pero hemos aprendido a superar los problemas de Joel de "un programador que causa trauma a todo el equipo"). o "necesita seis desarrolladores por dos semanas para reintegrar una sucursal"). Tenemos ramas de publicación que son muy estables y solo se utilizan para aplicar correcciones de errores. Tenemos troncales que deberían ser lo suficientemente estables para poder crear una versión dentro de una semana. Y tenemos ramas de características en las que los desarrolladores individuales o grupos de desarrolladores pueden trabajar. Sí, se eliminan después de la reintegración para que no llenen el depósito. ;)

Así que todavía estoy tratando de encontrar las ventajas de Hg / Git sobre SVN. Me encantaría tener alguna experiencia práctica, pero no hay proyectos más grandes que podamos mover a Hg / Git todavía, así que estoy atascado jugando con pequeños proyectos artificiales que solo contienen algunos archivos inventados. Y estoy buscando algunos casos en los que pueda sentir el impresionante poder de Hg / Git, ya que hasta ahora he leído sobre ellos pero no he podido encontrarlos.


No utilizo Subversion por mi cuenta, pero de las notas de la versión de Subversion 1.5: Merge tracking (fundamental) parece que existen las siguientes diferencias con respecto a cómo funciona el seguimiento de fusión en sistemas de control de versiones DAG completos como Git o Mercurial.

  • La fusión de la línea troncal a la línea es diferente de la combinación de la línea principal con el --reintegrate troncal: por alguna razón, la fusión de línea a línea requiere --reintegrate opción para svn merge .

    En los sistemas de control de versiones distribuidas como Git o Mercurial, no existe una diferencia técnica entre el tronco y la rama: todas las ramas se crean iguales (aunque puede haber diferencias sociales ). La fusión en cualquier dirección se realiza de la misma manera.

  • --use-merge-history proporcionar la nueva --use-merge-history -g ( --use-merge-history ) para svn log y svn blame para tener en cuenta el seguimiento de fusión.

    En Git y Mercurial, el seguimiento de fusión se tiene en cuenta automáticamente al mostrar el historial (registro) y la culpa. En Git puedes solicitar seguir al primer padre solo con --first-parent (supongo que existe una opción similar también para Mercurial) para "descartar" la información de seguimiento de fusión en el git log .

  • Por lo que sé, svn:mergeinfo propiedad svn:mergeinfo almacena información por ruta sobre conflictos (Subversion está basada en conjuntos de cambios), mientras que en Git y Mercurial simplemente se comprometen objetos que pueden tener más de un padre.

  • La subsección "Problemas conocidos" para el seguimiento de fusión en Subversion sugiere que la combinación repetida / cíclica / reflexiva podría no funcionar correctamente. Significa que con los siguientes historiales, la segunda fusión podría no hacer lo correcto (''A'' puede ser troncal o bifurcada, y ''B'' puede ser una bifurcación o troncal, respectivamente):

    *---*---x---*---y---*---*---*---M2 <-- A / / / --*----M1---*---*---/ <-- B

    En el caso de que el arte ASCII anterior se rompa: la rama ''B'' se crea (bifurcada) desde la rama ''A'' en la revisión ''x'', luego la rama ''A'' se fusiona en la revisión ''y'' en la rama ''B'' como fusionar ''M1'', y finalmente la rama ''B'' se fusiona en la rama ''A'' como fusionar ''M2''.

    *---*---x---*-----M1--*---*---M2 <-- A / / / /-*---y---*---*---/ <-- B

    En el caso de que el arte ASCII anterior se rompa: Rama ''B'' se crea (bifurcada) desde la rama ''A'' en la revisión ''x'', se fusiona en la rama ''A'' en ''y'' como ''M1'', y luego se fusionó de nuevo en la rama ''A'' como ''M2''.

  • Es posible que Subversion no admita el caso avanzado de fusión entrecruzada .

    *---b-----B1--M1--*---M3 / / / / / X / / / / / /--B2--M2--*

    Git maneja esta situación muy bien en la práctica usando la estrategia de fusión "recursiva". No estoy seguro de Mercurial.

  • En "Problemas conocidos", hay una advertencia de que el seguimiento de fusión no funciona con el cambio de nombre de archivo, por ejemplo, cuando un lado cambia el nombre del archivo (y quizás lo modifica) y el segundo modifica el archivo sin cambiar el nombre (bajo el nombre anterior).

    Tanto Git como Mercurial manejan este caso muy bien en la práctica: Git usa la detección de cambio de nombre , Mercurial usa el seguimiento de cambio de nombre .

HTH


Otros han cubierto los aspectos más teóricos de esto. Tal vez pueda prestar una perspectiva más práctica.

Actualmente estoy trabajando para una empresa que usa SVN en un modelo de desarrollo de "rama de características". Es decir:

  • No se puede trabajar en el tronco
  • Cada desarrollador puede tener crear sus propias sucursales
  • Las sucursales deben durar por la duración de la tarea emprendida
  • Cada tarea debe tener su propia rama
  • Las fusiones al tronco deben estar autorizadas (normalmente a través de bugzilla)
  • En momentos en que se requieren altos niveles de control, las fusiones pueden ser realizadas por un controlador de acceso

En general, funciona. SVN se puede usar para un flujo como este, pero no es perfecto. Hay algunos aspectos de SVN que se ponen en el camino y dan forma al comportamiento humano. Eso le da algunos aspectos negativos.

  • Hemos tenido bastantes problemas con personas que se bifurcan desde puntos inferiores a ^/trunk . Esta camada combina registros de información en todo el árbol y, finalmente, rompe el seguimiento de fusión. Comienzan a aparecer conflictos falsos y reina la confusión.
  • Recoger cambios del tronco en una rama es relativamente sencillo. svn merge hace lo que quieres. La fusión de sus cambios requiere (se nos dice) --reintegrate en el comando de fusión. Nunca entendí realmente este cambio, pero significa que la rama no puede fusionarse nuevamente en el tronco. Esto significa que es una rama muerta y debes crear una nueva para continuar trabajando. (Ver nota)
  • Todo el asunto de realizar operaciones en el servidor a través de URL al crear y eliminar sucursales realmente confunde y asusta a las personas. Entonces lo evitan.
  • Es fácil equivocarse al cambiar de una rama a otra, dejando que una parte mire la rama A, mientras que otra parte mira la rama B. Entonces, la gente prefiere hacer todo su trabajo en una rama.

Lo que suele ocurrir es que un ingeniero crea una sucursal el día 1. Comienza su trabajo y se olvida de él. Algún tiempo después aparece un jefe y le pregunta si puede liberar su trabajo en el maletero. El ingeniero ha estado temiendo este día porque la reintegración significa:

  • Fusionando su rama longeva de regreso al tronco y resolviendo todos los conflictos, y liberando un código no relacionado que debería haber estado en una rama separada, pero no lo fue.
  • Eliminando su rama
  • Creando una nueva rama
  • Cambiando su copia de trabajo a la nueva rama

... y como el ingeniero hace esto tan poco como pueden, no pueden recordar el "hechizo mágico" para hacer cada paso. Los conmutadores y URL incorrectos suceden, y de repente están en un lío y van a buscar al "experto".

Finalmente, todo se calma y la gente aprende a lidiar con las deficiencias, pero cada nuevo titular pasa por los mismos problemas. La realidad final (a diferencia de lo que comencé en el comienzo) es:

  • No se realiza ningún trabajo en el maletero
  • Cada desarrollador tiene una rama importante
  • Las sucursales duran hasta que el trabajo deba ser liberado
  • Las correcciones de errores con cupones tienden a tener su propia rama
  • Las fusiones al tronco se realizan cuando está autorizado

...pero...

  • A veces, el trabajo llega al tronco cuando no debería, porque está en la misma rama que otra cosa.
  • La gente evita toda fusión (incluso cosas fáciles), por lo que la gente a menudo trabaja en sus propias burbujas
  • Las grandes fusiones tienden a ocurrir y causan una cantidad limitada de caos.

Afortunadamente, el equipo es lo suficientemente pequeño como para hacer frente, pero no escala. La cosa es que nada de esto es un problema con CVCS, pero más porque las fusiones no son tan importantes como en DVCS, no son tan ingeniosas. Esa "fricción de fusión" causa un comportamiento que significa que un modelo de "rama de características" comienza a descomponerse. Las buenas fusiones deben ser una característica de todos los VCS, no solo DVCS.

De acuerdo con this ahora hay un --record-only que podría usarse para resolver el problema de la --reintegrate , y apparently v1.8 elige cuándo hacer una reintegración automáticamente, y no causa que la rama muera después


Recientemente, migramos de SVN a GIT y enfrentamos esta misma incertidumbre. Hubo mucha evidencia anecdótica de que GIT era mejor, pero era difícil encontrar ejemplos.

Puedo decirte, sin embargo, que GIT es MUCHO MEJOR en la fusión que SVN. Esto es obviamente anecdótico, pero hay una tabla a seguir.

Estas son algunas de las cosas que encontramos:

  • SVN solía lanzar muchos conflictos de árbol en situaciones donde parecía que no debería. Nunca llegamos al fondo de esto, pero no sucede en GIT.
  • Si bien es mejor, GIT es significativamente más complicado. Dedica algo de tiempo a la capacitación.
  • Estábamos acostumbrados a Tortoise SVN, que nos gustó. Tortoise GIT no es tan bueno y esto puede desanimarlo. Sin embargo, ahora uso la línea de comandos de GIT, que prefiero Tortoise SVN o cualquiera de las GUI de GIT.

Cuando estábamos evaluando GIT, realizamos las siguientes pruebas. Estos muestran a GIT como el ganador cuando se trata de fusionarse, pero no por mucho. En la práctica, la diferencia es mucho mayor, pero supongo que no hemos logrado replicar las situaciones que SVN maneja mal.


Sin hablar de las ventajas habituales (compromisos fuera de línea, proceso de publicación , ...) aquí hay un ejemplo de "fusión" que me gusta:

El escenario principal que sigo viendo es una rama en la que ... en realidad se desarrollan dos tareas no relacionadas
(comenzó a partir de una característica, pero condujo al desarrollo de esta otra característica.
O comenzó desde un parche, pero condujo al desarrollo de otra característica).

¿Cómo combinar solo una de las dos características en la rama principal?
¿O cómo aislar las dos características en sus propias ramas?

Podría tratar de generar algún tipo de parche, el problema es que ya no está seguro de las dependencias funcionales que podrían haber existido entre:

  • los commits (o revisión para SVN) usados ​​en sus parches
  • el otro no comete parte del parche

Git (y Mercurial también, supongo) proponen la opción rebase --onto para rebase (restablecer la raíz de la rama) parte de una rama:

De la publicación de Jefromi

- x - x - x (v2) - x - x - x (v2.1) / x - x - x (v2-only) - x - x - x (wss)

Puede desenredar esta situación en la que tiene parches para la v2 y una nueva característica de wss en:

- x - x - x (v2) - x - x - x (v2.1) |/ | x - x - x (v2-only) / x - x - x (wss)

, lo que le permite:

  • prueba cada rama de forma aislada para verificar si todo compila / funciona según lo previsto
  • fusiona solo lo que quieres main.

La otra característica que me gusta (que influencia se funde) es la capacidad de squash commits (en una rama que aún no se ha enviado a otro repositorio) para presentar:

  • un historial más limpio
  • commits que son más coherentes (en lugar de commit1 para function1, commit2 para function2, commit3 nuevamente para function1 ...)

Eso asegura fusiones que son mucho más fáciles, con menos conflictos.


Yo también he estado buscando un caso donde, digamos, Subversion no logra fusionar una rama y Mercurial (y Git, Bazaar, ...) hace lo correcto.

El libro de SVN describe cómo los archivos renombrados se combinan incorrectamente . ¡Esto se aplica a Subversion 1.5 , 1.6 , 1.7 y 1.8 ! He intentado recrear la situación a continuación:

cd /tmp rm -rf svn-repo svn-checkout svnadmin create svn-repo svn checkout file:///tmp/svn-repo svn-checkout cd svn-checkout mkdir trunk branches echo ''Goodbye, World!'' > trunk/hello.txt svn add trunk branches svn commit -m ''Initial import.'' svn copy ''^/trunk'' ''^/branches/rename'' -m ''Create branch.'' svn switch ''^/trunk'' . echo ''Hello, World!'' > hello.txt svn commit -m ''Update on trunk.'' svn switch ''^/branches/rename'' . svn rename hello.txt hello.en.txt svn commit -m ''Rename on branch.'' svn switch ''^/trunk'' . svn merge --reintegrate ''^/branches/rename''

De acuerdo con el libro, la fusión debería terminar limpiamente, pero con datos incorrectos en el archivo renombrado ya que la actualización en el trunk se ha olvidado. En cambio, tengo un conflicto de árbol (esto es con Subversion 1.6.17, la versión más nueva en Debian al momento de escribir):

--- Merging differences between repository URLs into ''.'': A hello.en.txt C hello.txt Summary of conflicts: Tree conflicts: 1

No debería haber ningún conflicto en absoluto: la actualización debe fusionarse con el nuevo nombre del archivo. Mientras Subversion falla, Mercurial maneja esto correctamente:

rm -rf /tmp/hg-repo hg init /tmp/hg-repo cd /tmp/hg-repo echo ''Goodbye, World!'' > hello.txt hg add hello.txt hg commit -m ''Initial import.'' echo ''Hello, World!'' > hello.txt hg commit -m ''Update.'' hg update 0 hg rename hello.txt hello.en.txt hg commit -m ''Rename.'' hg merge

Antes de la fusión, el repositorio se ve así (de hg glog ):

@ changeset: 2:6502899164cc | tag: tip | parent: 0:d08bcebadd9e | user: Martin Geisler | date: Thu Apr 01 12:29:19 2010 +0200 | summary: Rename. | | o changeset: 1:9d06fa155634 |/ user: Martin Geisler | date: Thu Apr 01 12:29:18 2010 +0200 | summary: Update. | o changeset: 0:d08bcebadd9e user: Martin Geisler date: Thu Apr 01 12:29:18 2010 +0200 summary: Initial import.

El resultado de la fusión es:

merging hello.en.txt and hello.txt to hello.en.txt 0 files updated, 1 files merged, 0 files removed, 0 files unresolved (branch merge, don''t forget to commit)

En otras palabras: Mercurial tomó el cambio de la revisión 1 y lo fusionó en el nuevo nombre de archivo de la revisión 2 ( hello.en.txt ). Manejar este caso es, por supuesto, esencial para apoyar la refactorización y la refactorización es exactamente el tipo de cosa que desearás hacer en una sucursal.