¿Por qué tantos proyectos prefieren "git rebase" sobre "git merge"?

69

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?

Jace Browning
fuente
18
Las herramientas no siempre lo hacen bien. Y cuando se equivocan, oh chico, ¿se equivocan?
Oded
2
@Oded ¿te refieres a los commits de fusión automática (como los creados por git pull)? Puedo ver por qué eso podría ser problemático, pero mi pregunta es sobre todos los commits de fusión.
Jace Browning
posible duplicado de ¿Son los conflictos de fusión complicados frecuentes un signo de problemas? "Las fusiones y rebases deben causar exactamente los mismos conflictos, para aquellos conflictos inherentes que un humano debe resolver (es decir, dos desarrolladores que cambian la misma línea de código) ..."
mosquito
Un pensamiento que tuve después de publicar mi respuesta, que la OMI no encaja exactamente como una respuesta: git pull --rebase == svn up(suelto-igual, no estricto-igual)
Izkata
en una tienda centralizada como SourceSafe, los cambios continuarían siendo registrados en un esfuerzo continuo, pero una vez que se alcanzara una meseta estable, etiquetaríamos todos los archivos a partir de una fecha determinada. Entonces, al menos, puede correlacionar errores con antes o después de tal y tal versión, o con una nueva línea .
Andyz Smith

Respuestas:

65

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".

Karl Bielefeldt
fuente
55
Eres el único que ha entendido mi pregunta correctamente. ¿Cómo puedo dejarlo más claro?
Jace Browning
12
Tal vez reformule a "¿Cuáles son los beneficios de un historial de desarrollo lineal?"
Karl Bielefeldt
11
"A menudo, harás esto para asegurarte de que tus commits se apliquen limpiamente en una rama remota, tal vez en un proyecto al que intentas contribuir pero que no mantienes. En este caso, harías tu trabajo en una rama y luego vuelva a basar su trabajo en origen / maestro cuando esté listo para enviar sus parches al proyecto principal. De esa manera, el responsable no tiene que hacer ningún trabajo de integración, solo un avance rápido o una solicitud limpia. " git-scm.com/book/en/Git-Branching-Rebasing
Andyz Smith
12
Hace que parezca simplemente cosmético, pero en realidad es práctico: es mucho más fácil entender lo que está sucediendo en una historia lineal que desde un DAG complicado con líneas que se cruzan y se fusionan.
Daniel Hershcovich
20
"No hay beneficios aparte de esa estética" - No sé sobre eso. Es realmente difícil entender lo que está sucediendo en su historial cuando su historial parece una placa de circuito. (Asumiendo múltiples miembros del equipo, cada uno con sus propias ramas y flujos de trabajo.)
Archagon
46

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:

def bar(arg=None):
    pass

def foo():
    bar()

La rama 1 realiza una refactorización de tal manera que argya no es válida:

def bar():
    pass

def foo():
    bar()

Branch 2 tiene una nueva característica que necesita usar arg:

def bar(arg=None):
    pass

def foo():
    bar(arg=1)

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 bisectal rescate!

Oh mierda. Esto es lo que ve:

(master: green)
|             \_________________________
|                \                      \
(master: green)  (branch 1: green)     (branch 2: green)
|                 |                     |
|                 |                     |
(master/merge commit: green)            |
|                         ______________/
|                        /
(master/merge commit: red)
|
...days pass...
|
(master: red)

Entonces, cuando enviemos git bisectpara 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:

(master: green)
|
(master: green)
|
(all branch 1 commits: green)
|
(some branch 2 commits: green)
|
(branch 2 commit: red)
|
(remaining branch 2 commits: red)
|
...days pass...
|
(master: still red)

Ahora, git bisectseñ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.

Izkata
fuente
2
Gracias por esta explicación ¿Puedes ampliar por qué aún prefieres fusionar? Sé que muchas personas prefieren fusionarse a rebase en la mayoría de las situaciones, pero nunca he podido entender por qué.
Philip
@Philip, dudo un poco en hacerlo, ya que solo he usado git durante unos meses en pequeños proyectos personales, nunca en un equipo. Pero al realizar un seguimiento de lo que he hecho recientemente, gitk --alles 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).
Izkata
16
Pero la fusión es el problema, por lo que desea una confirmación de fusión como bisectresultado. Es bastante fácil encontrar las confirmaciones individuales con una blameo una diferente bisectsi 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.
Karl Bielefeldt
1
Realmente no estoy de acuerdo con esta respuesta. Rebasar destruye la utilidad de bisect a veces. A excepción de las confirmaciones principales de una nueva versión, las confirmaciones de una nueva versión pueden no ser ejecutables. Ejemplo: se ramificó en A e hizo B, C, D, alguien empujó a E. Usted es B y C y trabaja pero rebasó en E, usted es B y C no se pueden ejecutar o se pueden ejecutar y no funcionan. El mejor caso es que te rindas, el peor caso crees que B o C contiene el problema cuando en realidad es D.
Lan
44
@Izkata ¿Por qué estás usando bisect? Porque ocurren errores / errores. Es una realidad humillante. Tal vez "arrastrar el mouse hacia la izquierda" no era parte de la lista de pruebas automatizadas / manuales antes y ahora nos damos cuenta de que esa característica está rota pero sabemos que solía funcionar.
Lan
17

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.

BrianH
fuente
55
Increíblemente redactado, señor!
Southpaw Hare
10
Esto responde por qué las personas tienen miedo de fusionarse, no por qué los compromisos de fusión son considerados malos por tantos.
Jay
23
El problema con esta respuesta es que esto sigue siendo cierto para un rebase . Después de todo, una nueva versión todavía se está fusionando donde ha cambiado una línea de código que ya había cambiado. Creo que este es un problema con la forma en que se planteó la pregunta.
deworde
2
Voté en contra de esta respuesta porque, como todavía es elocuente, está más allá del punto, en mi humilde opinión.
Eugene
66
Esto no parece acertar en rebase vs merge-merge en absoluto.
Andyz Smith
5

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.

BillThor
fuente
1
así que todo lo que hace es degradar los nuevos cambios a un nivel que se consideran "lo incorrecto" porque son el único cambio . Si todos los cambios anteriores se incluyen en la fusión, todos los cambios están en una base nivelada, con respecto a si son "lo correcto" hoy.
Andyz Smith
@AndyzSmith No consideraría que los nuevos cambios son incorrectos . Cuando se trata de determinar qué se está cambiando y se debe impulsar, son lo correcto . Una vez que haya cambiado el nombre, puede ignorar los cambios anteriores, ya que son parte de su línea de base.
BillThor
correcto, así que si no está seguro de cuál es su línea de base, es decir, si lo que ha cambiado ya está listo para fusionarse es "lo correcto", entonces no rebase.
Andyz Smith
Y, si alguien le presiona un cambio y rompe la construcción porque su prototipo de función no coincide con un prototipo existente, usted sabe instantáneamente, debido a la nueva versión, que el nuevo tipo está equivocado. no tiene que adivinar que tal vez el nuevo tipo tenga el prototipo correcto y que los cambios de changset anteriores, no fundamentados y no aplicados, sean aquellos con el prototipo incorrecto.
Andyz Smith
4

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.

Señor Fox
fuente
44
Esto responde por qué las personas tienen miedo de fusionarse, no por qué los compromisos de fusión son considerados malos por tantos.
Jay
Entonces, este tronco al que te refieres, ¿eso implica rebase? Expound por favor.
Andyz Smith
@AndyzSmith "tronco" es el término de svn para su equivalente a la rama "maestra" de git
Izkata
@Izkata, ¿entonces esta respuesta aboga por no usar ni fusión ni rebase?
Andyz Smith
@AndyzSmith Sí, así es como lo leí también. Y no estoy de acuerdo con eso; después de trabajar en un equipo con otros 7 desarrolladores de la manera sugerida, no lo sugiero. Deberíamos haber usado ramas de características más de lo que lo hicimos, pero la mayoría de ellas tienen miedo de fusionarse (como BrianDHall aborda en su respuesta).
Izkata
0

Un punto relevante adicional es este: con el rebase puedo fácilmente seleccionar o revertir una característica en mi rama de lanzamiento.

Bruno Schäpper
fuente
1
Esta elaborado en esto?
Akavall
Claro :-) En git flow, regularmente creamos nuevas ramas de lanzamiento. Si después de la creación, se soluciona un error en la rama de desarrollo, usamos git cherry-pick -x <commit>para aplicarlo a la rama de lanzamiento. O, podríamos querer deshacer algo para el lanzamiento, con git 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.
Bruno Schäpper
0

No se han dicho tres cosas en ninguna de las respuestas:

  • Diferenciando dentro de una rama:

    • La diferenciación entre un par arbitrario de commits en una rama se vuelve extremadamente difícil cuando hay commits de fusión.
  • Fusionar un elemento a la vez:

    • Cuando resuelve la diferencia entre dos ramas, una fusión generalmente ocurre de una vez, y cualquier conflicto de fusión que le quede por hacer sin el contexto de qué compromiso específico fue responsable del conflicto. Si reescribe, la repetición se detendrá en el punto en que ocurrió el conflicto y le permitirá resolverlo en ese contexto.
  • Limpieza antes del empuje:

    • Si cometió un error de confirmación que luego necesita corregir, si no ha presionado rebase interactivo, le permitirá combinar / dividir / cambiar / mover confirmaciones. Si bien aún puede hacerlo si se ha fusionado, se vuelve muy difícil si desea combinar / dividir / cambiar / mover a través de un límite de fusión.
Catskul
fuente