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.
rebase
comprensiónRespuestas:
"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:
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 :
( existe una técnica similar para el bazar )
git push --force
(en lugar degit pull --rebase
por ejemplo)fuente
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 revert
para 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:
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!
Cuando use el diff3, cada nuevo conflicto tendrá una tercera sección, el ancestro común fusionado.
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:
En general:
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/feature1
donde loslib/message.rb
conflictos.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 ocurrieronb
desde su última divergenciaa
o, en otras palabras, compara el ancestro común de a y b con b.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.
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.
fuente
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:
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:
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.
fuente
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
fuente
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.Tengo una pregunta después de leer tu explicación: ¿podría ser que nunca hiciste un
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.fuente
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
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.
fuente
origin
, debe duplicar a un tercer repositorio de respaldo dedicado.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.
fuente
"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/
fuente
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.
fuente
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).
fuente
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 .Mi flujo de trabajo es casi el mismo que el suyo, pero con solo una pequeña diferencia: me
squash
comprometo en uno en mi sucursal local antes de querebase
mi sucursal realice los últimos cambiosmaster
, porque:lo que significa que si tiene 15 confirmaciones que cambian la misma línea que lo
master
hace, debe verificar 15 veces si no aplasta, pero lo que importa es el resultado final, ¿verdad?Entonces, todo el flujo de trabajo es:
Pague
master
y tire para asegurarse de que tiene la última versiónA partir de ahí, crea una nueva sucursal
Haga su trabajo allí, puede comprometerse libremente varias veces y empujar a control remoto, no se preocupe, porque es su sucursal.
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.
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.
Pedido para
master
,pull
una vez más para asegurarse de que tiene la últimamaster
en local. Su diagrama debe ser similar a esto:Como puede ver, está en su sucursal local, que se origina en un estado desactualizado
master
, mientras quemaster
(tanto local como remoto) ha avanzado con los cambios de su colega.master
y elegir "Rebase"; otra razón por la que me gusta). Después de eso, será me gusta:Entonces, ahora tiene todos los cambios en la última
master
versió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,master
incluso si hace algo mal.Cree PR / MR y espere hasta que se apruebe, así
master
tendrá su contribución. ¡Felicidades! Por lo tanto, ahora puede realizar el pagomaster
, 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:
fuente