Ramas nombradas vs múltiples repositorios

130

Actualmente estamos usando subversion en una base de código relativamente grande. Cada versión tiene su propia rama, y ​​las correcciones se realizan contra el tronco y se migran a las ramas de liberación utilizandosvnmerge.py

Creo que ha llegado el momento de pasar a un mejor control de la fuente, y he estado jugando con Mercurial por un tiempo.

Parece que hay dos escuelas de pensamiento sobre la gestión de dicha estructura de lanzamiento utilizando Mercurial. O bien cada versión tiene su propio repositorio, y las correcciones se realizan contra la rama de la versión y se envían a la rama principal (y a cualquier otra rama de versión más nueva). O bien, usando ramas con nombre dentro de un único repositorio (o varias copias coincidentes).

En cualquier caso, parece que podría estar usando algo como un trasplante para realizar cambios de cereza para su inclusión en las ramas de liberación.

Te pido; ¿Cuáles son los méritos relativos de cada enfoque?

James Emerton
fuente

Respuestas:

129

La mayor diferencia es cómo se registran los nombres de las sucursales en el historial. Con las ramas con nombre, el nombre de la rama se incrusta en cada conjunto de cambios y, por lo tanto, se convertirá en una parte inmutable de la historia. Con los clones no habrá un registro permanente de dónde vino un conjunto de cambios en particular.

Esto significa que los clones son excelentes para experimentos rápidos en los que no desea registrar el nombre de una rama, y ​​las ramas con nombre son buenas para las ramas a largo plazo ("1.x", "2.x" y similares).

Tenga en cuenta también que un único repositorio puede acomodar fácilmente múltiples ramas livianas en Mercurial. Tales ramas en el repositorio pueden marcarse para que pueda encontrarlas fácilmente nuevamente. Digamos que ha clonado el repositorio de la compañía cuando se veía así:

[a] --- [b]

Hackeas y haces [x]y [y]:

[a] --- [b] --- [x] --- [y]

Es decir, mientras alguien pone [c]y [d]entra en el repositorio, así que cuando tire obtendrá un gráfico de historia como este:

            [x] --- [y]
           / /
[a B C D]

Aquí hay dos cabezas en un solo repositorio. Su copia de trabajo siempre reflejará un único conjunto de cambios, la llamada copia de trabajo del conjunto de cambios principal. Comprueba esto con:

% hg parents

Digamos que informa [y]. Puedes ver las cabezas con

% hg heads

y esto informará [y]y [d]. Si desea actualizar su repositorio a un pago limpio de [d], simplemente haga (sustituya [d]con el número de revisión [d]):

% hg update --clean [d]

Luego verá ese hg parentsinforme [d]. Esto significa que su próximo commit tendrá [d]como padre. Por lo tanto, puede corregir un error que haya notado en la rama principal y crear un conjunto de cambios [e]:

            [x] --- [y]
           / /
[a B C D e]

Para empujar [e]solo el conjunto de cambios , debe hacer

% hg push -r [e]

¿Dónde [e]está el hash changeset? Por defecto hg push, simplemente comparar los repositorios y ver que [x], [y]y [e]están desaparecidos, pero es posible que no quieren compartir [x]y [y]todavía.

Si la corrección de errores también le afecta, desea fusionarla con su rama de características:

% hg update [y]
% hg merge

Eso dejará su gráfico de repositorio con el siguiente aspecto:

            [x] --- [y] ----------- [z]
           / /
[a B C D e]

donde [z]es la fusión entre [y]y [e]. También podrías haber optado por tirar la rama:

% hg strip [x]

Mi punto principal de esta historia es esta: un solo clon puede representar fácilmente varias pistas de desarrollo. Esto siempre ha sido cierto para "hg simple" sin usar ninguna extensión. Sin embargo, la extensión de marcadores es de gran ayuda. Le permitirá asignar nombres (marcadores) a conjuntos de cambios. En el caso anterior, querrá un marcador en su cabeza de desarrollo y uno en la cabeza ascendente. Los marcadores se pueden empujar y extraer con Mercurial 1.6 y se han convertido en una característica incorporada en Mercurial 1.8.

Si hubiera optado por hacer dos clones, su clon de desarrollo se habría visto así después de hacer [x]y [y]:

[a] --- [b] --- [x] --- [y]

Y su clon aguas arriba contendrá:

[a] --- [b] --- [c] --- [d]

Ahora notas el error y lo arreglas. Aquí no tiene que hacerlo, hg updateya que el clon ascendente está listo para usar. Usted se compromete y crea [e]:

[a] --- [b] --- [c] --- [d] --- [e]

Para incluir la corrección de errores en su clon de desarrollo, tire de ella allí:

[a] --- [b] --- [x] --- [y]
           \
            [c] --- [d] --- [e]

y fusionar:

[a] --- [b] --- [x] --- [y] --- [z]
           \ /
            [c] --- [d] --- [e]

El gráfico puede verse diferente, pero tiene la misma estructura y el resultado final es el mismo. Usando los clones, tenías que hacer un poco menos de contabilidad mental.

Las ramas con nombre realmente no entraron en escena aquí porque son bastante opcionales. Mercurial se desarrolló utilizando dos clones durante años antes de cambiar a ramas con nombre. Mantenemos una rama llamada 'estable' además de la rama 'predeterminada' y hacemos nuestros lanzamientos basados ​​en la rama 'estable'. Consulte la página de ramificación estándar en la wiki para obtener una descripción del flujo de trabajo recomendado.

Martin Geisler
fuente
1
si el conjunto de cambios provenía de un usuario diferente, eso se habría registrado, por lo que usar clones no es nada malo. Al impulsar una nueva función, a menudo no es interesante saber que lo hizo desde un repositorio separado. También hay una extensión localbranch, que le da una rama local solamente. Útil cuando se clona el repositorio está asociado con altos costos (tiempo / espacio).
Johannes Rudolph, el
2
haciendo referencia a: 'los clones son excelentes para experimentos rápidos' - ¡No, no lo son! ¿Qué pasa si tienes algunos miles de archivos en repositorio? La clonación llevará años (en cualquier momento por encima de 1 minuto), mientras que el cambio de rama solo un momento (<1 segundo). Seguir usando ramas con nombre contaminará el registro de cambios. ¿No es un callejón sin salida? ¿O me falta algo?
seler
Esta bien seler; Suena como una modificación a su argumento original; Los clones son buenos cuando la sobrecarga de múltiples copias completas no es importante para usted, o cuando puede usar enlaces simbólicos / enlaces duros de hg para mitigar el costo de copias de trabajo locales separadas por rama.
Warren P
@seler: tienes razón en que los clones no son prácticos si el código es grande. Los marcadores son la solución entonces.
Martin Geisler
29

Creo que quieres toda la historia en un repositorio. Engendrar un repositorio a corto plazo es para experimentos a corto plazo, no para eventos importantes como lanzamientos.

Una de las decepciones de Mercurial es que no parece haber una manera fácil de crear una rama de corta duración, jugar con ella, abandonarla y recoger la basura. Las ramas son para siempre. Simpatizo con nunca querer abandonar la historia, pero las ramas súper baratas y desechables son una gitcaracterística que realmente me gustaría ver hg.

Norman Ramsey
fuente
20
Puede crear fácilmente una rama de este tipo de características: "actualizar hg" a su punto de ramificación, editar y "confirmar hg". Acaba de crear una línea de desarrollo divergente: nuevos compromisos ampliarán esta rama. Use "hg clone -r" para deshacerse de él, o quítelo en línea con "hg strip". Por lo tanto, no se decepcione o venga a las listas de correo de Mercurial con sus solicitudes de funciones.
Martin Geisler
8
Parece que hg stripes lo que quiero. ¿Por qué no se pueden eliminar las sucursales de reclamos de documentación en línea?
Norman Ramsey
11
Consulte también esta publicación de blog para obtener una explicación sobre cómo Mercurial tiene, de alguna manera, sucursales más baratas que git: stevelosh.com/blog/entry/2009/8/30/…
Martin Geisler
9
Puede cerrar una rama con nombre hg ci --close-branch.
Andrey Vlasovskikh
3
@ Norman Ramsey: cuando las personas dicen que las ramas no se pueden eliminar, significan que no se puede cambiar el nombre de la rama incrustado en los conjuntos de cambios. Un cambio nos establece un no en una rama, define una rama. Tendrá que eliminar el conjunto de cambios y volver a crearlo con un nombre de rama diferente si desea "moverlo" a una rama diferente.
Martin Geisler
14

Deberías hacer las dos cosas .

Comience con la respuesta aceptada de @Norman: use un repositorio con una rama con nombre por versión.

Luego, tenga un clon por rama de lanzamiento para compilar y probar.

Una nota clave es que incluso si usa múltiples repositorios, debe evitar usar transplantpara mover conjuntos de cambios entre ellos porque 1) cambia el hash y 2) puede introducir errores que son muy difíciles de detectar cuando hay cambios conflictivos entre el conjunto de cambios que trasplante y la rama objetivo. En su lugar, desea hacer la fusión habitual (y sin premezcla: siempre inspeccione visualmente la fusión), lo que dará como resultado lo que @mg dijo al final de su respuesta:

El gráfico puede verse diferente, pero tiene la misma estructura y el resultado final es el mismo.

De manera más detallada, si usa múltiples repositorios, el repositorio "troncal" (o predeterminado, principal, desarrollo, lo que sea) contiene TODOS los conjuntos de cambios en TODOS los repositorios. Cada repositorio de versión / rama es simplemente una rama en el tronco, todas fusionadas de una manera u otra de nuevo al tronco, hasta que desee dejar una versión anterior. Por lo tanto, la única diferencia real entre ese repositorio principal y el repositorio único en el esquema de rama con nombre es simplemente si las ramas se nombran o no.

Eso debería hacer obvio por qué dije "comenzar con un repositorio". Ese único repositorio es el único lugar que necesitarás buscar cualquier conjunto de cambios en cualquier lanzamiento . Puede etiquetar más conjuntos de cambios en las ramas de lanzamiento para el control de versiones. Es conceptualmente claro y simple, y simplifica la administración del sistema, ya que es lo único que tiene que estar disponible y recuperarse todo el tiempo.

Pero aún necesita mantener un clon por rama / lanzamiento que necesita construir y probar. Es lo más trivial posible hg clone <main repo>#<branch> <branch repo>, y luegohg pull en el repositorio de rama solo se extraerán nuevos conjuntos de cambios en esa rama (más los conjuntos de cambios ancestrales en las ramas anteriores que se fusionaron).

Esta configuración se adapta mejor al modelo de confirmación de kernel de Linux de un solo extractor (no se siente bien actuar como Lord Linus. En nuestra empresa llamamos al integrador de roles ), ya que el repositorio principal es lo único que los desarrolladores necesitan para clonar y el tirador necesita entrar. El mantenimiento de los repositorios de sucursales es puramente para la administración de versiones y puede ser completamente automatizado. Los desarrolladores nunca necesitan sacar / empujar a los repositorios de la rama.


Aquí está el ejemplo de @ mg presentado para esta configuración. Punto de partida:

[a] - [b]

Haga una rama con nombre para una versión de lanzamiento, diga "1.0", cuando llegue a la versión alfa. Comprometa correcciones de errores:

[a] - [b] ------------------ [m1]
         \                 /
          (1.0) - [x] - [y]

(1.0)no es un conjunto de cambios real ya que la rama con nombre no existe hasta que se confirma. (Puede realizar una confirmación trivial, como agregar una etiqueta, para asegurarse de que las ramas con nombre se creen correctamente).

La fusión [m1] es la clave de esta configuración. A diferencia de un repositorio de desarrolladores en el que puede haber un número ilimitado de cabezas, NO desea tener varias cabezas en su repositorio principal (excepto para la rama antigua de lanzamiento muerto como se mencionó anteriormente). Por lo tanto, siempre que tenga nuevos conjuntos de cambios en las ramas de lanzamiento, debe fusionarlos nuevamente con la rama predeterminada (o una rama de lanzamiento posterior) de inmediato. Esto garantiza que cualquier corrección de errores en una versión también se incluya en todas las versiones posteriores.

Mientras tanto, el desarrollo en la rama predeterminada continúa hacia la próxima versión:

          ------- [c] - [d]
         /
[a] - [b] ------------------ [m1]
         \                 /
          (1.0) - [x] - [y]

Y, como de costumbre, debe fusionar las dos cabezas en la rama predeterminada:

          ------- [c] - [d] -------
         /                         \
[a] - [b] ------------------ [m1] - [m2]
         \                 /
          (1.0) - [x] - [y]

Y este es el clon de la rama 1.0:

[a] - [b] - (1.0) - [x] - [y]

Ahora es un ejercicio agregar la próxima rama de lanzamiento. Si es 2.0, definitivamente se ramificará por defecto. Si es 1.1, puede optar por bifurcar 1.0 o por defecto. En cualquier caso, cualquier nuevo conjunto de cambios en 1.0 debe fusionarse primero con la siguiente rama y luego, por defecto. Esto se puede hacer automáticamente si no hay conflicto, lo que resulta simplemente en una fusión vacía.


Espero que el ejemplo aclare mis puntos anteriores. En resumen, las ventajas de este enfoque son:

  1. Repositorio autorizado único que contiene el conjunto completo de cambios y el historial de versiones.
  2. Gestión de versiones clara y simplificada.
  3. Flujo de trabajo claro y simplificado para desarrolladores e integradores.
  4. Facilite las iteraciones del flujo de trabajo (revisiones de código) y la automatización (fusión automática vacía).

ACTUALIZACIÓN hg hace esto : el repositorio principal contiene las ramas predeterminadas y estables, y el repositorio estable es el clon de rama estable. Sin embargo, no usa una rama versionada, ya que las etiquetas de versión a lo largo de la rama estable son lo suficientemente buenas para sus propósitos de administración de versiones.

Geoffrey Zheng
fuente
5

La mayor diferencia, hasta donde yo sé, es algo que ya has dicho: las ramificaciones nombradas están en un único repositorio. Las ramas con nombre tienen todo a mano en un solo lugar. Los repositorios separados son más pequeños y fáciles de mover. La razón por la que hay dos escuelas de pensamiento sobre esto es que no hay un ganador claro. Cualquiera de los argumentos del lado que tenga más sentido para usted es probablemente con el que debe ir, porque es probable que su entorno sea más similar al suyo.

dwc
fuente
2

Creo que es claramente una decisión pragmática dependiendo de la situación actual, por ejemplo, el tamaño de una función / rediseño. Creo que las bifurcaciones son realmente buenas para que los contribuyentes con roles aún no comprometidos se unan al equipo de desarrolladores al demostrar su aptitud con una sobrecarga técnica descuidada.

thSoft
fuente
0

Realmente recomendaría no usar ramas con nombre para las versiones. Para eso son realmente las etiquetas. Las ramas con nombre están destinadas a diversiones duraderas, como unstable rama.

Entonces, ¿por qué no solo usar etiquetas? Un ejemplo básico:

  • El desarrollo ocurre en una sola rama
  • Cada vez que se crea un lanzamiento, lo etiqueta en consecuencia
  • El desarrollo continúa desde allí
  • Si tiene algunos errores que corregir (o lo que sea) en una determinada versión, simplemente actualice a su etiqueta, realice sus cambios y confirme

Eso creará una nueva cabeza sin nombre en la defaultrama, también conocido como. una rama anónima, que está perfectamente bien en hg. En cualquier momento, puede fusionar las confirmaciones de corrección de errores en la pista de desarrollo principal. No hay necesidad de ramas con nombre.

DanMan
fuente
Esto depende en gran medida de su proceso. Una aplicación web, por ejemplo, funciona bien con una jerarquía de rama estable / prueba / desarrollo. Al crear software de escritorio, generalmente tenemos una rama de desarrollo (predeterminada), así como una a tres (!) Ramas diferentes en mantenimiento. Es difícil predecir cuándo tendremos que volver a visitar una sucursal, y hay una cierta elegancia en hacer que una sucursal rastree una versión major.minor.
James Emerton