funciona - ¿Cuándo usas git rebase en lugar de git merge?
git rebase vs merge (14)
TL; DR
Si tienes alguna duda, utiliza fusionar.
Respuesta corta
Las únicas diferencias entre una rebase y una fusión son:
- La estructura de árbol resultante del historial (generalmente solo se nota cuando se mira un gráfico de confirmación) es diferente (uno tendrá ramas, el otro no).
- La fusión generalmente creará una confirmación adicional (por ejemplo, un nodo en el árbol).
- Fusionar y rebase manejará los conflictos de manera diferente. Rebase presentará los conflictos, una confirmación a la vez, donde la fusión los presentará todos a la vez.
Por lo tanto, la respuesta corta es elegir rebase o fusionar en función de cómo desea que se vea su historial .
Respuesta larga
Hay algunos factores que debe considerar al elegir qué operación usar.
¿La rama de la que está obteniendo cambios es compartida con otros desarrolladores fuera de su equipo (por ejemplo, código abierto, público)?
Si es así, no rebase. Rebase destruye la rama y esos desarrolladores tendrán repositorios rotos / inconsistentes a menos que usen git pull --rebase
. Esta es una buena manera de molestar a otros desarrolladores rápidamente.
¿Cuánta habilidad tiene tu equipo de desarrollo?
Rebase es una operación destructiva. Eso significa que, si no lo aplica correctamente, podría perder el trabajo comprometido y / o romper la consistencia de los repositorios de otros desarrolladores.
He trabajado en equipos en los que todos los desarrolladores venían de una época en que las empresas podían permitirse contratar personal dedicado para lidiar con la creación de sucursales y la fusión. Esos desarrolladores no saben mucho sobre Git y no quieren saber mucho. En estos equipos no me arriesgaría a recomendar el rebasado por ningún motivo.
¿La rama en sí representa información útil?
Algunos equipos utilizan el modelo de rama por función donde cada rama representa una función (o corrección de errores, o subcaracterística, etc.) En este modelo, la rama ayuda a identificar conjuntos de confirmaciones relacionadas. Por ejemplo, uno puede revertir rápidamente una característica al revertir la combinación de esa rama (para ser justos, esta es una operación rara). O difiera una característica comparando dos ramas (más comunes). Rebase destruiría la rama y esto no sería sencillo.
También he trabajado en equipos que usaron el modelo de rama por desarrollador (todos hemos estado allí). En este caso, la propia sucursal no transmite ninguna información adicional (el compromiso ya tiene el autor). No habría ningún daño en rebasar.
¿Podrías revertir la fusión por alguna razón?
Revertir (como en deshacer) una rebase es considerablemente difícil y / o imposible (si la rebase tuvo conflictos) en comparación con revertir una fusión. Si crees que hay una posibilidad que querrás revertir, usa Merge.
¿Trabajas en un equipo? Si es así, ¿estás dispuesto a adoptar un enfoque de todo o nada en esta rama?
Las operaciones de rebase deben realizarse con un git pull --rebase
correspondiente. Si está trabajando solo, puede recordar cuál debe usar en el momento adecuado. Si está trabajando en un equipo, será muy difícil coordinarlo. Esta es la razón por la que la mayoría de los flujos de trabajo de rebase recomiendan el uso de rebase para todas las combinaciones (y git pull --rebase
para todos los pulls).
Mitos comunes
Fusionar destruye la historia (squashes commit).
Suponiendo que tiene la siguiente combinación:
B -- C
/ /
A--------D
Algunas personas dirán que la fusión "destruye" el historial de confirmación porque si solo se mirara el registro de la rama maestra (A - D) se perderían los mensajes de confirmación importantes contenidos en B y C.
Si esto fuera cierto, no tendríamos preguntas como esta . Básicamente, verá B y C a menos que pida explícitamente que no los vea (usando - primer-padre). Esto es muy fácil de probar por ti mismo.
Rebase permite fusiones más seguras / simples
Los dos enfoques se fusionan de manera diferente, pero no está claro que uno sea siempre mejor que el otro y puede depender del flujo de trabajo del desarrollador. Por ejemplo, si un desarrollador tiende a comprometerse regularmente (por ejemplo, tal vez se compromete dos veces al día cuando realiza la transición del trabajo al hogar), entonces puede haber muchos compromisos para una rama determinada. Es posible que muchos de esos compromisos no se parezcan al producto final (tiendo a refactorizar mi enfoque una o dos veces por función). Si alguien más estaba trabajando en un área de código relacionada e intentaron volver a cambiar mis cambios, podría ser una operación bastante tediosa.
Rebase es más guay / más sexy / más profesional
Si te gusta el alias rm
to rm -rf
para "ahorrar tiempo", quizás la rebase sea para ti.
Mis dos centavos
Siempre pienso que algún día me encontraré con un escenario en el que git rebase es la increíble herramienta que resuelve el problema. Al igual que creo que me encontraré con un escenario en el que git reflog es una herramienta increíble que resuelve mi problema. He trabajado con git durante más de cinco años. No ha pasado
Las historias desordenadas nunca han sido realmente un problema para mí. Nunca leo mi historia de cometer como una novela emocionante. La mayoría de las veces necesito un historial. Voy a usar Git Culpe o Git Bisect de todos modos. En ese caso, tener la confirmación de fusión es realmente útil para mí porque si la combinación introdujo el problema, esa información es significativa para mí.
Actualización (4/2017)
Me siento obligado a mencionar que personalmente me he suavizado con el uso de rebase, aunque mi consejo general sigue en pie. Recientemente he estado interactuando mucho con el proyecto de Angular 2 Material . Han utilizado rebase para mantener un historial de compromiso muy limpio. Esto me ha permitido ver muy fácilmente qué compromiso solucionó un defecto determinado y si ese compromiso se incluyó o no en un lanzamiento. Sirve como un gran ejemplo de usar rebase correctamente.
¿Cuándo se recomienda usar git rebase
vs. git merge
?
¿Sigo necesitando fusionarme después de una rebase exitosa?
Version corta
- Fusionar toma todos los cambios en una rama y los combina en otra rama en una confirmación.
- Rebase dice que quiero que el punto en el que me bifurco se mueva hacia un nuevo punto de partida
Entonces, ¿cuándo usas uno de los dos?
Unir
- Digamos que ha creado una rama con el propósito de desarrollar una sola función. Cuando quiera traer esos cambios al maestro, probablemente desee fusionar (no le importa mantener todas las confirmaciones provisionales).
Rebase
- Un segundo escenario sería si comenzara a hacer algo de desarrollo y luego otro desarrollador realizó un cambio no relacionado. Probablemente quiera tirar y luego volver a basarse para basar sus cambios de la versión actual del repositorio.
Es simple, con rebase usted dice que use otra rama como la nueva base para su trabajo.
Si, por ejemplo, tiene una rama master
y crea una rama para implementar una nueva función, diga que la llama cool-feature
, por supuesto, la rama principal es la base para su nueva función.
Ahora, en un determinado momento, desea agregar la nueva característica que implementó en la rama master
. Simplemente puedes cambiar a master
y fusionar la rama cool-feature
:
$ git checkout master
$ git merge cool-feature
pero de esta manera se agrega un nuevo dummy commit, si quieres evitar spaghetti-history puedes rebase :
$ git checkout cool-feature
$ git rebase master
y luego fusionarlo en el master
:
$ git checkout master
$ git merge cool-feature
Esta vez, dado que la rama del tema tiene los mismos compromisos de maestro más los compromisos con la nueva característica, la combinación será solo un avance rápido.
Esta oración lo consigue:
En general, la forma de obtener lo mejor de ambos mundos es volver a hacer cambios en los cambios locales que has hecho pero que aún no has compartido antes de forzarlos para limpiar tu historia, pero nunca volver a hacer cambios en cualquier lugar que hayas hecho.
Fuente: http://www.git-scm.com/book/en/v2/Git-Branching-Rebasing#Rebase-vs.-Merge
Fusionar significa: crear un nuevo compromiso único que combine mis cambios en el destino.
Rebase significa: crear una nueva serie de confirmaciones, usando mi conjunto actual de confirmaciones como sugerencias. En otras palabras, calcule cómo se habrían visto mis cambios si hubiera empezado a hacerlos desde el punto en el que me he vuelto a basar. Por lo tanto, después de la reorganización, es posible que deba volver a probar sus cambios y, durante la reorganización, posiblemente tenga algunos conflictos.
Dado esto, ¿por qué rebase? Solo para mantener el historial de desarrollo claro. Digamos que estás trabajando en la función X y cuando hayas terminado, combinas los cambios. El destino ahora tendrá un solo compromiso que dirá algo similar a "Característica X añadida". Ahora, en lugar de fusionar, si se redimensiona y luego se fusiona, el historial de desarrollo de destino contendría todos los compromisos individuales en una progresión lógica única. Esto hace que revisar los cambios más adelante sea mucho más fácil. Imagina lo difícil que sería revisar el historial de desarrollo si 50 desarrolladores fusionaran varias características todo el tiempo.
Dicho esto, si ya has empujado la rama en la que estás trabajando en sentido ascendente, no debes volver a generar, sino fusionar. Para las ramas que no han sido empujadas hacia arriba, rebase, pruebe y fusione.
Es posible que otra ocasión en la que desee reajustar es cuando desee deshacerse de los compromisos de su sucursal antes de empujar hacia arriba. Por ejemplo: confirmaciones que introducen un código de depuración temprano y otras confirmaciones adicionales en la limpieza de ese código. La única manera de hacer esto es realizando una rebase interactiva: git rebase -i <branch/commit/tag>
ACTUALIZACIÓN: También desea utilizar la rebase cuando use Git para interactuar con un sistema de control de versiones que no admite el historial no lineal (subversión, por ejemplo). Cuando se utiliza el puente git-svn, es muy importante que los cambios que fusione nuevamente en subversion sean una lista secuencial de cambios sobre los cambios más recientes en el tronco. Solo hay dos formas de hacerlo: (1) Volver a crear manualmente los cambios y (2) Usar el comando rebase, que es mucho más rápido.
ACTUALIZACIÓN2: Una forma adicional de pensar en una rebase es que permite una especie de mapeo desde su estilo de desarrollo al estilo aceptado en el repositorio al que se está comprometiendo. Digamos que te gusta cometer en pequeños, pequeños trozos. Tiene un compromiso para corregir un error tipográfico, un compromiso para deshacerse del código no utilizado y así sucesivamente. Cuando haya terminado lo que necesita hacer, tiene una larga serie de confirmaciones. Ahora digamos que el repositorio al que se compromete anima a realizar grandes compromisos, por lo que para el trabajo que está haciendo, uno esperaría uno o quizás dos compromisos. ¿Cómo tomas tu cadena de compromisos y los comprimes a lo que se espera? Usaría una rebase interactiva y aplastaría sus diminutos compromisos en menos trozos más grandes. Lo mismo es cierto si se necesitaba el reverso: si su estilo era de pocos compromisos grandes, pero el repositorio exigía cadenas largas de compromisos pequeños. También usarías un rebase para hacer eso. Si en su lugar se había fusionado, ahora ha insertado su estilo de compromiso en el repositorio principal. Si hay muchos desarrolladores, puedes imaginar lo difícil que sería seguir un historial con varios estilos de compromiso diferentes después de algún tiempo.
ACTUALIZACIÓN 3: Does one still need to merge after a successful rebase?
Si tu puedes. La razón es que una rebase esencialmente implica un "cambio" de compromisos. Como he dicho anteriormente, estas confirmaciones se calculan, pero si tuvo 14 confirmaciones desde el punto de bifurcación, luego, suponiendo que no haya ningún problema con su rebase, tendrá 14 confirmaciones por delante (del punto en el que se rebasará) después de la rebase esta hecha Tenías una rama antes de una rebase. Tendrás una rama de la misma longitud después. Aún necesita fusionar antes de publicar sus cambios. En otras palabras, vuelva a realizar la rebase tantas veces como desee (de nuevo, solo si no ha impulsado sus cambios en sentido ascendente). Combinar solo después de que rebase.
Muchas respuestas aquí dicen que la fusión convierte todos sus compromisos en uno solo, y por lo tanto, sugiera utilizar rebase para preservar sus compromisos. Esto es incorrecto. Y una mala idea si ya has empujado tus compromisos .
Fusionar no borra tus compromisos. Fusionar conserva la historia! (solo mira gitk) Rebase reescribe la historia, que es una cosa mala después de haberla empujado .
Use fusionar, no rebase cuando ya haya presionado.
Aquí está Linus (el autor de git) lo toma . Es una muy buena lectura. O puedes leer mi propia versión de la misma idea a continuación.
Rebasando una rama en el maestro:
- proporciona una idea incorrecta de cómo se crearon los compromisos
- contamina al maestro con un montón de confirmaciones intermedias que pueden no haber sido bien probadas
- en realidad podría introducir saltos de compilación en estas confirmaciones intermedias debido a los cambios que se hicieron para dominar entre cuándo se creó la rama del tema original y cuando se volvió a basar.
- hace que sea difícil encontrar buenos lugares en el master para pagar.
- Provoca que las marcas de tiempo en las confirmaciones no se alineen con su orden cronológico en el árbol. Así que verías que el commit A precede a commit B en master, pero el commit B fue creado primero. (¡¿Qué?!)
- Produce más conflictos porque las confirmaciones individuales en la rama del tema pueden implicar conflictos de fusión que deben resolverse individualmente (más allá de la historia sobre lo que sucedió en cada confirmación).
- Es una reescritura de la historia. Si la rama que está siendo rebasada ha sido empujada en algún lugar (compartida con alguien que no sea usted), entonces ha jodido a todos los demás que tienen esa rama desde que reescribieron la historia.
Por el contrario, la fusión de una rama de tema en maestro:
- conserva el historial del lugar donde se crearon las ramas temáticas, incluidas las combinaciones entre el maestro y la rama temática para mantenerlo actualizado. Realmente tienes una idea precisa de con qué código trabajaba el desarrollador cuando estaban construyendo.
- master es una rama compuesta principalmente de fusiones, y cada una de esas confusiones de fusión son típicamente "buenos puntos" en la historia que son seguros de revisar porque ahí es donde la rama del tema estaba lista para integrarse.
- todas las confirmaciones individuales de la rama de tema se conservan, incluido el hecho de que se encontraban en una rama de tema, por lo que aislar esos cambios es natural y puede profundizar cuando sea necesario.
- los conflictos de fusión solo deben resolverse una vez (en el punto de la fusión) para que los cambios de confirmación intermedios realizados en la rama del tema no tengan que resolverse de forma independiente.
- Se puede hacer varias veces sin problemas. Si integras tu rama de tema para dominar periódicamente, la gente puede seguir construyendo sobre la rama de tema y puede seguir fusionándose de forma independiente.
Para complementar mi propia respuesta mencionada por TSamper ,
una rebase es a menudo una buena idea para hacer antes de una fusión, porque la idea es que integres en tu rama
Y
el trabajo de la ramaB
sobre la cual te fusionarás.
Pero nuevamente, antes de fusionar, resuelve cualquier conflicto en su sucursal (es decir, "rebase", como en "reproducir mi trabajo en mi sucursal a partir de un punto reciente de la sucursalB
)
Si se hace correctamente, la fusión posterior de la rama a la ramaB
puede avanzar rápidamente.una fusión afecta directamente a la rama
B
destino, lo que significa que las combinaciones deben ser triviales, de lo contrario, la ramaB
puede ser larga para volver a un estado estable (es hora de resolver todos los conflictos)
¿El punto de fusionarse después de una rebase?
En el caso que describo, reagrupo B
en mi sucursal, solo para tener la oportunidad de repetir mi trabajo desde un punto más reciente de B
, pero mientras permanezco en mi sucursal.
En este caso, todavía se necesita una combinación para llevar mi trabajo "repetido" a B
El otro escenario ( descrito en Git Ready, por ejemplo), es llevar su trabajo directamente a B
través de una rebase (que conserva todos sus compromisos agradables, o incluso le da la oportunidad de reordenarlos a través de una rebase interactiva).
En ese caso (donde rebasa mientras está en la rama B), tiene razón: no se necesita más fusión:
Un árbol de git por defecto cuando no hemos fusionado ni rebasado
obtenemos por rebasar:
El segundo escenario tiene que ver con: ¿cómo puedo hacer que la nueva característica vuelva al maestro?
Mi punto, al describir el primer escenario de rebase, es recordar a todos que una rebase también se puede usar como un paso preliminar para eso (que es "obtener una nueva característica de nuevo en master").
Puede usar la función de rebase para traer primero al maestro "en" la rama de nuevas características: la base de datos volverá a reproducir las confirmaciones de nuevas funciones desde el HEAD master
, pero aún en la rama de nuevas características, moviendo efectivamente el punto de inicio de la rama desde un antiguo compromiso maestro a HEAD-master
.
Eso le permite resolver cualquier conflicto en su rama (es decir, de forma aislada, mientras que le permite al maestro continuar evolucionando en paralelo si su etapa de resolución de conflictos toma demasiado tiempo).
Luego, puede cambiar a maestro y fusionar new-feature
(o volver a establecer una new-feature
en master
si desea conservar los compromisos realizados en su rama de new-feature
).
Asi que:
- "rebase vs. merge" puede verse como dos formas de importar un trabajo en, por ejemplo,
master
. - Pero "rebase y luego fusionar" puede ser un flujo de trabajo válido para resolver primero el conflicto de forma aislada y luego recuperar su trabajo.
Si bien la fusión es definitivamente la forma más fácil y más común de integrar cambios, no es la única: Rebase es un medio alternativo de integración.
Entender fusionar un poco mejor
Cuando Git realiza una fusión, busca tres confirmaciones:
- (1) Cometido de ancestro común Si sigue el historial de dos ramas en un proyecto, siempre tienen al menos un compromiso en común: en este momento, ambas ramas tenían el mismo contenido y luego evolucionaron de manera diferente.
- (2) + (3) Puntos finales de cada rama El objetivo de una integración es combinar los estados actuales de dos ramas. Por lo tanto, sus respectivas últimas revisiones son de especial interés. La combinación de estos tres compromisos resultará en la integración que buscamos.
Compromiso de avance rápido o fusión
En casos muy simples, una de las dos ramas no tiene ningún compromiso nuevo desde que se produjo la ramificación: su último compromiso sigue siendo el ancestro común.
En este caso, realizar la integración es muy simple: Git solo puede agregar todos los confirmaciones de la otra rama encima de la confirmación de ancestro común. En Git, esta forma más simple de integración se denomina fusión "de avance rápido". Ambas ramas comparten la misma historia exacta.
En muchos casos, sin embargo, ambas ramas avanzaron individualmente.
Para realizar una integración, Git tendrá que crear un nuevo compromiso que contenga las diferencias entre ellos: el compromiso de combinación.
Compromisos humanos y compromisos de fusión
Normalmente, un compromiso es creado cuidadosamente por un ser humano. Es una unidad significativa que envuelve solo los cambios relacionados y los anota con un comentario.
Una confirmación de fusión es un poco diferente: en lugar de ser creada por un desarrollador, Git la crea automáticamente. Y en lugar de envolver un conjunto de cambios relacionados, su propósito es conectar dos ramas, como un nudo. Si desea comprender una operación de fusión más adelante, debe echar un vistazo al historial de ambas ramas y al gráfico de confirmación correspondiente.
Integración con Rebase
Algunas personas prefieren ir sin tales compromisos de fusión automática. En su lugar, quieren que la historia del proyecto se vea como si hubiera evolucionado en una sola línea recta. No hay indicios de que se haya dividido en varias ramas en algún momento.
Veamos paso a paso una operación de rebase. El escenario es el mismo que en los ejemplos anteriores: queremos integrar los cambios de la rama B en la rama A, pero ahora utilizando rebase.
Lo haremos en tres pasos.
-
git rebase branch-A // syncs the history with branch-A
-
git checkout branch-A // change the current branch to branch-A
-
git merge branch-B // merge/take the changes from branch-B to branch-A
Primero, Git "deshará" todas las confirmaciones en la rama A que sucedieron después de que las líneas comenzaran a expandirse (después de la confirmación del antepasado común). Sin embargo, por supuesto, no los descartará: en cambio, puede pensar que esas confirmaciones se "guardan temporalmente".
A continuación, aplica las confirmaciones de la rama B que queremos integrar. En este punto, ambas ramas se ven exactamente iguales.
En el paso final, las nuevas confirmaciones en la rama A ahora se vuelven a aplicar, pero en una nueva posición, en la parte superior de las confirmaciones integradas de la sucursal B (se basan de nuevo). El resultado parece que el desarrollo había ocurrido en línea recta. En lugar de una confirmación de combinación que contiene todos los cambios combinados, se conservó la estructura de confirmación original.
Finalmente, obtiene una rama-A limpia sin compromisos no deseados y generados automáticamente.
Nota: Tomado de la impresionante post de git-tower
. Las desventajas de rebase
también es una buena lectura en el mismo post.
antes de fusionar / rebase:
A <- B <- C [master]
^
/
D <- E [branch]
después de git merge master
:
A <- B <- C
^ ^
/ /
D <- E <- F
después de git rebase master
:
A <- B <- C <- D'' <- E''
(A, B, C, D, E y F son confirmaciones)
este ejemplo y mucho más información bien ilustrada sobre git se pueden encontrar aquí: http://excess.org/article/2008/07/ogre-git-tutorial/
Algunos ejemplos prácticos, de alguna manera relacionados con el desarrollo a gran escala donde se utiliza gerrit para la integración de revisión y entrega.
Me fusiono cuando elevo mi rama de características a un nuevo maestro remoto. Esto proporciona un mínimo trabajo de elevación y es fácil seguir el historial del desarrollo de características en, por ejemplo, gitk.
git fetch
git checkout origin/my_feature
git merge origin/master
git commit
git push origin HEAD:refs/for/my_feature
Me fusiono cuando preparo un compromiso de entrega.
git fetch
git checkout origin/master
git merge --squash origin/my_feature
git commit
git push origin HEAD:refs/for/master
Rebase cuando mi entrega falla la integración por cualquier motivo y necesito actualizarla hacia un nuevo maestro remoto.
git fetch
git fetch <gerrit link>
git checkout FETCH_HEAD
git rebase origin/master
git push origin HEAD:refs/for/master
Git rebase se utiliza para hacer que las rutas de bifurcación en la historia del limpiador y la estructura del repositorio sean lineales.
También se utiliza para mantener en privado las sucursales creadas por usted, ya que después de cambiar la base y enviar los cambios al servidor, si elimina su sucursal, no habrá evidencia de que haya trabajado en la sucursal. Así que tu sucursal es ahora tu preocupación local.
Después de hacer el rebase, también nos deshacemos de un compromiso adicional que solíamos ver si hacemos una fusión normal.
Y sí, uno aún necesita fusionarse después de una rebase exitosa, ya que el comando rebase solo pone su trabajo en la parte superior de la rama que mencionó durante la rebase, diga master y realiza el primer commit de su rama como descendiente directo de la rama master. Esto significa que ahora podemos hacer una combinación de avance rápido para llevar los cambios de esta rama a la rama maestra.
¿Cuándo uso git rebase
? Casi nunca, porque reescribe la historia. git merge
es casi siempre la opción preferible, ya que respeta lo que realmente sucedió en su proyecto.
El libro pro git como una muy buena explicación en la página de rebasado .
Básicamente una fusión tomará 2 confirmaciones y las combinará.
Una rebase irá al ancestro común en el 2 y aplicará incrementalmente los cambios uno encima del otro. Esto hace para una historia ''más limpia'' y más lineal.
Pero cuando cambias de base, abandonas los compromisos anteriores y creas otros nuevos. Por lo tanto, nunca se debe cambiar un repositorio que sea público. Las otras personas que trabajan en el repo te odiarán.
Por eso solo me fusiono casi exclusivamente. El 99% de las veces, mis sucursales no difieren mucho, por lo que si hay conflictos solo es en uno o dos lugares.
Esta respuesta está ampliamente orientada alrededor de Git Flow . Las tablas se han generado con el agradable generador de tablas ASCII y los árboles de historia con este comando maravilloso (con aliased como git lg
):
git log --graph --abbrev-commit --decorate --date=format:''%Y-%m-%d %H:%M:%S'' --format=format:''%C(bold blue)%h%C(reset) - %C(bold cyan)%ad%C(reset) %C(bold green)(%ar)%C(reset)%C(bold yellow)%d%C(reset)%n'''' %C(white)%s%C(reset) %C(dim white)- %an%C(reset)''
Las tablas están en orden cronológico inverso para ser más coherentes con los árboles de historia. Vea también la diferencia entre git merge
y la git merge --no-ff
primera (por lo general, desea usarla git merge --no-ff
porque hace que su historia se vea más cercana a la realidad):
git merge
Comandos:
Time Branch "develop" Branch "features/foo"
------- ------------------------------ -------------------------------
15:04 git merge features/foo
15:03 git commit -m "Third commit"
15:02 git commit -m "Second commit"
15:01 git checkout -b features/foo
15:00 git commit -m "First commit"
Resultado:
* 142a74a - YYYY-MM-DD 15:03:00 (XX minutes ago) (HEAD -> develop, features/foo)
| Third commit - Christophe
* 00d848c - YYYY-MM-DD 15:02:00 (XX minutes ago)
| Second commit - Christophe
* 298e9c5 - YYYY-MM-DD 15:00:00 (XX minutes ago)
First commit - Christophe
git merge --no-ff
Comandos:
Time Branch "develop" Branch "features/foo"
------- -------------------------------- -------------------------------
15:04 git merge --no-ff features/foo
15:03 git commit -m "Third commit"
15:02 git commit -m "Second commit"
15:01 git checkout -b features/foo
15:00 git commit -m "First commit"
Resultado:
* 1140d8c - YYYY-MM-DD 15:04:00 (XX minutes ago) (HEAD -> develop)
|/ Merge branch ''features/foo'' - Christophe
| * 69f4a7a - YYYY-MM-DD 15:03:00 (XX minutes ago) (features/foo)
| | Third commit - Christophe
| * 2973183 - YYYY-MM-DD 15:02:00 (XX minutes ago)
|/ Second commit - Christophe
* c173472 - YYYY-MM-DD 15:00:00 (XX minutes ago)
First commit - Christophe
git merge
vs git rebase
Primer punto: siempre fusionar características en desarrollar, nunca rebase desarrollar desde características . Esta es una consecuencia de la Regla de Oro de Rebasar :
La regla de oro de
git rebase
es nunca usarlo en las sucursales públicas .
Nunca rebase nada de lo que hayas empujado en alguna parte.
Personalmente agregaría: a menos que sea una rama de la característica Y usted y su equipo son conscientes de las consecuencias .
Por lo tanto, la pregunta de git merge
vs se git rebase
aplica casi solo a las ramas de características (en los siguientes ejemplos, --no-ff
siempre se ha utilizado al fusionar). Tenga en cuenta que, como no estoy seguro de que haya una solución mejor ( existe un debate ), solo proporcionaré cómo se comportan los dos comandos. En mi caso, prefiero usarlo git rebase
porque produce un árbol de historia más agradable :)
Entre ramas de características
git merge
Comandos:
Time Branch "develop" Branch "features/foo" Branch "features/bar"
------- -------------------------------- ------------------------------- --------------------------------
15:10 git merge --no-ff features/bar
15:09 git merge --no-ff features/foo
15:08 git commit -m "Sixth commit"
15:07 git merge --no-ff features/foo
15:06 git commit -m "Fifth commit"
15:05 git commit -m "Fourth commit"
15:04 git commit -m "Third commit"
15:03 git commit -m "Second commit"
15:02 git checkout -b features/bar
15:01 git checkout -b features/foo
15:00 git commit -m "First commit"
Resultado:
* c0a3b89 - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|/ Merge branch ''features/bar'' - Christophe
| * 37e933e - YYYY-MM-DD 15:08:00 (XX minutes ago) (features/bar)
| | Sixth commit - Christophe
| * eb5e657 - YYYY-MM-DD 15:07:00 (XX minutes ago)
| |/ Merge branch ''features/foo'' into features/bar - Christophe
| * | 2e4086f - YYYY-MM-DD 15:06:00 (XX minutes ago)
| | | Fifth commit - Christophe
| * | 31e3a60 - YYYY-MM-DD 15:05:00 (XX minutes ago)
| | | Fourth commit - Christophe
* | | 98b439f - YYYY-MM-DD 15:09:00 (XX minutes ago)
|/ / / Merge branch ''features/foo'' - Christophe
| |/ /
|/| /
| |/
| * 6579c9c - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| | Third commit - Christophe
| * 3f41d96 - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/ Second commit - Christophe
* 14edc68 - YYYY-MM-DD 15:00:00 (XX minutes ago)
First commit - Christophe
git rebase
Comandos:
Time Branch "develop" Branch "features/foo" Branch "features/bar"
------- -------------------------------- ------------------------------- -------------------------------
15:10 git merge --no-ff features/bar
15:09 git merge --no-ff features/foo
15:08 git commit -m "Sixth commit"
15:07 git rebase features/foo
15:06 git commit -m "Fifth commit"
15:05 git commit -m "Fourth commit"
15:04 git commit -m "Third commit"
15:03 git commit -m "Second commit"
15:02 git checkout -b features/bar
15:01 git checkout -b features/foo
15:00 git commit -m "First commit"
Resultado:
* 7a99663 - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|/ Merge branch ''features/bar'' - Christophe
| * 708347a - YYYY-MM-DD 15:08:00 (XX minutes ago) (features/bar)
| | Sixth commit - Christophe
| * 949ae73 - YYYY-MM-DD 15:06:00 (XX minutes ago)
| | Fifth commit - Christophe
| * 108b4c7 - YYYY-MM-DD 15:05:00 (XX minutes ago)
| | Fourth commit - Christophe
* | 189de99 - YYYY-MM-DD 15:09:00 (XX minutes ago)
|/ / Merge branch ''features/foo'' - Christophe
| |/
| * 26835a0 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| | Third commit - Christophe
| * a61dd08 - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/ Second commit - Christophe
* ae6f5fc - YYYY-MM-DD 15:00:00 (XX minutes ago)
First commit - Christophe
De develop
a una rama característica
git merge
Comandos:
Time Branch “develop" Branch "features/foo" Branch "features/bar"
------- -------------------------------- ------------------------------- -------------------------------
15:10 git merge --no-ff features/bar
15:09 git commit -m “Sixth commit"
15:08 git merge --no-ff development
15:07 git merge --no-ff features/foo
15:06 git commit -m “Fifth commit"
15:05 git commit -m “Fourth commit"
15:04 git commit -m “Third commit"
15:03 git commit -m “Second commit"
15:02 git checkout -b features/bar
15:01 git checkout -b features/foo
15:00 git commit -m “First commit"
Resultado:
* 9e6311a - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|/ Merge branch ''features/bar'' - Christophe
| * 3ce9128 - YYYY-MM-DD 15:09:00 (XX minutes ago) (features/bar)
| | Sixth commit - Christophe
| * d0cd244 - YYYY-MM-DD 15:08:00 (XX minutes ago)
| |/ Merge branch ''develop'' into features/bar - Christophe
| |/
|/|
* | 5bd5f70 - YYYY-MM-DD 15:07:00 (XX minutes ago)
|/ / Merge branch ''features/foo'' - Christophe
| * | 4ef3853 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| | | Third commit - Christophe
| * | 3227253 - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/ / Second commit - Christophe
| * b5543a2 - YYYY-MM-DD 15:06:00 (XX minutes ago)
| | Fifth commit - Christophe
| * 5e84b79 - YYYY-MM-DD 15:05:00 (XX minutes ago)
|/ Fourth commit - Christophe
* 2da6d8d - YYYY-MM-DD 15:00:00 (XX minutes ago)
First commit - Christophe
git rebase
Comandos:
Time Branch “develop" Branch "features/foo" Branch "features/bar"
------- -------------------------------- ------------------------------- -------------------------------
15:10 git merge --no-ff features/bar
15:09 git commit -m “Sixth commit"
15:08 git rebase development
15:07 git merge --no-ff features/foo
15:06 git commit -m “Fifth commit"
15:05 git commit -m “Fourth commit"
15:04 git commit -m “Third commit"
15:03 git commit -m “Second commit"
15:02 git checkout -b features/bar
15:01 git checkout -b features/foo
15:00 git commit -m “First commit"
Resultado:
* b0f6752 - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|/ Merge branch ''features/bar'' - Christophe
| * 621ad5b - YYYY-MM-DD 15:09:00 (XX minutes ago) (features/bar)
| | Sixth commit - Christophe
| * 9cb1a16 - YYYY-MM-DD 15:06:00 (XX minutes ago)
| | Fifth commit - Christophe
| * b8ddd19 - YYYY-MM-DD 15:05:00 (XX minutes ago)
|/ Fourth commit - Christophe
* 856433e - YYYY-MM-DD 15:07:00 (XX minutes ago)
|/ Merge branch ''features/foo'' - Christophe
| * 694ac81 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| | Third commit - Christophe
| * 5fd94d3 - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/ Second commit - Christophe
* d01d589 - YYYY-MM-DD 15:00:00 (XX minutes ago)
First commit - Christophe
Notas laterales
git cherry-pick
Cuando sólo se necesita una cometen específica, git cherry-pick
es una buena solución (la -x
opción añade una línea que dice " (cereza escogió de cometer ...) " al cuerpo original de mensajes de confirmación, así que por lo general es una buena idea para usarla - git log <commit_sha1>
para ver eso):
Comandos:
Time Branch “develop" Branch "features/foo" Branch "features/bar"
------- -------------------------------- ------------------------------- -----------------------------------------
15:10 git merge --no-ff features/bar
15:09 git merge --no-ff features/foo
15:08 git commit -m “Sixth commit"
15:07 git cherry-pick -x <second_commit_sha1>
15:06 git commit -m “Fifth commit"
15:05 git commit -m “Fourth commit"
15:04 git commit -m “Third commit"
15:03 git commit -m “Second commit"
15:02 git checkout -b features/bar
15:01 git checkout -b features/foo
15:00 git commit -m “First commit"
Resultado:
* 50839cd - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|/ Merge branch ''features/bar'' - Christophe
| * 0cda99f - YYYY-MM-DD 15:08:00 (XX minutes ago) (features/bar)
| | Sixth commit - Christophe
| * f7d6c47 - YYYY-MM-DD 15:03:00 (XX minutes ago)
| | Second commit - Christophe
| * dd7d05a - YYYY-MM-DD 15:06:00 (XX minutes ago)
| | Fifth commit - Christophe
| * d0d759b - YYYY-MM-DD 15:05:00 (XX minutes ago)
| | Fourth commit - Christophe
* | 1a397c5 - YYYY-MM-DD 15:09:00 (XX minutes ago)
|/ / Merge branch ''features/foo'' - Christophe
| |/
|/|
| * 0600a72 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| | Third commit - Christophe
| * f4c127a - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/ Second commit - Christophe
* 0cf894c - YYYY-MM-DD 15:00:00 (XX minutes ago)
First commit - Christophe
git pull --rebase
No estoy seguro de poder explicarlo mejor que Derek Gourlay ... Básicamente, use en git pull --rebase
lugar de git pull
:) Sin embargo, lo que falta en el artículo es que puede habilitarlo de manera predeterminada :
git config --global pull.rebase true
git rerere
De nuevo, muy bien explicado here . Pero en pocas palabras, si lo habilitas, ya no tendrás que resolver el mismo conflicto varias veces.