Una de las ventajas de usar un DVCS es el flujo de trabajo edit-commit-merge (sobre edit-merge-commit a menudo aplicado por un CVCS). Permitir que cada cambio único se registre en el repositorio independientemente de las fusiones garantiza que el DAG refleje con precisión el verdadero pedigrí del proyecto.
¿Por qué tantos sitios web hablan de querer "evitar compromisos de fusión"? ¿La fusión previa al compromiso o el rebase posterior a la fusión hace que sea más difícil aislar regresiones, revertir cambios pasados, etc.?
Punto de aclaración: el comportamiento predeterminado para un DVCS es crear confirmaciones de fusión. ¿Por qué tantos lugares hablan de un deseo de ver un historial de desarrollo lineal que oculte estos compromisos de fusión?
fuente
git pull
)? Puedo ver por qué eso podría ser problemático, pero mi pregunta es sobre todos los commits de fusión.git pull --rebase == svn up
(suelto-igual, no estricto-igual)Respuestas:
La gente quiere evitar las confirmaciones de fusión porque hace que el registro sea más bonito. Seriamente. Parece que los registros centralizados con los que crecieron, y localmente pueden hacer todo su desarrollo en una sola rama. No hay beneficios aparte de esa estética, y varios inconvenientes además de los que mencionó, como hacer que sea propenso a conflictos extraer directamente de un colega sin pasar por el servidor "central".
fuente
En dos palabras:
git bisect
Un historial lineal le permite identificar la fuente real de un error.
Un ejemplo. Este es nuestro código inicial:
La rama 1 realiza una refactorización de tal manera que
arg
ya no es válida:Branch 2 tiene una nueva característica que necesita usar
arg
:No habrá conflictos de fusión, sin embargo, se ha introducido un error. Afortunadamente, este en particular quedará atrapado en el paso de compilación, pero no siempre tenemos tanta suerte. Si el error se manifiesta como un comportamiento inesperado, en lugar de un error de compilación, es posible que no lo encontremos durante una o dos semanas. En ese punto,
git bisect
al rescate!Oh mierda. Esto es lo que ve:
Entonces, cuando enviemos
git bisect
para encontrar el commit que rompió la compilación, identificará un commit de fusión. Bueno, eso ayuda un poco, pero esencialmente apunta a un paquete de confirmaciones, no a una sola. Todos los antepasados son verdes. Por otro lado, con rebase, obtienes un historial lineal:Ahora,
git bisect
señalará la confirmación exacta de la característica que rompió la compilación. Idealmente, el mensaje de confirmación explicará lo que se pretendía lo suficientemente bien como para hacer otra refactorización y corregir el error de inmediato.El efecto solo se agrava en proyectos grandes cuando los encargados del mantenimiento no escribieron todo el código, por lo que no necesariamente recuerdan por qué se realizó un compromiso determinado / para qué fue cada rama. Por lo tanto, determinar la confirmación exacta (y luego poder examinar las confirmaciones a su alrededor) es de gran ayuda.
Dicho esto, (actualmente) todavía prefiero fusionarme. Rebasarse en una rama de lanzamiento le dará su historial lineal para usar
git bisect
, mientras conserva el historial real para el trabajo diario.fuente
gitk --all
es extremadamente útil poder ver el flujo de confirmaciones entre las ramas (especialmente porque generalmente tengo ramas de 3 ish en cualquier momento dado, y cambio entre ellas cada día dependiendo de mi estado de ánimo en el tiempo).bisect
resultado. Es bastante fácil encontrar las confirmaciones individuales con unablame
o una diferentebisect
si desea profundizar. Con un historial lineal, la fusión se realiza fuera del libro, por lo que ya no puede darse cuenta de que una mala fusión fue el problema. Simplemente parece un idiota usando un argumento que no existe, especialmente si el argumento fue eliminado varias confirmaciones anteriores. Tratar de descubrir por qué haría eso es mucho más difícil cuando destruyes la historia.En resumen, porque la fusión es a menudo otro lugar para que algo salga mal, y solo tiene que salir mal una vez para que la gente tenga mucho miedo de volver a lidiar con eso (una vez mordido dos veces, si lo desea).
Entonces, supongamos que estamos trabajando en una nueva pantalla de administración de cuentas, y resulta que hay un error descubierto en el flujo de trabajo de Nueva cuenta. De acuerdo, tomamos dos caminos separados: finaliza la Gestión de cuentas y soluciono el error con Cuentas nuevas. Dado que ambos estamos lidiando con cuentas, hemos estado trabajando con un código muy similar, tal vez incluso tuvimos que ajustar los mismos fragmentos de código.
Ahora, en este momento tenemos dos versiones de software diferentes pero totalmente funcionales. Los dos nos hemos comprometido con nuestros cambios, los dos hemos probado nuestro código debidamente e, independientemente, estamos muy seguros de haber hecho un trabajo increíble. ¿Ahora que?
Bueno, es hora de fusionarse, pero ... mierda, ¿qué pasa ahora? Podríamos pasar de dos conjuntos de software en funcionamiento a uno, un software unificado y horriblemente roto de software con errores nuevos en el que su Gestión de cuentas no funciona y las Cuentas nuevas no funcionan y ni siquiera sé si el error anterior todavía está allí .
Tal vez el software era inteligente y decía que había un conflicto e insistió en que le demos orientación. Bueno, mierda, me siento para hacerlo y veo que has agregado un código complejo que no entiendo de inmediato. Creo que está en conflicto con los cambios que he hecho ... Te pregunto, y cuando tienes un minuto lo compruebas y ves mi código que no entiendes. Uno o ambos tenemos que tomarse el tiempo para sentarnos, hacer una fusión adecuada y posiblemente volver a probar todo el proceso para asegurarnos de no romperlo.
Mientras tanto, otros 8 chicos están cometiendo código como los sádicos que son, hice algunas pequeñas correcciones de errores y las envié antes de saber que teníamos un conflicto de fusión, y seguro que parece un buen momento para tomar un descanso, y tal vez usted están fuera por la tarde o estancados en una reunión o lo que sea. Quizás debería tomarme unas vacaciones. O cambiar de carrera.
Y así, para escapar de esta pesadilla, algunas personas tienen mucho miedo al compromiso (¿qué más hay de nuevo, de verdad?). Naturalmente, somos reacios al riesgo en escenarios como este, a menos que pensemos que apestamos y lo vamos a arruinar de todos modos, en cuyo caso las personas comienzan a actuar con un abandono imprudente. suspiro
Ahí vas. Sí, los sistemas modernos están diseñados para aliviar este dolor, y se supone que es capaz de retroceder y rebajar y degradar fácilmente y freebase y hanglide y todo eso.
Pero todo es más trabajo, y solo queremos presionar el botón en el microondas y hacer una comida de 4 platos antes de que tengamos tiempo de encontrar un tenedor, y todo se siente tan insatisfactorio: el código es trabajo, es productivo, es significativo, pero manejar con gracia una fusión simplemente no cuenta.
Los programadores, como regla, tienen que desarrollar una gran memoria de trabajo, y luego tienden a olvidar de inmediato todos esos nombres basura y variables y el alcance tan pronto como hayan terminado el problema, y manejando un conflicto de fusión (o peor, un fusión mal manejada) es una invitación para recordar su mortalidad.
fuente
Rebasing proporciona un punto de ramificación móvil que simplifica el proceso de empujar los cambios de regreso a la línea de base. Esto le permite tratar una rama de larga ejecución como si fuera un cambio local. Sin rebase, las ramas acumulan cambios desde la línea de base que se incluirán en los cambios que se fusionan de nuevo con la línea de base.
La fusión deja su línea de base en el punto de ramificación original. Si combina cambios de algunas semanas de la línea de la que se bifurcó, ahora tiene muchos cambios desde su punto de ramificación, muchos de los cuales estarán en su línea de base. Esto hace que sea difícil identificar sus cambios en su sucursal. Retrasar los cambios a la línea de base puede generar conflictos no relacionados con sus cambios. En el caso de conflictos, es posible impulsar cambios inconsistentes. Las fusiones en curso requieren un esfuerzo de gestión, y es relativamente fácil perder cambios.
Rebase mueve su punto de ramificación a la última revisión en su línea base. Cualquier conflicto que encuentre será solo por su (s) cambio (s). Empujar cambios es mucho más simple. Los conflictos se resuelven en la sucursal local haciendo un rebase adicional. En el caso de empujes conflictivos, el último en impulsar su cambio deberá resolver el problema con su cambio.
fuente
Las herramientas automatizadas están mejorando para asegurarse de que el código de fusión se compilará y ejecutará, evitando así los conflictos sintácticos , pero no pueden garantizar la ausencia de conflictos lógicos que pueden ser introducidos por las fusiones. Por lo tanto, una fusión 'exitosa' le da una sensación de falsa confianza, cuando en realidad no garantiza nada, y tiene que rehacer todas sus pruebas.
El verdadero problema con la ramificación y la fusión, según lo veo, es que patea la poderosa lata en el futuro. Te permite decir "Trabajaré en mi pequeño mundo" durante una semana y resolver los problemas que surjan más tarde. Pero corregir errores siempre es más rápido / más barato cuando están frescos. Para cuando todas las ramas del código comiencen a fusionarse, es posible que ya olvide algunos de los matices de las cosas que se hicieron.
Si se combinan los dos problemas antes mencionados, es posible que se encuentre en una situación en la que es más simple y fácil hacer que todos trabajen desde el mismo tronco y resuelvan continuamente los conflictos a medida que surgen, incluso si hacen que el desarrollo activo sea un poco más lento.
fuente
Un punto relevante adicional es este: con el rebase puedo fácilmente seleccionar o revertir una característica en mi rama de lanzamiento.
fuente
git cherry-pick -x <commit>
para aplicarlo a la rama de lanzamiento. O, podríamos querer deshacer algo para el lanzamiento, congit revert <commit>
. Si<commit>
es una fusión, se vuelve peluda. Que es posible, ya que lo que sé, pero no es fácil de hacer. Cuando se usa rebase, cada 'fusión' es un compromiso gigante pero regular, fácilmente seleccionable y revertable.No se han dicho tres cosas en ninguna de las respuestas:
Diferenciando dentro de una rama:
Fusionar un elemento a la vez:
Limpieza antes del empuje:
fuente