Preguntas de flujo de trabajo de Git y rebase vs fusión

971

He estado usando Git ahora durante un par de meses en un proyecto con otro desarrollador. Tengo varios años de experiencia con SVN , así que supongo que llevo mucho equipaje a la relación.

He oído que Git es excelente para ramificar y fusionar, y hasta ahora, simplemente no lo veo. Claro, la ramificación es muy simple, pero cuando trato de fusionarme, todo se va al infierno. Ahora, estoy acostumbrado a eso desde SVN, pero me parece que acabo de cambiar un sistema de versiones por debajo del par por otro.

Mi compañero me dice que mis problemas surgen de mi deseo de fusionarme de todas formas, y que debería usar rebase en lugar de fusionar en muchas situaciones. Por ejemplo, aquí está el flujo de trabajo que ha establecido:

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature
git checkout master
git merge my_new_feature

Esencialmente, cree una rama de características, SIEMPRE rebase de maestro a rama y fusione de la rama de nuevo a maestro. Es importante tener en cuenta que la sucursal siempre permanece local.

Aquí está el flujo de trabajo con el que comencé

clone remote repository
create my_new_feature branch on remote repository
git checkout -b --track my_new_feature origin/my_new_feature
..work, commit, push to origin/my_new_feature
git merge master (to get some changes that my partner added)
..work, commit, push to origin/my_new_feature
git merge master
..finish my_new_feature, push to origin/my_new_feature
git checkout master
git merge my_new_feature
delete remote branch
delete local branch

Hay dos diferencias esenciales (creo): siempre uso la combinación en lugar de la reorganización, y empujo mi rama de características (y mis confirmaciones de ramas de características) al repositorio remoto.

Mi razonamiento para la rama remota es que quiero hacer una copia de seguridad de mi trabajo mientras estoy trabajando. Nuestro repositorio se respalda automáticamente y se puede restaurar si algo sale mal. Mi computadora portátil no es, o no tan a fondo. Por lo tanto, odio tener código en mi computadora portátil que no se refleje en otro lugar.

Mi razonamiento para la fusión en lugar de rebase es que la fusión parece ser estándar y rebase parece ser una característica avanzada. Mi intuición es que lo que estoy tratando de hacer no es una configuración avanzada, por lo que el rebase debería ser innecesario. Incluso he leído el nuevo libro de programación pragmática en Git, y cubren la fusión extensamente y apenas mencionan rebase.

De todos modos, estaba siguiendo mi flujo de trabajo en una sucursal reciente, y cuando intenté fusionarlo nuevamente con master, todo se fue al infierno. Hubo toneladas de conflictos con cosas que no deberían haber importado. Los conflictos simplemente no tenían sentido para mí. Me llevó un día resolver todo, y finalmente culminó en un empuje forzado hacia el maestro remoto, ya que mi maestro local resolvió todos los conflictos, pero el remoto aún no estaba contento.

¿Cuál es el flujo de trabajo "correcto" para algo como esto? Se supone que Git hace que la ramificación y la fusión sean súper fáciles, y simplemente no lo veo.

Actualizar 15/04/2011

Esta parece ser una pregunta muy popular, así que pensé en actualizar con mis dos años de experiencia desde la primera vez que pregunté.

Resulta que el flujo de trabajo original es correcto, al menos en nuestro caso. En otras palabras, esto es lo que hacemos y funciona:

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature, commit
git rebase master
git checkout master
git merge my_new_feature

De hecho, nuestro flujo de trabajo es un poco diferente, ya que tendemos a hacer fusiones de squash en lugar de combinaciones sin procesar. ( Nota: Esto es controvertido, ver más abajo ) . Esto nos permite convertir toda nuestra rama de características en un solo commit en master. Luego eliminamos nuestra rama de características. Esto nos permite estructurar lógicamente nuestros commits en master, incluso si son un poco desordenados en nuestras ramas. Entonces, esto es lo que hacemos:

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature, commit
git rebase master
git checkout master
git merge --squash my_new_feature
git commit -m "added my_new_feature"
git branch -D my_new_feature

Controversia de fusión de squash : como han señalado varios comentaristas, la fusión de squash desechará todo el historial en su rama característica. Como su nombre lo indica, aplasta todos los commits en uno solo. Para características pequeñas, esto tiene sentido ya que lo condensa en un solo paquete. Para funciones más grandes, probablemente no sea una gran idea, especialmente si sus confirmaciones individuales ya son atómicas. todo se reduce a la preferencia personal.

Github y Bitbucket (¿otros?) Solicitudes de extracción: en caso de que se pregunte cómo se relaciona la fusión / rebase con las solicitudes de extracción, le recomiendo que siga todos los pasos anteriores hasta que esté listo para volver a fusionarse con el maestro. En lugar de fusionarse manualmente con git, solo acepta el PR. Tenga en cuenta que esto no hará una fusión de squash (al menos no de manera predeterminada), pero no es squash ni avance rápido es la convención de fusión aceptada en la comunidad Pull Request (que yo sepa). Específicamente, funciona así:

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature, commit
git rebase master
git push # May need to force push
...submit PR, wait for a review, make any changes requested for the PR
git rebase master
git push # Will probably need to force push (-f), due to previous rebases from master
...accept the PR, most likely also deleting the feature branch in the process
git checkout master
git branch -d my_new_feature
git remote prune origin

He llegado a amar a Git y nunca quiero volver a SVN. Si estás luchando, solo quédate y eventualmente verás la luz al final del túnel.

Micah
fuente
31
Desafortunadamente, el nuevo libro de Programación Pragmstic está escrito principalmente usando Git sin dejar de pensar en SVN, y en este caso te ha engañado. En Git, rebase mantiene las cosas simples cuando pueden ser. Su experiencia podría decirle que su flujo de trabajo no funciona en Git, no que Git no funcione.
Paul el
18
No recomendaría la fusión de squash en este caso, ya que no guarda información sobre lo que se fusiona (al igual que svn, pero no mergeinfo aquí).
Marius K
77
Me encanta la nota en la parte inferior, tuve una experiencia similar de lucha con Git, pero ahora lucho por imaginar no usarla. Gracias por la explicación final también, ayudó mucho con la rebasecomprensión
Jon Phenow
66
Una vez que haya terminado la función, ¿no debería volver a crear una nueva base antes de fusionar new_feature con master?
Softarn
17
Su flujo de trabajo pierde todo el historial de confirmación de la rama eliminada :(
Max Nanasy

Respuestas:

372

"Conflictos" significa "evoluciones paralelas de un mismo contenido". Entonces, si se va "al infierno" durante una fusión, significa que tienes evoluciones masivas en el mismo conjunto de archivos.

La razón por la cual un rebase es mejor que una fusión es que:

  • reescribe su historial de confirmación local con el del maestro (y luego vuelve a aplicar su trabajo, resolviendo cualquier conflicto)
  • la fusión final será sin duda una de "avance rápido", ya que tendrá todo el historial de confirmaciones del maestro, además de solo sus cambios para volver a aplicar.

Confirmo que el flujo de trabajo correcto en ese caso (evoluciones en un conjunto común de archivos) es primero volver a redactar, luego fusionar .

Sin embargo, eso significa que, si empuja su rama local (por razones de respaldo), esa rama no debe ser extraída (o al menos utilizada) por nadie más (ya que el historial de confirmación será reescrito por la sucesiva rebase).


Sobre ese tema (rebase luego fusionar flujo de trabajo), barraponto menciona en los comentarios dos publicaciones interesantes, ambas de randyfay.com :

Usando esta técnica, su trabajo siempre va por encima de la rama pública como un parche que está actualizado con el actual HEAD.

( existe una técnica similar para el bazar )

VonC
fuente
27
Para ver una técnica que permite la reorganización y el intercambio, consulte softwareswirl.blogspot.com/2009/04/…
mhagger
2
randyfay.com/node/91 y randyfay.com/node/89 son lecturas maravillosas. Estos artículos me hicieron comprender lo que funcionaba con mi flujo de trabajo y lo que sería un flujo de trabajo ideal.
Capi Etheriel
solo para aclararlo, el cambio de la rama maestra a su local es básicamente para actualizar cualquier historial que su local haya perdido y que el maestro tenga conocimiento después de cualquier tipo de fusión.
hellatan
@dtan lo que describo aquí es rebase local en la parte superior del maestro. No actualiza exactamente el historial local, sino que vuelve a aplicar el historial local sobre el maestro para resolver cualquier conflicto dentro de la rama local.
VonC
386

TL; DR

Un flujo de trabajo git rebase no lo protege de las personas que son malas para la resolución de conflictos o de las personas que están acostumbradas a un flujo de trabajo SVN, como se sugiere en Evitar desastres de Git: una historia sangrienta . Solo hace que la resolución de conflictos sea más tediosa para ellos y hace que sea más difícil recuperarse de una mala resolución de conflictos. En su lugar, use diff3 para que no sea tan difícil en primer lugar.


¡Rebase el flujo de trabajo no es mejor para la resolución de conflictos!

Soy muy partidario de limpiar la historia. Sin embargo, si alguna vez llego a un conflicto, inmediatamente aborto el rebase y hago una fusión. Realmente me mata que la gente esté recomendando un flujo de trabajo de rebase como una mejor alternativa a un flujo de trabajo de fusión para la resolución de conflictos (que es exactamente de lo que se trataba esta pregunta).

Si se va "todo al infierno" durante una fusión, se irá "todo al infierno" durante un rebase, ¡y potencialmente mucho más infierno también! Este es el por qué:

Razón 1: resuelva conflictos una vez, en lugar de una vez para cada confirmación

Cuando rebasas en lugar de fusionar, tendrás que realizar la resolución de conflictos tantas veces como te hayas comprometido a rebasar, ¡para el mismo conflicto!

Escenario real

Me ramifico del maestro para refactorizar un método complicado en una rama. Mi trabajo de refactorización se compone de 15 confirmaciones en total mientras trabajo para refactorizarlo y obtener revisiones de código. Parte de mi refactorización implica arreglar las pestañas y espacios mixtos que estaban presentes en master antes. Esto es necesario, pero desafortunadamente entrará en conflicto con cualquier cambio realizado posteriormente a este método en master. Efectivamente, mientras estoy trabajando en este método, alguien realiza un cambio simple y legítimo al mismo método en la rama maestra que debería fusionarse con mis cambios.

Cuando llega el momento de fusionar mi sucursal con master, tengo dos opciones:

git merge: me sale un conflicto. Veo el cambio que hicieron para dominarlo y fusionarlo con (el producto final de) mi sucursal. Hecho.

git rebase: Tengo un conflicto con mi primer commit. Resuelvo el conflicto y continúo el rebase. Tengo un conflicto con mi segundo commit. Resuelvo el conflicto y continúo el rebase. Tengo un conflicto con mi tercer commit. Resuelvo el conflicto y continúo el rebase. Tengo un conflicto con mi cuarto commit. Resuelvo el conflicto y continúo el rebase. Tengo un conflicto con mi quinto commit. Resuelvo el conflicto y continúo el rebase. Tengo un conflicto con mi sexto commit. Resuelvo el conflicto y continúo el rebase. Tengo un conflicto con mi séptimocometer. Resuelvo el conflicto y continúo el rebase. Tengo un conflicto con mi octavo compromiso. Resuelvo el conflicto y continúo el rebase. Tengo un conflicto con mi noveno compromiso. Resuelvo el conflicto y continúo el rebase. Tengo un conflicto con mi décimo commit. Resuelvo el conflicto y continúo el rebase. Tengo un conflicto con mi undécimo compromiso. Resuelvo el conflicto y continúo el rebase. Tengo un conflicto con mi duodécimo compromiso. Resuelvo el conflicto y continúo el rebase. Tengo un conflicto con mi decimotercer compromiso. Resuelvo el conflicto y continúo el rebase. Tengo un conflicto con mi decimocuartocometer. Resuelvo el conflicto y continúo el rebase. Tengo un conflicto con mi decimoquinta comisión. Resuelvo el conflicto y continúo el rebase.

Debes estar bromeando si este es tu flujo de trabajo preferido. Todo lo que se necesita es una corrección de espacios en blanco que entre en conflicto con un cambio realizado en el maestro, y cada confirmación entrará en conflicto y debe resolverse. Y este es un escenario simple con solo un conflicto de espacios en blanco. Dios no lo quiera usted tiene un conflicto real que implica grandes cambios en el código a través de archivos y tienen que resolver que en múltiples ocasiones.

Con toda la resolución de conflicto adicional que necesita hacer, solo aumenta la posibilidad de que se equivoque . Pero los errores están bien en git ya que puedes deshacer, ¿verdad? Excepto por supuesto ...

Razón # 2: ¡Con rebase, no hay deshacer!

Creo que todos podemos estar de acuerdo en que la resolución de conflictos puede ser difícil, y también que algunas personas son muy malas en eso. Puede ser muy propenso a errores, por lo que es tan bueno que git hace que sea fácil de deshacer.

Cuando fusiona una rama, git crea una confirmación de fusión que puede descartarse o modificarse si la resolución del conflicto no es adecuada. Incluso si ya ha enviado la confirmación de fusión incorrecta al repositorio público / autorizado, puede usar git revertpara deshacer los cambios introducidos por la fusión y rehacer la fusión correctamente en una nueva confirmación de fusión.

Cuando vuelves a crear una rama, en el caso probable de que la resolución de conflictos se haga mal, estás jodido. Cada confirmación ahora contiene la combinación incorrecta, y no puede simplemente rehacer la rebase *. En el mejor de los casos, debe volver y modificar cada una de las confirmaciones afectadas. No es divertido.

Después de un rebase, es imposible determinar qué fue originalmente parte de los commits y qué se introdujo como resultado de una mala resolución de conflictos.

* Puede ser posible deshacer un rebase si puedes sacar las referencias antiguas de los registros internos de git, o si creas una tercera rama que apunta al último commit antes de rebase.

Aléjese de la resolución de conflictos: use diff3

Tome este conflicto por ejemplo:

<<<<<<< HEAD
TextMessage.send(:include_timestamp => true)
=======
EmailMessage.send(:include_timestamp => false)
>>>>>>> feature-branch

Al observar el conflicto, es imposible saber qué cambió cada rama o cuál fue su intención. En mi opinión, esta es la razón más importante por la cual la resolución de conflictos es confusa y difícil.

¡diff3 al rescate!

git config --global merge.conflictstyle diff3

Cuando use el diff3, cada nuevo conflicto tendrá una tercera sección, el ancestro común fusionado.

<<<<<<< HEAD
TextMessage.send(:include_timestamp => true)
||||||| merged common ancestor
EmailMessage.send(:include_timestamp => true)
=======
EmailMessage.send(:include_timestamp => false)
>>>>>>> feature-branch

Primero examine el ancestro común fusionado. Luego compare cada lado para determinar la intención de cada rama. Puede ver que HEAD cambió EmailMessage a TextMessage. Su intención es cambiar la clase utilizada para TextMessage, pasando los mismos parámetros. También puede ver que la intención de la rama característica es pasar falso en lugar de verdadero para la opción: include_timestamp. Para fusionar estos cambios, combine la intención de ambos:

TextMessage.send(:include_timestamp => false)

En general:

  1. Compare el ancestro común con cada rama y determine qué rama tiene el cambio más simple
  2. Aplique ese cambio simple a la versión del código de la otra rama, para que contenga tanto el cambio más simple como el más complejo.
  3. Elimine todas las secciones del código de conflicto que no sea la que acaba de fusionar los cambios en

Alternativa: resuelva aplicando manualmente los cambios de la rama

Finalmente, algunos conflictos son terribles de entender incluso con diff3. Esto sucede especialmente cuando diff encuentra líneas en común que no son semánticamente comunes (por ejemplo, ¡ambas ramas tenían una línea en blanco en el mismo lugar!). Por ejemplo, una rama cambia la sangría del cuerpo de una clase o reordena métodos similares. En estos casos, una mejor estrategia de resolución puede ser examinar el cambio desde cualquier lado de la fusión y aplicar manualmente la diferencia al otro archivo.

Echemos un vistazo a cómo podríamos resolver un conflicto en un escenario donde fusionar origin/feature1donde los lib/message.rbconflictos.

  1. Decide si nuestra rama actualmente desprotegida ( HEAD, o --ours) o la rama que estamos fusionando ( origin/feature1, o --theirs) es un cambio más simple de aplicar. El uso de diff con punto triple ( git diff a...b) muestra los cambios que ocurrieron bdesde su última divergencia ao, en otras palabras, compara el ancestro común de a y b con b.

    git diff HEAD...origin/feature1 -- lib/message.rb # show the change in feature1
    git diff origin/feature1...HEAD -- lib/message.rb # show the change in our branch
    
  2. Echa un vistazo a la versión más complicada del archivo. Esto eliminará todos los marcadores de conflicto y usará el lado que elijas.

    git checkout --ours -- lib/message.rb   # if our branch's change is more complicated
    git checkout --theirs -- lib/message.rb # if origin/feature1's change is more complicated
    
  3. Con el complicado cambio desprotegido, levante la diferencia del cambio más simple (vea el paso 1). Aplique cada cambio de esta diferencia al archivo en conflicto.

Edward Anderson
fuente
44
¿Cómo sería mejor combinar todos los conflictos de una sola vez que los compromisos individuales? Ya tengo problemas al fusionar confirmaciones individuales (especialmente de personas que no dividen las confirmaciones en partes lógicas Y proporcionan pruebas suficientes para la verificación). Además, el rebase no es peor que la fusión cuando se trata de opciones de respaldo, el uso inteligente de rebase interactivo y herramientas como tortoisegit (que permite la selección de los compromisos que se incluirán) será de gran ayuda.
prusswan
8
Siento que abordé el motivo en el n. ° 1. Si las confirmaciones individuales no son lógicamente consistentes, una razón más para fusionar la rama lógicamente coherente, de modo que pueda entender el conflicto. Si commit 1 tiene errores y commit 2 lo corrige, la fusión de commit 1 será confusa. Hay razones legítimas por las que puede obtener 15 conflictos seguidos, como el que describí anteriormente. Además, su argumento para que rebase no sea peor es algo infundado. Rebase mezcla fusiones malas en las confirmaciones buenas originales y no deja las confirmaciones buenas para que pueda volver a intentarlo. La fusión lo hace.
Edward Anderson el
66
Estoy completamente de acuerdo contigo nilbus. Buena publicación; eso aclara algunas cosas. Sin embargo, me pregunto si rerere sería de alguna ayuda aquí. Además, gracias por la sugerencia de usar diff3, definitivamente voy a activar esa en este momento.
derick
45
+1 por hablarme solo de diff3: con qué frecuencia estaba viendo un conflicto incomprensible maldiciendo a quien sea responsable de no decirme lo que tenía que decir el antepasado común. Muchas gracias.
John
44
Esta debería haber sido la respuesta aceptada. El flujo de trabajo de rebase también es horrible porque oculta el hecho de que hubo una gran divergencia en la base de código en algún momento, lo que podría ser útil saber si desea comprender cómo se escribió el código que está viendo. Solo las ramas pequeñas que no entran en conflicto deben volverse a formar en maestro.
Robert Rüger
32

En mi flujo de trabajo, modifico lo más posible (y trato de hacerlo a menudo. No permitir que las discrepancias se acumulen drásticamente reduce la cantidad y la gravedad de las colisiones entre las ramas).

Sin embargo, incluso en un flujo de trabajo basado principalmente en rebase, hay un lugar para las fusiones.

Recuerde que la fusión en realidad crea un nodo que tiene dos padres. Ahora considere la siguiente situación: tengo dos ramas de características independientes A y B, y ahora quiero desarrollar cosas en la rama de características C que depende de A y B, mientras que A y B se están revisando.

Lo que hago entonces es lo siguiente:

  1. Cree (y finalice) la rama C encima de A.
  2. Fusionarlo con B

Ahora la rama C incluye cambios tanto de A como de B, y puedo seguir desarrollándolos. Si hago algún cambio en A, entonces reconstruyo el gráfico de ramas de la siguiente manera:

  1. crear rama T en la nueva parte superior de A
  2. fusionar T con B
  3. rebase C sobre T
  4. eliminar rama T

De esta forma, puedo mantener gráficos arbitrarios de ramas, pero hacer algo más complejo que la situación descrita anteriormente ya es demasiado complejo, dado que no hay una herramienta automática para hacer la reorganización cuando el padre cambia.

Alex Gontmakher
fuente
1
Podrías lograr lo mismo con solo rebases. La fusión en realidad no es necesaria aquí (excepto si no desea duplicar las confirmaciones, pero apenas lo veo como un argumento).
Odwl
1
De hecho, no quiero duplicar los commits. Me gustaría mantener la estructura de vuelo de mi trabajo lo más limpia posible. Pero eso es una cuestión de gusto personal y no es necesariamente adecuado para todos.
Alex Gontmakher
Estoy 100% de acuerdo con el primer párrafo. (La respuesta de @ Edward funciona donde ese no es el caso, pero prefiero que todos los proyectos en el mundo funcionen como usted sugiere). El resto de la respuesta parece un poco exagerado en el sentido de que trabajar en C mientras A y B están en progreso ya es un poco arriesgado (al menos en la medida en que realmente depende de A y B), e incluso al final probablemente no mantendría las fusiones (C se rebautizaría además de la última y mejor)
Alois Mahdal
23

NO use git push origin --mirror BAJO CUALQUIER CIRCUNSTANCIA.

No le pregunta si está seguro de querer hacer esto, y será mejor que esté seguro, porque borrará todas sus ramas remotas que no están en su casilla local.

http://twitter.com/dysinger/status/1273652486

Scott Brown
fuente
66
¿O no haces cosas que no estás seguro de cuál será el resultado? Una máquina que solía administrar tenía Instructions to this machine may lead to unintended consequences, loss of work/data, or even death (at the hands of the sysad). Remember that you are solely responsible for the consequences of your actions en el MOTD.
richo
usarlo si usted tiene un acuerdo de recompra de espejo (aunque en mi caso ahora es ejecutado por un usuario especial en el repositorio fuente en el post-recepción gancho)
prusswan
14

Tengo una pregunta después de leer tu explicación: ¿podría ser que nunca hiciste un

git checkout master
git pull origin
git checkout my_new_feature

antes de hacer el 'git rebase / merge master' en tu rama de características?

Porque su rama maestra no se actualizará automáticamente desde el repositorio de su amigo. Tienes que hacer eso con el git pull origin. Es decir, ¿quizás siempre rebasarías de una rama maestra local que nunca cambia? Y luego, cuando llega el momento de empujar, está empujando en un repositorio que tiene confirmaciones (locales) que nunca vio y, por lo tanto, el empuje falla.

Knweiss
fuente
13

En su situación, creo que su pareja es correcta. Lo bueno de rebase es que para el extraño tus cambios parecen haber sucedido en una secuencia limpia por sí mismos. Esto significa

  • sus cambios son muy fáciles de revisar
  • puedes continuar haciendo pequeños y agradables commits y, sin embargo, puedes hacer públicos los conjuntos de esos commits (fusionándolos en master) de una vez
  • cuando miras la rama maestra pública, verás diferentes series de confirmaciones para diferentes características de diferentes desarrolladores, pero no todas se mezclarán

Todavía puede continuar empujando su rama de desarrollo privado al repositorio remoto en aras de la copia de seguridad, pero otros no deben tratar eso como una rama "pública", ya que estará haciendo un rebase. Por cierto, un comando fácil para hacer esto es git push --mirror origin.

El artículo El software de empaquetado que usa Git hace un trabajo bastante bueno al explicar las compensaciones en la fusión frente al rebase. Es un contexto un poco diferente, pero los principios son los mismos: básicamente se trata de si sus sucursales son públicas o privadas y cómo planea integrarlas en la línea principal.

Pat Notz
fuente
1
El enlace al software de empaquetado usando git ya no funciona. No pude encontrar un buen enlace para editar la respuesta original.
Chetan
No debe duplicar origin, debe duplicar a un tercer repositorio de respaldo dedicado.
Miral
12

De todos modos, estaba siguiendo mi flujo de trabajo en una sucursal reciente, y cuando intenté fusionarlo nuevamente con master, todo se fue al infierno. Hubo toneladas de conflictos con cosas que no deberían haber importado. Los conflictos simplemente no tenían sentido para mí. Me llevó un día resolver todo, y finalmente culminó en un empuje forzado hacia el maestro remoto, ya que mi maestro local resolvió todos los conflictos, pero el remoto aún no estaba contento.

En los flujos de trabajo de su compañero o sugeridos no debería haber encontrado conflictos que no tenían sentido. Incluso si lo hubiera hecho, si está siguiendo los flujos de trabajo sugeridos, entonces, después de la resolución, no debería requerirse un impulso 'forzado'. Sugiere que en realidad no ha fusionado la rama a la que estaba empujando, sino que ha tenido que empujar una rama que no era descendiente de la punta remota.

Creo que debes mirar cuidadosamente lo que sucedió. ¿Podría alguien más haber rebobinado (deliberadamente o no) la rama maestra remota entre su creación de la rama local y el punto en el que intentó fusionarla nuevamente en la rama local?

En comparación con muchos otros sistemas de control de versiones, descubrí que usar Git implica menos lucha contra la herramienta y le permite trabajar en los problemas que son fundamentales para sus transmisiones de origen. Git no realiza magia, por lo que los cambios conflictivos causan conflictos, pero debería facilitar la tarea de escribir mediante el seguimiento de la paternidad de confirmación.

CB Bailey
fuente
Estás insinuando que el OP tiene algún rebase o error no descubierto en su proceso, ¿verdad?
krosenvold
8

"Incluso si usted es un desarrollador único con solo unas pocas ramas, vale la pena acostumbrarse a usar rebase y fusionar adecuadamente. El patrón de trabajo básico se verá así:

  • Crear una nueva rama B a partir de la rama A existente

  • Agregar / confirmar cambios en la rama B

  • Rebase de actualizaciones desde la rama A

  • Fusionar cambios de la rama B a la rama A "

https://www.atlassian.com/git/tutorials/merging-vs-rebasing/

Rakka Rage
fuente
7

Por lo que he observado, git merge tiende a mantener las ramas separadas incluso después de la fusión, mientras que rebase luego merge lo combina en una sola rama. El último sale mucho más limpio, mientras que en el primero, sería más fácil averiguar qué confirmaciones pertenecen a qué rama incluso después de la fusión.

Pepe
fuente
4

Con Git no hay flujo de trabajo "correcto". Usa lo que sea que flota tu bote. Sin embargo, si constantemente tiene conflictos al fusionar sucursales, ¿tal vez debería coordinar mejor sus esfuerzos con sus compañeros desarrolladores? Parece que ustedes dos siguen editando los mismos archivos. Además, tenga cuidado con los espacios en blanco y las palabras clave de subversión (es decir, "$ Id $" y otros).

Bombe
fuente
0

Solo uso el flujo de trabajo de rebase, porque es visualmente más claro (no solo en GitKraken, sino también en Intellij y en gitk, pero recomiendo el primero): tienes una rama, se origina en el maestro y vuelve al maestro . Cuando el diagrama esté limpio y hermoso, sabrás que nada se va al infierno .

ingrese la descripción de la imagen aquí

Mi flujo de trabajo es casi el mismo que el suyo, pero con solo una pequeña diferencia: me squashcomprometo en uno en mi sucursal local antes de que rebasemi sucursal realice los últimos cambios master, porque:

rebasetrabaja en base a cada commit

lo que significa que si tiene 15 confirmaciones que cambian la misma línea que lo masterhace, debe verificar 15 veces si no aplasta, pero lo que importa es el resultado final, ¿verdad?

Entonces, todo el flujo de trabajo es:

  1. Pague mastery tire para asegurarse de que tiene la última versión

  2. A partir de ahí, crea una nueva sucursal

  3. Haga su trabajo allí, puede comprometerse libremente varias veces y empujar a control remoto, no se preocupe, porque es su sucursal.

  4. Si alguien te dice, "oye, mi PR / MR está aprobado, ahora está fusionado para dominar", puedes buscarlos / extraerlos. Puedes hacerlo en cualquier momento o en el paso 6.

  5. Después de hacer todo su trabajo, comprométalas, y si tiene varias confirmaciones, elimínelas (son todo su trabajo, y cuántas veces cambie una línea de código no importa; lo único importante es la versión final). Empújalo o no, no importa.

  6. Pedido para master, pulluna vez más para asegurarse de que tiene la última masteren local. Su diagrama debe ser similar a esto:

ingrese la descripción de la imagen aquí

Como puede ver, está en su sucursal local, que se origina en un estado desactualizado master, mientras que master(tanto local como remoto) ha avanzado con los cambios de su colega.

  1. Vuelva a pagar a su sucursal y cambie la base a master. Ahora solo tendrá un commit, por lo que resolverá los conflictos solo una vez . (Y en GitKraken, solo tiene que arrastrar su rama mastery elegir "Rebase"; otra razón por la que me gusta). Después de eso, será me gusta:

ingrese la descripción de la imagen aquí

  1. Entonces, ahora tiene todos los cambios en la última masterversión, combinados con los cambios en su sucursal. Ahora puede empujar a su control remoto y, si lo ha hecho antes, deberá forzarlo; Git te dirá que no puedes simplemente avanzar rápidamente. Eso es normal, debido al rebase, ha cambiado el punto de inicio de su rama. Pero no debes temer: usa la fuerza sabiamente . Al final, el control remoto también es su rama, por lo que no afecta, masterincluso si hace algo mal.

  2. Cree PR / MR y espere hasta que se apruebe, así mastertendrá su contribución. ¡Felicidades! Por lo tanto, ahora puede realizar el pago master, extraer los cambios y eliminar su sucursal local para limpiar el diagrama. La rama remota también debe eliminarse, si esto no se hace cuando la combina en maestro.

El diagrama final es limpio y claro nuevamente:

ingrese la descripción de la imagen aquí

WesternGun
fuente