Mi oficina está tratando de descubrir cómo manejamos las divisiones y fusiones de sucursales, y nos hemos encontrado con un gran problema.
Nuestro problema es con las ramas laterales a largo plazo, del tipo en el que hay algunas personas que trabajan en una rama lateral que se divide del maestro, que desarrollamos durante unos meses, y cuando alcanzamos un hito sincronizamos los dos.
Ahora, en mi humilde opinión, la forma natural de manejar esto es aplastar la rama lateral en una sola confirmación. master
sigue progresando hacia adelante; como debería: no estamos volcando retroactivamente meses de desarrollo paralelo en master
la historia de. Y si alguien necesita una mejor resolución para la historia de la rama lateral, bueno, por supuesto, todo sigue ahí, simplemente no está master
, está en la rama lateral.
Aquí está el problema: trabajo exclusivamente con la línea de comando, pero el resto de mi equipo usa GUIS. Y he descubierto que el GUIS no tiene una opción razonable para mostrar el historial de otras ramas. Entonces, si llega a una confirmación de squash, diciendo "este desarrollo aplastado desde la rama XYZ
", es un gran dolor ir a ver qué hay dentro XYZ
.
En SourceTree, por lo que puedo encontrar, es un gran dolor de cabeza: si está conectado master
y desea ver el historial master+devFeature
, debe verificar master+devFeature
(tocar cada archivo que sea diferente), o bien desplácese por un registro que muestre TODAS las ramas de su repositorio en paralelo hasta que encuentre el lugar correcto. Y buena suerte para averiguar dónde estás allí.
Mis compañeros de equipo, con toda razón, no quieren tener un historial de desarrollo tan inaccesible. Por lo tanto, quieren que se fusionen estas grandes y largas ramas de desarrollo lateral, siempre con un compromiso de fusión. No quieren ningún historial al que no se pueda acceder inmediatamente desde la rama maestra.
Me gusta esa idea; significa una maraña interminable e inevitable de la historia paralela del desarrollo. Pero no veo qué alternativa tenemos. Y estoy bastante desconcertado; Esto parece bloquear casi todo lo que sé sobre una buena administración de sucursales, y será una frustración constante para mí si no puedo encontrar una solución.
¿Tenemos alguna opción aquí además de fusionar constantemente ramas laterales en master con merge-commits? O, ¿hay alguna razón por la que constantemente usar merge-commits no sea tan malo como me temo?
fuente
git log master+bugfixDec10th
ser el punto de ruptura: - /merge --no-ff
lo que quieres Enmaster
sí mismo, tiene una confirmación que describe lo que cambió en la rama, pero todas las confirmaciones todavía existen y están relacionadas con HEAD.Respuestas:
Aunque uso Git en la línea de comando, tengo que estar de acuerdo con tus colegas. No es sensato aplastar grandes cambios en una sola confirmación. Estás perdiendo la historia de esa manera, no solo haciéndola menos visible.
El punto de control de la fuente es rastrear el historial de todos los cambios. ¿Cuándo cambió qué por qué? Con ese fin, cada confirmación contiene punteros a las confirmaciones principales, una diferencia y metadatos como un mensaje de confirmación. Cada confirmación describe el estado del código fuente y el historial completo de todos los cambios que llevaron a ese estado. El recolector de basura puede eliminar confirmaciones que no son accesibles.
Acciones como rebase, selección de cerezas o aplastar borrar o reescribir el historial. En particular, las confirmaciones resultantes ya no hacen referencia a las confirmaciones originales. Considera esto:
[1]: Algunos servidores Git permiten que las ramas estén protegidas contra la eliminación accidental, pero dudo que quieras mantener todas tus ramas características por la eternidad.
Ahora ya no puede buscar esa confirmación, simplemente no existe.
Hacer referencia a un nombre de sucursal en un mensaje de confirmación es aún peor, ya que los nombres de sucursal son locales para un repositorio. Lo que está
master+devFeature
en su caja local puede estardoodlediduh
en el mío. Las ramas son solo etiquetas móviles que apuntan a algún objeto de confirmación.De todas las técnicas de reescritura de historial, el rebase es el más benigno porque duplica los commits completos con todo su historial, y simplemente reemplaza un commit padre.
Que la
master
historia incluya la historia completa de todas las ramas que se fusionaron es algo bueno, porque eso representa la realidad. [2] Si hubo un desarrollo paralelo, eso debería ser visible en el registro.[2]: Por esta razón, también prefiero las confirmaciones de fusión explícitas sobre la historia linealizada pero en última instancia falsa resultante del rebase.
En la línea de comando, se
git log
esfuerza por simplificar el historial que se muestra y mantener todas las confirmaciones mostradas relevantes. Puede ajustar la simplificación del historial para satisfacer sus necesidades. Es posible que sienta la tentación de escribir su propia herramienta de registro git que recorra el gráfico de confirmación, pero generalmente es imposible responder "¿esta confirmación se confirmó originalmente en esta o aquella rama?". El primer padre de una confirmación de fusión es el anteriorHEAD
, es decir, la confirmación en la rama en la que se está fusionando. Pero eso supone que no hizo una fusión inversa de maestro en la rama de características, luego avanzó rápidamente maestro a la fusión.La mejor solución para las sucursales a largo plazo que he encontrado es evitar las sucursales que solo se fusionan después de un par de meses. La fusión es más fácil cuando los cambios son recientes y pequeños. Idealmente, se fusionará al menos una vez por semana. La integración continua (como en Extreme Programming, no como en "configuremos un servidor Jenkins"), incluso sugiere múltiples fusiones por día, es decir, no para mantener ramas de características separadas sino compartir una rama de desarrollo como equipo. La fusión antes de que una característica sea QA requiere que la característica esté oculta detrás de un indicador de característica.
A cambio, la integración frecuente hace posible detectar problemas potenciales mucho antes y ayuda a mantener una arquitectura consistente: son posibles cambios de gran alcance porque estos cambios se incluyen rápidamente en todas las ramas. Si un cambio rompe algún código, solo romperá un par de días de trabajo, no un par de meses.
La reescritura de la historia puede tener sentido para proyectos verdaderamente grandes cuando hay múltiples millones de líneas de código y cientos o miles de desarrolladores activos. Es cuestionable por qué un proyecto tan grande tendría que ser un único repositorio git en lugar de dividirse en bibliotecas separadas, pero a esa escala es más conveniente si el repositorio central solo contiene "versiones" de los componentes individuales. Por ejemplo, el kernel de Linux emplea el aplastamiento para mantener manejable el historial principal. Algunos proyectos de código abierto requieren que los parches se envíen por correo electrónico, en lugar de una fusión a nivel de git.
fuente
git bisect
, solo para que llegue a un uber-commit de 10,000 líneas desde una rama lateral.Me gusta la respuesta de Amon , pero sentí que una pequeña parte necesitaba mucho más énfasis: puede simplificar fácilmente el historial mientras visualiza registros para satisfacer sus necesidades, pero otros no pueden agregar el historial mientras visualiza registros para satisfacer sus necesidades. Por eso es preferible mantener la historia tal como ocurrió.
Aquí hay un ejemplo de uno de nuestros repositorios. Usamos un modelo de solicitud de extracción, por lo que cada característica se parece a sus ramas de larga duración en el historial, a pesar de que generalmente solo se ejecutan una semana o menos. Los desarrolladores individuales a veces eligen aplastar su historial antes de fusionarse, pero a menudo combinamos características, por lo que es relativamente inusual. Aquí están los pocos commits principales
gitk
, la interfaz gráfica de usuario que viene incluida con git:Sí, un poco enredado, pero también nos gusta porque podemos ver con precisión quién tuvo los cambios a qué hora. Refleja con precisión nuestra historia de desarrollo. Si queremos ver una vista de nivel superior, una solicitud de extracción fusionada a la vez, podemos ver la siguiente vista, que es equivalente al
git log --first-parent
comando:git log
tiene muchas más opciones diseñadas para brindarte con precisión las vistas que deseas.gitk
puede tomar cualquiergit log
argumento arbitrario para construir una vista gráfica. Estoy seguro de que otras GUI tienen capacidades similares. Lea los documentos y aprenda a usarlos correctamente, en lugar de imponer sugit log
vista preferida a todos en el momento de la fusión.fuente
merge --no-ff
- no use fusiones de avance rápido, en su lugar siempre cree una confirmación de fusión para que--first-parent
tenga algo con lo que trabajarMi primer pensamiento es: ni siquiera hagas esto a menos que sea absolutamente necesario. Tus fusiones deben ser desafiantes a veces. Mantenga las ramas independientes y lo más efímeras posible. Es una señal de que necesita dividir sus historias en fragmentos de implementación más pequeños.
En el caso de que tenga que hacer esto, entonces es posible fusionarse en git con la opción --no-ff para que las historias se mantengan distintas en su propia rama. Las confirmaciones seguirán apareciendo en el historial combinado, pero también se pueden ver por separado en la rama de características para que al menos sea posible determinar de qué línea de desarrollo formaron parte.
Tengo que admitir que cuando comencé a usar git, me pareció un poco extraño que las confirmaciones de rama aparecieran en el mismo historial que la rama principal después de la fusión. Fue un poco desconcertante porque no parecía que esos compromisos pertenecieran a esa historia. Pero en la práctica, no es algo realmente doloroso, si se considera que la rama de integración es solo eso: todo su propósito es combinar las ramas de características. En nuestro equipo, no aplastamos, y hacemos compromisos de fusión frecuentes. Usamos --no-ff todo el tiempo para asegurarnos de que sea fácil ver el historial exacto de cualquier característica si queremos investigarla.
fuente
git merge --no-ff
git merge --no-ff
. Si usa gitflow, esto se convierte en el predeterminado.Déjame responder tus puntos directa y claramente:
Por lo general, no desea dejar sus ramas sin sincronización durante meses.
Su rama de características se ha ramificado de algo dependiendo de su flujo de trabajo; vamos a llamarlo
master
por simplicidad. Ahora, cada vez que te comprometes a dominar, puedes y debesgit checkout long_running_feature ; git rebase master
. Esto significa que sus ramas están, por diseño, siempre sincronizadas.git rebase
También es lo correcto hacer aquí. No es un truco o algo extraño o peligroso, sino completamente natural. Pierde un poco de información, que es el "cumpleaños" de la rama de características, pero eso es todo. Si alguien considera que eso es importante, podría proporcionarlo guardándolo en otro lugar (en su sistema de tickets o, si la necesidad es grande, en ungit tag
...).No, absolutamente no quieres eso, quieres un commit de fusión. Una confirmación de fusión también es una "confirmación única". De alguna manera, no inserta todos los commits de rama individuales "en" maestro. Es un compromiso único con dos padres: el
master
jefe y el jefe de la sucursal en el momento de la fusión.Asegúrese de especificar la
--no-ff
opción, por supuesto; fusionarse sin--no-ff
, en su caso, debería estar estrictamente prohibido. Lamentablemente,--no-ff
no es el predeterminado; pero creo que hay una opción que puede configurar que lo haga así. Veagit help merge
lo que--no-ff
hace (en resumen: activa el comportamiento que describí en el párrafo anterior), es crucial.Absolutamente no: nunca está volcando algo "en la historia" de alguna sucursal, especialmente no con un compromiso de fusión.
Con un commit de fusión, todavía está allí. No en el maestro, sino en la rama lateral, claramente visible como uno de los padres del compromiso de fusión, y guardado por la eternidad, como debería ser.
¿Ves lo que he hecho? Todas las cosas que describe para su confirmación de squash están ahí con la
--no-ff
confirmación de fusión .(Comentario lateral: también trabajo casi exclusivamente con la línea de comandos (bueno, eso es mentira, generalmente uso emacs magit, pero esa es otra historia: si no estoy en un lugar conveniente con mi configuración individual de emacs, prefiero el comando línea también). Pero, por favor, hágase un favor y probar al menos
git gui
una vez. es por lo tanto más eficiente para recoger trozos de líneas, etc., para añadir / deshacer añade.)Eso es porque lo que estás tratando de hacer está totalmente en contra del espíritu de
git
.git
se construye desde el núcleo en un "gráfico acíclico dirigido", lo que significa que hay mucha información en la relación padre-hijo de los commits. Y, para las fusiones, eso significa verdaderos compromisos de fusión con dos padres y un hijo. Las GUI de sus colegas estarán bien tan pronto como use lasno-ff
confirmaciones de fusión.Sí, pero eso no es un problema de la GUI, sino de la confirmación de squash. El uso de una calabaza significa que deja la cabeza de la rama característica colgando y creando una nueva confirmación
master
. Esto rompe la estructura en dos niveles, creando un gran desastre.Y tienen toda la razón. Pero no se "fusionaron en ", que se acaba de fusionarse. Una fusión es algo realmente equilibrado, no tiene un lado preferido que se fusione "en" el otro (
git checkout A ; git merge B
es exactamente el mismo que,git checkout B ; git merge A
excepto por pequeñas diferencias visuales, como las ramas que se intercambian,git log
etc.).Lo cual es completamente correcto. En un momento en que no hay características no fusionadas, tendrías una sola rama
master
con un rico historial que encapsula todas las líneas de confirmación de características que alguna vez hubo, volviendo a lagit init
confirmación desde el principio de los tiempos (ten en cuenta que evité específicamente usar el término " ramas "en la última parte de ese párrafo porque el historial en ese momento ya no es" ramas ", aunque el gráfico de compromiso sería bastante ramificado).Entonces te dolerá un poco, ya que estás trabajando en contra de la herramienta que estás utilizando. El
git
enfoque es muy elegante y poderoso, especialmente en el área de ramificación / fusión; si lo hace bien (como se aludió anteriormente, especialmente con--no-ff
) es a pasos agigantados superiour a otros enfoques (por ejemplo, el desorden de subversión de tener estructuras de directorio paralelas para ramas).Sin fin, paralelo, sí.
Innavigable, maraña - no.
¿Por qué no trabajar
git
todos los días como lo hacen el inventor de sus colegas y el resto del mundo?No hay otras opciones; No es tan malo.
fuente
Aplastar una rama lateral a largo plazo te haría perder mucha información.
Lo que haría es tratar de volver a formar al maestro en la rama lateral a largo plazo antes de fusionar la rama lateral en el maestro . De esa manera, mantiene cada commit en master, mientras hace que el historial de commit sea lineal y más fácil de entender.
Si no pudiera hacer eso fácilmente en cada confirmación, lo dejaría ser no lineal , para mantener claro el contexto de desarrollo. En mi opinión, si tengo una fusión problemática durante la reubicación del maestro en la rama lateral, significa que la no linealidad tiene un significado en el mundo real. Eso significa que será más fácil entender lo que sucedió en caso de que necesite profundizar en la historia. También obtengo el beneficio inmediato de no tener que hacer un rebase.
fuente
rebase
. Si es o no el mejor enfoque aquí (personalmente no he tenido grandes experiencias con él, aunque no lo he usado mucho), definitivamente parece ser lo más parecido a lo que OP realmente parece querer, específicamente, un compromiso entre un volcado de historial fuera de servicio y ocultar completamente ese historial.Personalmente, prefiero hacer mi desarrollo en una bifurcación, luego extraer solicitudes para fusionarlas en el repositorio principal.
Eso significa que si quiero volver a basar mis cambios en la parte superior del maestro, o aplastar algunas confirmaciones de WIP, puedo hacerlo totalmente. O simplemente puedo solicitar que toda mi historia se fusione también.
Lo que me gusta hacer es hacer mi desarrollo en una rama, pero frecuentemente rebase contra master / dev. De esa forma obtengo los cambios más recientes de master sin tener un montón de commits de fusión en mi rama, o tener que lidiar con una carga completa de conflictos de fusión cuando es el momento de fusionar con master.
Para responder explícitamente a su pregunta:
Sí, puede fusionarlos una vez por rama (cuando la función o corrección está "completa") o si no le gusta que la fusión se confirme en su historial, simplemente puede hacer una fusión de avance rápido en master después de hacer una nueva versión final.
fuente
El control de revisión es basura en la basura.
El problema es que el trabajo en progreso en la rama de características puede contener una gran cantidad de "intentemos esto ... no, eso no funcionó, lo reemplazamos con eso" y todos los commits excepto el final "que" acaban contaminando la historia inútilmente.
En última instancia, se debe mantener el historial (parte de él podría ser útil en el futuro), pero solo se debe combinar una "copia limpia".
Con Git, esto se puede hacer bifurcando la rama de la característica primero (para mantener todo el historial), luego (interactivamente) rebase la rama de la rama de la característica del maestro y luego fusionando la rama rebaseada.
fuente
git
y son locales. Lo que se nombrafoo
en un repositorio se puede nombrarbar
en uno diferente. Y están destinados a ser temporales: no desea saturar su repositorio con miles de ramas de características que deben mantenerse vivas para evitar perder el historial. Y necesitaría mantener esas ramas para mantener el historial como referencia, yagit
que eventualmente eliminará cualquier confirmación que ya no sea accesible por una rama.