¿Qué modelos de ramificación de Git funcionan para usted?

379

Actualmente, nuestra empresa está utilizando un modelo de ramificación simple de troncal / lanzamiento / revisión y desea recibir asesoramiento sobre qué modelos de ramificación funcionan mejor para su empresa o proceso de desarrollo.

  1. Flujos de trabajo / modelos de ramificación

    A continuación se encuentran las tres descripciones principales de esto que he visto, pero se contradicen parcialmente entre sí o no van lo suficientemente lejos como para resolver los problemas posteriores que hemos encontrado (como se describe a continuación). Por lo tanto, nuestro equipo hasta el momento utiliza soluciones no tan buenas. ¿Estás haciendo algo mejor?

  2. Fusión vs rebase (historia enredada vs secuencial)

    ¿Debería uno pull --rebaseo esperar con la fusión a la línea principal hasta que su tarea haya finalizado? Personalmente, me inclino por la fusión, ya que esto conserva una ilustración visual de en qué base se inició y finalizó una tarea, e incluso prefiero merge --no-ffpara este propósito. Sin embargo, tiene otros inconvenientes. Además, muchos no se han dado cuenta de la propiedad útil de la fusión: que no es conmutativa (fusionar una rama de tema en maestro no significa fusionar maestro en la rama de tema).

  3. Estoy buscando un flujo de trabajo natural.

    Algunas veces los errores ocurren porque nuestros procedimientos no capturan una situación específica con reglas simples. Por ejemplo, una solución necesaria para versiones anteriores debería, por supuesto, basarse lo suficientemente corriente abajo como para ser posible fusionarla en todas las ramas necesarias (¿es lo suficientemente claro el uso de estos términos?). Sin embargo, sucede que una solución llega al maestro antes de que el desarrollador se dé cuenta de que debería haberse colocado más abajo, y si eso ya se ha empujado (aún peor, fusionado o algo basado en él), la opción restante es la selección de cereza, con Sus peligros asociados. ¿Qué reglas simples como esas usas?También en esto se incluye la incomodidad de una rama temática excluyendo necesariamente otras ramas temáticas (suponiendo que se ramifiquen a partir de una línea de base común). Los desarrolladores no quieren terminar una característica para comenzar otra sintiendo que el código que acaban de escribir ya no existe.

  4. ¿Cómo evitar crear conflictos de fusión (debido a la selección de cereza)?

    Lo que parece una forma segura de crear un conflicto de fusión es elegir entre ramas, ¿nunca se pueden volver a fusionar? ¿Aplicar la misma confirmación en revertir (¿cómo hacer esto?) En cualquier rama posiblemente resolvería esta situación? Esta es una razón por la que no me atrevo a impulsar un flujo de trabajo basado en gran medida en la fusión.

  5. ¿Cómo descomponerse en ramas tópicas?

    Nos damos cuenta de que sería increíble reunir una integración completa a partir de ramas temáticas, pero a menudo el trabajo de nuestros desarrolladores no está claramente definido (a veces tan simple como "hurgar") y si algún código ya ha entrado en un tema "misceláneo", no se puede sacar de allí nuevamente, de acuerdo con la pregunta anterior? ¿Cómo trabaja con la definición / aprobación / graduación / liberación de sus ramas temáticas?

  6. Por supuesto, los procedimientos adecuados, como la revisión del código y la graduación, serían encantadores.

    Pero simplemente no podemos mantener las cosas lo suficientemente desenredadas para manejar esto, ¿alguna sugerencia? ramas de integración, ilustraciones?

A continuación se muestra una lista de preguntas relacionadas:

También revise lo que Plastic SCM escribe sobre el desarrollo impulsado por tareas , y si Plastic no es su elección, estudie el modelo de ramificación de nvie y sus scripts de soporte .

HiQ CJ
fuente
2
Ja, gracias, de hecho lo ha hecho ... De hecho, he leído la mayoría de eso ... cosas :-). Es algo por lo que soy conocido: no conformarse con la solución mediocre, sino continuar buscando la solución perfecta. A menudo esto es un error, pero en este caso hay mucho en juego y las soluciones disponibles son demasiado desordenadas o pobres que necesito seguir buscando. Así que decidí enumerar todos los problemas que tengo con él.
HiQ CJ
El blog Plastic SCM lanzó su opinión en la discusión, es al menos perspicaz: codicesoftware.blogspot.com/2010/08/…
HiQ CJ
1
Debe
Doppelganger
1
@Doppelganger Me interesaría cómo específicamente --no-ff supuestamente está contribuyendo al problema descrito en el enlace que publica. Para mí, el problema descrito es la falla de la bisección con las confirmaciones de los puntos de control, y la falla de git culpa por ayudar en ese caso, pero no veo cómo "--no-ff" cambia algo, en lugar de no usarlo. El autor se queja de que una fusión con --no-ff no modifica un archivo; sin embargo, sin él, el archivo tampoco se modificaría, solo vería la confirmación anterior en su historial, ¿verdad?
codeling
Otro modelo de ramificación: modelo de cactus barro.github.io/2016/02/… , modelo principal bitsnbites.eu/a-stable-mainline-branching-model-for-git . Este modelo de ramificación ofrece otro enfoque que no es gitflow.
Mathieu Momal

Respuestas:

91

La característica más problemática que los nuevos desarrolladores de DVCS deben tener en cuenta es sobre el proceso de publicación :

  • puede importar (recuperar / extraer) cualquier repositorio remoto que necesite
  • puede publicar (push) en cualquier repositorio (desnudo) que desee

A partir de eso, puede respetar algunas reglas para facilitar sus preguntas:

Ahora:

Flujos de trabajo / modelos de ramificación :

cada flujo de trabajo está ahí para soportar un proceso de administración de versiones , y eso se adapta a cada proyecto.
Lo que puedo agregar al flujo de trabajo que menciona es: cada desarrollador no debe crear una rama de características, solo una rama de "desarrollo actual", porque la verdad es que el desarrollador a menudo no sabe qué producirá exactamente su rama: una característica, varias (porque terminó siendo una característica demasiado compleja), ninguna (porque no estaba lista a tiempo para el lanzamiento), otra característica (porque la original se había "transformado"), ...

Solo un "integrador" debe establecer ramas de funciones oficiales en un repositorio "central", que luego los desarrolladores pueden buscar para volver a crear / fusionar la parte de su trabajo que se ajuste a esa función.

Fusión vs rebase (historia enredada vs secuencial) :

Me gusta mi respuesta que mencionas (" Descripción del flujo de trabajo para el uso de git para el desarrollo interno ")

Estoy buscando un flujo de trabajo natural :

para los arreglos, puede ayudar asociar cada arreglo con un ticket de un seguimiento de errores, lo que ayuda al desarrollador a recordar dónde (es decir, en qué rama, es decir, una rama dedicada "para arreglos") él / ella debe cometer tales modificaciones.
Luego, los ganchos pueden ayudar a proteger un repositorio central contra los empujes de correcciones de errores no validadas o de ramas desde las cuales no se debe empujar. (no hay una solución específica aquí, todo esto debe adaptarse a su entorno)

¿Cómo evitar crear conflictos de fusión (debido a la selección de cereza)?

Según lo declarado por Jakub Narębski en su respuesta , la recolección de cerezas debe reservarse para situaciones raras donde se requiere.
Si su configuración implica una gran selección de cerezas (es decir, "no es raro"), entonces algo está apagado.

Aplicar el mismo commit en revertir (¿cómo hacer esto?)

git revert debería ocuparse de eso, pero eso no es lo ideal.

¿Cómo descomponerse en ramas tópicas?

Mientras una rama aún no haya sido empujada a todas partes, un desarrollador debe reorganizar su historial de confirmaciones (una vez que finalmente vea que el desarrollo toma una forma más definitiva y estable) en:

  • varias ramas si es necesario (una por característica claramente identificada)
  • un conjunto coherente de confirmaciones dentro de una rama (consulte Recortar registros de Git )

¿Procedimientos adecuados como revisión de código y graduación?

El repositorio de ramas de integración (en una integración dedicada) puede ayudar al desarrollador a:

  • volver a basar su desarrollo en la parte superior de esa rama de integración remota (pull --rebase)
  • resolver localmente
  • impulsar el desarrollo a ese repositorio
  • consulte con el integrador que no resulte en un desastre;)
VonC
fuente
@UncleCJ: como puede ver, esta no es exactamente una respuesta final a su "pregunta final";)
VonC
Entiendo, y también tengo un buen sentido de la ironía, está bien ;-)
HiQ CJ
3
@UncleCJ upstream es justo donde usted regularmente extrae, desde mi publicación, donde terminan todos los commits (la versión de lanzamiento o troncal en lenguaje SVN). Río abajo están todos debajo de ellos. Enviar cosas en sentido ascendente es el proceso de fusionarlo en el repositorio de lanzamiento (como linux-2.6) y en sentido descendente son los cambios a partir de ahí, o desde su repositorio, como dice el gerente de desarrollo de dicha función para sus secuaces ... I equipo malo
2
@UncleCJ: "Todavía me resulta difícil recortar sus registros para lograr un historial estrictamente secuencial": es más fácil con Git1.7 y es rebase --interactive --autosquashque moverá automáticamente todas las confirmaciones con el mismo comienzo de otro mensaje de confirmación. Si esas confirmaciones usan un número de ticket (por ejemplo), incluso si esas correcciones relacionadas con ese ticket no se hicieron secuencialmente en ese momento, el autoquash permite un reordenamiento rápido de esas confirmaciones.
VonC
1
@UncleCJ: "historia estrictamente secuencial (¿es esto necesario o no ?!"): no siempre es necesario, pero ayuda a mantener el rastro de dependencias funcionales ( stackoverflow.com/questions/881092/… ) y conflictos semánticos ( stackoverflow.com/questions / 2514502 / ... )
VonC
21

Creo, y podría estar equivocado, que una de las cosas que más se malinterpreta sobre git es su naturaleza distribuida. Esto hace que sea muy diferente decir subversión en las formas en que puede trabajar, aunque puede imitar el comportamiento SVN si lo desea. El problema es que cualquier flujo de trabajo servirá, lo cual es excelente pero también engañoso.

Si entiendo bien el desarrollo del kernel (me centraré en eso), todos tienen su propio repositorio git para desarrollar el kernel. Hay un repositorio, linux-2.6.git, cuidado por Torvalds, que actúa como repositorio de lanzamiento. Las personas clonan desde aquí si desean comenzar a desarrollar una característica contra la rama de "lanzamiento".

Otros repositorios hacen algún desarrollo. La idea es clonar desde linux-2.6, ramificarse tantas veces como desee hasta el punto en que tenga una función "nueva" que funcione. Luego, cuando esté listo, puede ponerlo a disposición de alguien considerado confiable, que extraerá esta rama de su repositorio en la suya y la fusionará en la corriente principal. En el kernel de Linux, esto sucede en varios niveles (lugartenientes de confianza) hasta que alcanza linux-2.6.git, en cuyo punto se convierte en "el kernel".

Ahora aquí es donde se vuelve confuso. Los nombres de las ramas no necesitan ser consistentes en todos los repositorios. Entonces puedo git pull origin master:vanilla-codey obtener una rama del originmaestro de 's en una rama en mi repositorio llamada vanilla-code. Siempre que sepa lo que está sucediendo, realmente no importa: se distribuye en el sentido de que todos los repositorios son iguales entre sí y no solo se comparten en varias computadoras como SVN.

Entonces, con todo esto en mente:

  1. Creo que depende de cada programador cómo hacen su ramificación. Todo lo que necesita es un repositorio central para administrar versiones, etc. Troncal podría ser head. Las versiones pueden ser etiquetas o ramas y las revisiones son probablemente ramas en sí mismas. De hecho, probablemente haría lanzamientos como ramas para que puedas seguir parchándolos.
  2. Me fusionaría y no rebasaría. Si por ejemplo se toma un repositorio, clon de ella, rama y hace algo de dev, a continuación, tire de su origindebe, en su repositorio, probablemente haría otra rama y combinar la última masteren yourbranchpara que otra persona puede tirar de sus cambios con el menor esfuerzo posible. Muy raramente hay una necesidad de rebase verdaderamente, en mi experiencia.
  3. Creo que es un caso de entender la forma en que funciona Git y lo que puede hacer. Lleva un tiempo y mucha buena comunicación: solo realmente comencé a entender lo que estaba sucediendo cuando comencé a usar git con otros desarrolladores e incluso ahora, algunas cosas de las que no estoy seguro.
  4. Los conflictos de fusión son útiles. Lo sé, lo sé, quieres que todo funcione, pero el hecho es que el código cambia y necesitas fusionar los resultados en algo que funcione. Los conflictos de fusión son, de hecho, solo más programación. Nunca he encontrado una explicación fácil de qué hacer con ellos, así que aquí está: tenga en cuenta los archivos que tienen conflictos de fusión, vaya y cámbielos a lo que deberían ser, git add .y luego git commit.
  5. Sin embargo, se adapta. Como he dicho, el repositorio de git de cada usuario es propio para jugar y los nombres de las ramas no tienen por qué ser iguales . Si tuviera un repositorio provisional, por ejemplo, podría aplicar un esquema de nombres, pero no es necesario para cada desarrollador, solo en el repositorio de lanzamiento.
  6. Esta es la etapa de fusión. Solo se fusiona en las ramas de lanzamiento, etc., cuando considera que el código debe revisarse / pasar pruebas de calidad.

Espero que eso ayude. Me doy cuenta de que VonC acaba de publicar una explicación muy similar ... ¡No puedo escribir lo suficientemente rápido!

Edite algunas ideas adicionales sobre cómo usar git en un entorno comercial, ya que esto parece relevante para el OP de los comentarios:

  • El repositorio de lanzamiento, lo llamaremos product.git, es accesible por varios programadores senior / técnicos responsables de cuidar el producto en sí. Son análogos al papel de los mantenedores en OSS.
  • Estos programadores probablemente también lideren en parte el desarrollo de nuevas versiones, por lo que también podrían codificarse y mantener varios repositorios. Es posible que administren depósitos de almacenamiento para características realmente nuevas y también pueden tener sus propios repositorios.
  • Debajo de ellos hay programadores responsables de desarrollar bits individuales. Por ejemplo, alguien podría ser responsable del trabajo de la interfaz de usuario. Por lo tanto, gestionan el repositorio UI.git.
  • Debajo de ellos están los programadores reales que desarrollan las características como su trabajo completo del día a día.

¿Así que lo que sucede? Bueno, todo el mundo saca al comienzo de cada día de la fuente "aguas arriba", es decir, el repositorio de lanzamiento (que probablemente también contendrá el material más reciente del desarrollo de los días anteriores). Todos hacen esto, directamente. Esto irá en una rama en su repositorio, probablemente llamado "maestro" o tal vez si usted me llama "último". El programador entonces hará algo de trabajo. Este trabajo puede ser algo de lo que no están seguros, por lo que hacen una rama, hacen el trabajo. Si no funciona, pueden eliminar la rama y volver. Si es así, tendrán que fusionarse con la rama principal en la que están trabajando actualmente. Diremos que este es un programador de UI trabajando, latest-uiasí que lo git checkout latest-uisiguegit merge abc-ui-mywhizzynewfeature. Luego le dice a su líder técnico (el líder de la interfaz de usuario) oye, he completado esa tarea, retíreme. Así lo hace el líder de la interfaz de usuario git pull user-repo lastest-ui:lastest-ui-suchafeature-abc. El líder de la interfaz de usuario luego lo mira en esa rama y dice, en realidad, eso es muy bueno, lo fusionaré ui-latest. Luego podría decirle a todos los que están debajo de él que tomen de él en sus ui-latestramas o cualquier nombre que les hayan dado, y así los desarrolladores exploran la característica. Si el equipo está contento, el líder de la interfaz de usuario puede pedirle al líder de prueba que se retire de él y combine los cambios. Esto se propaga a todo el mundo (después del cambio) que lo prueba y envía informes de errores, etc. Finalmente, si la función pasa las pruebas, etc., uno de los principales líderes técnicos podría fusionarlo con la copia de trabajo actual del programa, en ese momento Todos los cambios se propagan de nuevo hacia abajo. Y así.

No es una forma de trabajo "tradicional" y está diseñada para ser "dirigida por pares" en lugar de "jerárquica" como SVN / CVS. En esencia, todos tienen acceso de compromiso, pero solo localmente. Es el acceso al repositorio y el repositorio que designe como repositorio de lanzamiento lo que le permite usar la jerarquía.


fuente
Muchas gracias por su extensa respuesta (y votos), lo leeré un par de veces más para extraer información útil. Sin embargo, somos una empresa, no un comité de desarrollo de OSS ;-), y tengo que ayudar a mis desarrolladores con pautas más claras que "juguetear como quieran en su propio repositorio". Veamos a dónde lleva esta publicación, siento un buen impulso, ¡sigan llegando!
HiQ CJ
@VonC Gracias. @UncleCJ es cierto, pero sí, estoy seguro, tiene gestores de lanzamiento, etc. Cualquier persona con acceso al repositorio puede hacer estas cosas. En cuanto al desarrollo, ¿por qué no dar a los desarrolladores la libertad, dentro de lo razonable, de ramificarse? Siempre que tenga algún protocolo para acordar fusiones y sus repositorios centrales se nombren como desee, no hay problema. Dicho esto, un esquema de nombres común no es una mala idea. Tiendo a usar initials-version-feature-subbranches para ramas personales y version para ramas.
@UncleCJ He agregado un ejemplo de cómo podría funcionar en una empresa. Esencialmente, los roles de OSS se reemplazan con gerentes, pero se entiende la idea. Tiene el beneficio adicional sobre SVN de que sus desarrolladores también pueden trabajar sin conexión (solo necesitan la red para tirar / empujar) y creo que hace que sea más fácil probar las funciones, si lo implementa bien.
Wow, ese es realmente un gran ejemplo, podemos comenzar a usar algo así para la graduación. No quise decir tanto que, dado que no estamos haciendo OSS, todos tenemos que ser regulados, en realidad somos un equipo bastante pequeño y plano, pero tenemos que tratar de colaborar de manera eficiente en un horario apretado y también aprender como equipo . Por eso estoy aquí haciendo estas estúpidas preguntas para poder ayudar al resto del equipo más tarde :-). También me di cuenta de #git que la línea de base mal definida combinada con la presión para acortar los plazos de entrega nos está haciendo tropezar ... volveremos más tarde.
HiQ CJ
Eso es bastante justo: he estado allí recientemente, que es exactamente cómo tomé ese ejemplo, probándolo y fallando mucho ... y también adaptándome a las formas de trabajo de algunos proyectos OSS. Supongo que el verdadero problema es que no importa cómo se ramifique y dónde estén sus repositorios ... ¡puede definirlos de la forma que desee, lo que fue una verdadera sorpresa para mí de todos modos! Pero te permite hacer algunas cosas interesantes. De todos modos, buena suerte y diviértete!
9

Un modelo que he usado con buenos resultados es el siguiente:

Un repositorio "bendecido" que todos empujan y sacan, básicamente, una topología cliente-servidor.

No hay una rama maestra, por lo que ningún desarrollador puede insertar ningún código en la "línea principal".

Todos los desarrollos ocurren en ramas temáticas. Colocamos nombres de espacios de nombres para detectar fácilmente quién es responsable de ello: jn / newFeature o jn / issue-1234

También hay un mapeo casi 1 a 1 entre ramas y tarjetas kanban / scrum en la pizarra.

Para liberar una rama, se empuja al repositorio bendecido y la tarjeta kanban se mueve a lista para su revisión.

Luego, si la sucursal es aceptada por la revisión, es un candidato para una liberación.

Un lanzamiento ocurre cuando un conjunto de ramas aceptadas se fusionan y se etiquetan con un número de versión.

Al colocar la nueva etiqueta en el repositorio bendecido, existe una nueva base posible para nuevas funciones.

Para evitar conflictos de fusión, se solicita a los desarrolladores que actualicen (fusionen) sus ramas inéditas a la última etiqueta de lanzamiento.

John Nilsson
fuente
2

Personalmente, trato de mantener solo el código listo para el lanzamiento en la rama maestra.

Cuando trabajo en una nueva función o corrección de errores, lo hago en una rama. También realizo pruebas unitarias en la sucursal. Si todo funciona bien, solo entonces fusiono / rebaso en master.

Intento usar las convenciones de denominación de ramas comunes también, como:

  • bugfix / recursive_loop
  • bugfix / sql_timeout
  • feature / new_layout
  • característica / búsqueda_ mejorado
xero
fuente