tortoise subversion repositorio funciona desvincular crear como carpeta svn git version-control mercurial merge

subversion - tortoise svn server



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

He escuchado en algunos lugares que una de las razones principales por las que los sistemas de control de versiones distribuidos brillan, es mucho mejor que en las herramientas tradicionales como SVN. ¿Es esto realmente debido a diferencias inherentes en cómo funcionan los dos sistemas, o las implementaciones específicas de DVCS como Git / Mercurial simplemente tienen algoritmos de fusión más inteligentes que SVN?


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

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

Lea el artículo here .


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 combinación, por lo que era incapaz de realizar futuras combinaciones sin la ayuda del usuario, que necesitaba proporcionar información que SVN no había registrado. Con 1.5 mejoró, y de hecho el modelo de almacenamiento SVN es un poco más capaz que el DAG de Git. Pero SVN almacenó la información de combinación en una forma bastante complicada que permite que las combinaciones tomen mucho más tiempo que en Git. He observado factores de 300 en el tiempo de ejecución.

Además, SVN afirma que hace un seguimiento de los nombres para ayudar a las combinaciones de archivos movidos. Pero en realidad aún los almacena como una copia y una acción de eliminación por separado, y el algoritmo de fusión aún tropieza con ellos en situaciones de modificación / cambio de nombre, es decir, cuando un archivo se modifica en una rama y se renombra en la otra, y esas ramas son ser fusionado. Dichas situaciones seguirán produciendo conflictos de combinación falsos, y en el caso de que el directorio cambie el nombre, incluso puede llevar a una pérdida silenciosa de modificaciones. (La gente de SVN luego tiende a señalar que las modificaciones aún están en la historia, 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 nombres sino que los resuelve después del hecho (en el momento de la fusión), y lo hace de manera bastante mágica.

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

Pero después de todo esto (lo que en mi humilde opinión muestra una falta de comprensión de lo que están haciendo), sería (muy bien, soy) advertencias para usar SVN en cualquier escenario de bifurcación no trivial, y lo ideal sería tratar 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 las capacidades de fusión (pero para la usabilidad). Además, las "tiendas Git cambian mientras que las tiendas SVN (algo diferente)" están fuera del punto. Git almacena conceptualmente cada confirmación como un árbol separado (como un archivo tar ), y luego utiliza bastante heurística para almacenar eso de manera eficiente. El cálculo de los cambios entre dos confirmaciones es independiente de la implementación de almacenamiento. Lo que sí es cierto es que Git almacena el historial DAG de una forma mucho más sencilla que SVN hace su mergeinfo. Cualquiera que intente entender esto último sabrá lo que quiero decir.

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. Prácticamente es mejor fusionar.


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

Git utiliza un algoritmo de combinación de 3 vías de forma predeterminada, que consiste en encontrar un ancestro común de las cabezas que se fusionan y hacer uso del conocimiento que existe en ambos lados de la combinación. Esto permite a Git ser más inteligente para evitar conflictos.

Git también tiene un código sofisticado para encontrar nombres, lo que también ayuda. No almacena los conjuntos de cambios ni almacena ninguna información de seguimiento; simplemente almacena el estado de los archivos en cada confirmación y utiliza heurísticas para localizar los cambios de nombre y 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 expone ningún seguimiento).


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 funcionaba la ramificación y fusión en Subversion hace un tiempo. Subversion anterior a 1.5.0 no almacenaba ninguna información sobre cuándo se fusionaban las sucursales, por lo tanto, cuando quería fusionar tenía que especificar qué rango de revisiones debían fusionarse.

Entonces, ¿por qué se fusiona Subversion apesta ?

Reflexiona sobre este ejemplo:

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

Cuando queremos merge los cambios de b1 en el troncal, emitimos el siguiente comando, mientras estamos parados en una carpeta que tiene el troncal desprotegido:

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

... que intentará fusionar los cambios de b1 en su directorio de trabajo local. Y luego confirma los cambios después de resolver cualquier conflicto y probar el resultado. Cuando confirmes 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 la versión crece, ya que Subversion no tenía ningún metadato sobre cuándo y qué revisiones se fusionaron. Reflexiona sobre lo que pasa 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 albergue una copia del tronco, pero no almacena ninguna información sobre cuándo y qué. las cosas se fusionaron de nuevo. Esto llevará a conflictos de fusión desagradables a veces. Lo que fue aún peor es que Subversion utilizó 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 ancestro común.

Para mitigar esto, Subversion ahora almacena metadatos para ramificar y fusionar. Eso solucionaría todos los problemas ¿verdad?

Y oh, 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 grave ruido cognitivo. Cuantas más ramas agregues, más basura podrás ver.

Cuantas más sucursales públicas tengas en un repositorio, más difícil será realizar un seguimiento de todas las diferentes sucursales. Entonces, la pregunta que tendrá es si la rama todavía está en desarrollo o si está realmente muerta, lo que es difícil de detectar en cualquier sistema de control de versiones centralizado.

La mayoría de las veces, por lo que he visto, una organización usará de manera predeterminada una gran sucursal de todos modos. Lo que es una vergüenza porque a su vez será difícil hacer un seguimiento de las versiones de prueba y lanzamiento, y todo lo demás viene de la ramificación.

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

Hay una razón muy simple por la que: 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 trabajar simplemente con la sincronización de repositorios (es decir, empujar y tirar ).

Lo primero que haces cuando trabajas con un DVCS es clonar repositorios ( clone de git, clone hg y branch de bzr). La clonación es conceptualmente lo mismo que crear una rama en el control de versiones. Algunos lo llaman bifurcación o bifurcación (aunque esta última a menudo también se usa para referirse a sucursales coubicadas), pero es lo mismo. Cada usuario ejecuta su propio repositorio, lo que significa que tiene una bifurcación por usuario activa .

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, es decir, un gráfico que no tiene ningún ciclo). Realmente no es necesario centrarse en los detalles de un DAG que no sea que cada confirmación tenga una o más referencias principales (en las que se basó la confirmación). Por lo tanto, los siguientes gráficos mostrarán las flechas entre las revisiones al revés debido a esto.

Un ejemplo muy simple de fusión sería este; Imagine un repositorio central llamado origin y un usuario, Alice, clonando el repositorio a 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 en Alice exactamente como fueron (lo cual es validado por los identificadores de hash identificables de forma única), y marca dónde se encuentran las ramas del origen.

Alice luego 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 debe hacer el repositorio de origin es tomar todas las nuevas revisiones 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 la combinación de algoritmos, ya que el algoritmo de combinación de tres vías es prácticamente el mismo entre todos los sistemas de control de versiones. El tema es más sobre la estructura que nada .

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, por lo que vamos a hacer uno mucho más retorcido aunque uno más común. ¿Recuerdas que el origin comenzó con tres revisiones? Bueno, el tipo que los hizo, llamémosle Bob , ha estado trabajando por su cuenta y cometió 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 repositorio de origin . La forma en que el sistema detecta esto es verificando si las revisiones de Bob descienden directamente de las de origin , lo que en este caso no lo hace. Cualquier intento de empujar resultará en que el sistema diga algo como " Uh ... Me temo que no puedo dejar que hagas eso Bob ".

Así que Bob tiene que jalar y luego fusionar los cambios (con el jalón de git; o el jalear y pull de hg; o el bzr''s merge ). Este es un proceso de dos pasos. Primero, Bob tiene que buscar las nuevas revisiones, que las copiarán tal como son desde el repositorio de origin . Ahora podemos ver que la gráfica 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 comprometerse con el resultado:

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

Es de esperar que la combinación no tenga conflictos (si los anticipa, puede realizar los dos pasos manualmente en git con fetch y merge ). Lo que se necesita hacer más adelante es volver a introducir esos cambios en el origin , lo que se traducirá en una combinación de avance rápido, ya que la confirmación de la fusión es un descendiente directo de lo último en el repositorio de origin :

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 fusionar en git y hg, llamada rebase , que moverá los cambios de Bob después de los cambios más recientes. Como no quiero que esta respuesta sea más detallada, le dejaré que lea los documentos de git , mercurial o bazaar sobre eso en su lugar.

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

También está el problema de enviar parches entre cada desarrollador, que fue un gran problema en Subversion que se mitigó en git, hg y bzr por revisiones identificables de forma única. Una vez que alguien ha fusionado sus cambios (es decir, ha realizado una confirmación de fusión) y los envía a todos los demás miembros del equipo para que los consuman, ya sea empujando 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, en cambio, un DAG, permite que la bifurcació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.


Leí la respuesta aceptada. Es simplemente un error.

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

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

Convierta su SVN en el formulario de Git equivalente, hágalo en Git y luego verifique el resultado, quizás utilizando múltiples confirmaciones, algunas ramas adicionales. Si puedes imaginar una forma automatizada de convertir un problema de SVN en un problema de 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 la cuarta información que está disponible en Git / Mercurial / DVC que no está disponible en SVN / VC centralizado.

¡Eso no quiere decir que no sean mejores herramientas!


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

Sigo usando 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?


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 update antes de poder realizar la confirmación. 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 reset o hg update -C ), porque no hay un compromiso al que volver. Si la combinación no es trivial, esto significa que no puede continuar trabajando en su función antes de haber limpiado el resultado de la combinación.

Pero entonces, tal vez eso sea solo una ventaja para las personas que son demasiado tontas para usar sucursales separadas (si recuerdo bien, solo tuvimos una sucursal que se usó para el desarrollo en la empresa donde usé SVN).