¿Cuándo se recomienda usar Git rebase vs. Git merge?
¿Todavía necesito fusionarme después de un rebase exitoso?
¿Cuándo se recomienda usar Git rebase vs. Git merge?
¿Todavía necesito fusionarme después de un rebase exitoso?
Respuestas:
Entonces, ¿cuándo usas uno de los dos?
init
un nuevo repositorio, add
el archivo y commit
. Verificación de una nueva rama de características ( checkout -b feature
.) Cambie el archivo de texto, confirme y repita para que haya dos nuevas confirmaciones en la rama de características. Entonces checkout master
y merge feature
. En log
, veo mi commit inicial en master, seguido de los dos que se fusionaron de la función. Si usted merge --squash feature
, la función se fusiona en master pero no se confirma, por lo que el único commit nuevo en master será el que usted mismo haga.
Es simple. Con rebase usted dice que use otra rama como la nueva base para su trabajo.
Si tiene, por ejemplo, una rama master
, crea una rama para implementar una nueva característica y dice que la nombra cool-feature
, por supuesto, la rama maestra es la base de su nueva característica.
Ahora, en cierto punto, desea agregar la nueva característica que implementó en la master
rama. Podrías cambiar master
y fusionar la cool-feature
rama:
$ git checkout master
$ git merge cool-feature
Pero de esta manera se agrega una nueva confirmación ficticia. Si desea evitar la historia de espagueti, puede volver a redactar :
$ git checkout cool-feature
$ git rebase master
Y luego fusionarlo en master
:
$ git checkout master
$ git merge cool-feature
Esta vez, dado que la rama de tema tiene las mismas confirmaciones de master más las confirmaciones con la nueva característica, la fusión será solo un avance rápido.
but this way a new dummy commit is added, if you want to avoid spaghetti-history
- como es malo?
Sean Schofield
pone en un comentario: "Rebase también es bueno porque una vez que finalmente fusionas tus cosas de nuevo en master (que es trivial como ya se describió) lo tienes en la" parte superior "de tu historial de commit. proyectos donde las características pueden escribirse pero fusionarse varias semanas después, no desea fusionarlas en el maestro porque se "rellenan" en el maestro en la historia. Personalmente, me gusta poder hacer git log y ver esa característica reciente justo en la "parte superior". Tenga en cuenta que las fechas de confirmación se conservan: la modificación no cambia esa información ".
merge
, rebase
, fast-forward
, etc.) se refieren a las manipulaciones específicas de un grafo acíclico dirigido. Se vuelven más fáciles de razonar con ese modelo mental en mente.
Para complementar mi propia respuesta mencionada por TSamper ,
a menudo es una buena idea hacer una nueva versión antes de una fusión, porque la idea es que integre en su rama Y
el trabajo de la rama B
sobre la que se fusionará.
Pero nuevamente, antes de fusionar, resuelve cualquier conflicto en su rama (es decir, "rebase", como en "reproducir mi trabajo en mi rama comenzando desde un punto reciente de la rama B
).
Si se hace correctamente, la fusión posterior de su rama a la rama B
puede avanzar rápidamente.
una fusión afecta directamente a la rama de destino B
, lo que significa que es mejor que las fusiones sean triviales, de lo contrario, esa rama B
puede ser larga para volver a un estado estable (tiempo para resolver todos los conflictos)
¿El punto de fusión después de un rebase?
En el caso que describo, vuelvo a basarme B
en mi rama, solo para tener la oportunidad de reproducir mi trabajo desde un punto más reciente B
, pero mientras permanezco en mi rama.
En este caso, todavía se necesita una fusión para incorporar mi trabajo "reproducido" B
.
El otro escenario ( descrito en Git Ready, por ejemplo), es llevar su trabajo directamente a B
través de un rebase (que conserva todas sus buenas confirmaciones, o incluso le da la oportunidad de reordenarlas a través de un rebase interactivo).
En ese caso (cuando reescribe mientras está en la rama B), tiene razón: no se necesita más fusión:
Un árbol Git por defecto cuando no nos hemos fusionado ni rebajado
obtenemos rebasando:
El segundo escenario tiene que ver con: ¿cómo vuelvo a incorporar las nuevas funciones en master?
Mi punto, al describir el primer escenario de rebase, es recordarles a todos que una rebase también se puede usar como un paso preliminar para eso (que es "volver a poner la nueva característica en master").
Puede usar rebase para primero traer el maestro "a" la rama de nuevas características: la rebase reproducirá confirmaciones de nuevas características desde HEAD master
, pero aún en la rama de nuevas características, moviendo efectivamente el punto de inicio de la rama desde una antigua confirmación maestra a HEAD-master
.
Eso le permite resolver cualquier conflicto en su rama (es decir, de forma aislada, mientras permite que el maestro continúe evolucionando en paralelo si su etapa de resolución de conflictos toma demasiado tiempo).
Luego puede cambiar a master y fusionar new-feature
(o volver new-feature
a crear una base master
si desea conservar los commits realizados en sunew-feature
rama).
Entonces:
master
.Si tiene alguna duda, use merge.
Las únicas diferencias entre un rebase y una fusión son:
Entonces, la respuesta corta es elegir rebase o fusionar en función de cómo desea que se vea su historial .
Hay algunos factores que debe considerar al elegir qué operación usar.
Si es así, no rebases. Rebase destruye la rama y esos desarrolladores tendrán repositorios rotos / inconsistentes a menos que lo usen git pull --rebase
. Esta es una buena manera de molestar a otros desarrolladores rápidamente.
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 proceden de una época en que las empresas podían permitirse personal dedicado para ocuparse de la ramificación 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 un rebase por ningún motivo.
Algunos equipos usan el modelo de rama por característica donde cada rama representa una característica (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 fusión de esa rama (para ser justos, esta es una operación rara). O diferencie una característica al comparar dos ramas (más común). Rebase destruiría la rama y esto no sería sencillo.
También he trabajado en equipos que usaron el modelo de sucursal por desarrollador (todos hemos estado allí). En este caso, la rama en sí no transmite ninguna información adicional (el commit ya tiene el autor). No habría daño en el rebase.
Revertir (como al deshacer) un rebase es considerablemente difícil y / o imposible (si el rebase tuvo conflictos) en comparación con revertir una fusión. Si crees que existe la posibilidad de que quieras revertir, usa merge.
Las operaciones de rebase deben extraerse con un correspondiente git pull --rebase
. Si está trabajando solo, puede recordar cuál debe usar en el momento adecuado. Si está trabajando en un equipo, será muy difícil de coordinar. Esta es la razón por la cual la mayoría de los flujos de trabajo de rebase recomiendan usar rebase para todas las fusiones (y git pull --rebase
para todos los pulls).
Suponiendo que tiene la siguiente fusión:
B -- C
/ \
A--------D
Algunas personas afirmarán que la fusión "destruye" el historial de confirmación porque si mirara el registro de solo la rama maestra (A - D), perdería los mensajes importantes de confirmación contenidos en B y C.
Si esto fuera cierto, no tendríamos preguntas como esta . Básicamente, verá B y C a menos que explícitamente solicite no verlos (usando --first-parent). Esto es muy fácil de probar por ti mismo.
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 comprometen dos veces al día mientras hacen la transición del trabajo al hogar), entonces podría haber muchos compromisos para una rama determinada. Es posible que muchas de esas confirmaciones no se parezcan en nada 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 intentaban modificar mis cambios, podría ser una operación bastante tediosa.
Si le gusta usar un alias rm
para rm -rf
"ahorrar tiempo", entonces tal vez sea necesario un rebase.
Siempre pienso que algún día me encontraré con un escenario en el que Git rebase es la herramienta increíble que resuelve el problema. Al igual que creo, me encontraré con un escenario en el que el reflog de Git es una herramienta increíble que resuelve mi problema. He trabajado con Git por más de cinco años. No ha sucedido
Las historias desordenadas nunca han sido realmente un problema para mí. No solo leo mi historia de compromiso como una novela emocionante. La mayoría de las veces necesito un historial, voy a usar Git culpar o Git bisecar de todos modos. En ese caso, tener la confirmación de fusión es realmente útil para mí, porque si la fusión introdujo el problema, esa es información significativa para mí.
Me siento obligado a mencionar que personalmente me he suavizado con el uso de rebase, aunque mi consejo general sigue vigente. Recientemente he estado interactuando mucho con el proyecto Angular 2 Material . Han utilizado rebase para mantener un historial de confirmación muy limpio. Esto me ha permitido ver muy fácilmente qué confirmación corrigió un defecto dado y si esa confirmación se incluyó o no en una versión. Sirve como un gran ejemplo de uso de rebase correctamente.
Muchas respuestas aquí dicen que la fusión convierte todas sus confirmaciones en una sola y, por lo tanto, sugiere utilizar rebase para preservar sus confirmaciones. Esto es incorrecto. Y una mala idea si ya has empujado tus commits .
La fusión no borra tus commits. ¡La fusión preserva la historia! (solo mira gitk) Rebase reescribe el historial, lo cual es una mala cosa después de haberlo empujado .
Use merge - no rebase cuando ya haya presionado.
Aquí está la versión de Linus (autor de Git) (ahora alojada en mi propio blog, recuperada por Wayback Machine ). Es una muy buena lectura.
O puede leer mi propia versión de la misma idea a continuación.
Rebasar una rama en master:
Por el contrario, fusionar una rama de tema en maestro:
TLDR: depende de lo más importante: una historia ordenada o una representación real de la secuencia de desarrollo
Si un historial ordenado es lo más importante, primero debe volver a crear una base y luego combinar los cambios, para que quede claro exactamente cuál es el nuevo código. Si ya ha empujado su rama, no rebase a menos que pueda lidiar con las consecuencias.
Si la representación verdadera de la secuencia es lo más importante, usted se fusionaría sin rebasar.
Fusionar significa: crear una nueva confirmación única que combine mis cambios en el destino. Nota: Esta nueva confirmación tendrá dos padres: la última confirmación de su cadena de confirmaciones y la última confirmación de la otra rama que está fusionando.
Rebase significa: Crear una serie completamente nueva de confirmaciones, utilizando mi conjunto actual de confirmaciones como pistas. En otras palabras, calcule cómo se habrían visto mis cambios si hubiera comenzado a hacerlos desde el punto en el que estoy haciendo un rebase. Después de la nueva versión, por lo tanto, es posible que deba volver a probar sus cambios y durante la nueva versión, posiblemente tenga algunos conflictos.
Dado esto, ¿por qué rebajas? Solo para mantener claro el historial de desarrollo. Supongamos que está trabajando en la función X y cuando haya terminado, combine sus cambios. El destino ahora tendrá una única confirmación que dirá algo similar a "Función X añadida". Ahora, en lugar de fusionar, si reorganiza y luego fusiona, el historial de desarrollo de destino contendría todas las confirmaciones individuales en una sola progresión lógica. Esto hace que revisar los cambios más adelante sea mucho más fácil. Imagine lo difícil que sería revisar el historial de desarrollo si 50 desarrolladores fusionaran varias funciones todo el tiempo.
Dicho esto, si ya ha empujado la rama en la que está trabajando en sentido ascendente, no debe volver a redactar, sino fusionar. Para las sucursales que no han sido empujadas aguas arriba, rebase, pruebe y fusione.
Otro momento en el que es posible que desee cambiar la base es cuando desea deshacerse de los commits de su rama antes de avanzar. Por ejemplo: los commits que introducen algún código de depuración desde el principio y otros commits más adelante que limpian ese código. La única forma de hacer esto es realizar un rebase interactivo:git rebase -i <branch/commit/tag>
ACTUALIZACIÓN: también desea utilizar rebase cuando use Git para interactuar con un sistema de control de versiones que no admite historial no lineal ( Subversion, por ejemplo). Cuando se usa el puente git-svn, es muy importante que los cambios que fusiones de nuevo en Subversion sean una lista secuencial de cambios además de los cambios más recientes en troncal. Solo hay dos formas de hacerlo: (1) Vuelva a crear manualmente los cambios y (2) Usando el comando rebase, que es mucho más rápido.
ACTUALIZACIÓN 2: Una forma adicional de pensar en un rebase es que permite una especie de mapeo desde su estilo de desarrollo al estilo aceptado en el repositorio con el que se está comprometiendo. Digamos que te gusta cometer en trozos pequeños y pequeños. Tiene una confirmación para corregir un error tipográfico, una confirmación para deshacerse del código no utilizado, etc. Cuando haya terminado lo que necesita hacer, tiene una larga serie de confirmaciones. Ahora, digamos que el repositorio al que se compromete fomenta grandes confirmaciones, por lo que para el trabajo que está haciendo, uno esperaría una o quizás dos confirmaciones. ¿Cómo tomas tu cadena de commits y los comprimes a lo que se espera? Usaría un rebase interactivo y aplastaría sus pequeños commits en menos trozos más grandes. Lo mismo es cierto si se necesitaba lo contrario: si su estilo consistía en algunas confirmaciones grandes, pero el repositorio exigía largas cadenas de pequeños commits. También usarías un rebase para hacer eso. Si se fusionó, ahora ha injertado su estilo de confirmación en el repositorio principal. Si hay muchos desarrolladores, puedes imaginar lo difícil que sería seguir un historial con varios estilos de confirmación diferentes después de un tiempo.
ACTUALIZACIÓN 3: Does one still need to merge after a successful rebase?
Sí, lo haces. La razón es que un rebase implica esencialmente un "cambio" de commits. Como he dicho anteriormente, estas confirmaciones se calculan, pero si tuvo 14 confirmaciones desde el punto de bifurcación, suponiendo que nada salga mal con su rebase, tendrá 14 confirmaciones por delante (del punto en el que rebase) después El rebase está hecho. Tenías una rama antes de un rebase. Tendrás una rama de la misma longitud después. Aún necesita fusionarse antes de publicar sus cambios. En otras palabras, rebase tantas veces como desee (nuevamente, solo si no ha empujado sus cambios en sentido ascendente). Fusionarse solo después de rebase.
git merge
admite la --no-ff
opción que lo obliga a realizar una confirmación de fusión.
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.
Comprender fusionar un poco mejor
Cuando Git realiza una fusión, busca tres confirmaciones:
Avance rápido o confirmación de fusión
En casos muy simples, una de las dos ramas no tiene nuevas confirmaciones desde que ocurrió la ramificación: su última confirmación sigue siendo el antepasado común.
En este caso, realizar la integración es muy simple: Git puede agregar todas las 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 exactamente la misma historia.
Sin embargo, en muchos casos, ambas ramas avanzaron individualmente.
Para realizar una integración, Git tendrá que crear una nueva confirmación que contenga las diferencias entre ellos: la confirmación de fusió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.
Un commit de fusión es un poco diferente: en lugar de ser creado por un desarrollador, Git lo 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 el gráfico de confirmación correspondiente.
Integrando con Rebase
Algunas personas prefieren ir sin tales compromisos de fusión automática. En cambio, 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.
Pasemos por una operación de rebase paso a paso. El escenario es el mismo que en los ejemplos anteriores: queremos integrar los cambios de la rama B en la rama A, pero ahora usando rebase.
Haremos esto en tres pasos.
git rebase branch-A // Synchronises 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 "deshacerá" todas las confirmaciones en la rama A que sucedió después de que las líneas comenzaron a ramificarse (después de la confirmación del ancestro común). Sin embargo, por supuesto, no los descartará: en su lugar, puede pensar en esas confirmaciones como "guardadas temporalmente".
A continuación, aplica los commits 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, además de las confirmaciones integradas de la rama B (se vuelven a basar).
El resultado parece que el desarrollo había sucedido en línea recta. En lugar de una confirmación de fusión que contiene todos los cambios combinados, se mantuvo la estructura de confirmación original.
Finalmente, obtienes una rama limpia branch -A sin commits no deseados y autogenerados.
Nota: Tomado de la impresionante publicación de git-tower
. Las desventajas de rebase
también es una buena lectura en la misma publicación.
Antes de fusionar / rebase:
A <- B <- C [master]
^
\
D <- E [branch]
Después git merge master
:
A <- B <- C
^ ^
\ \
D <- E <- F
Después git rebase master
:
A <- B <- C <- D' <- E'
(A, B, C, D, E y F son confirmaciones)
Este ejemplo y mucha más información bien ilustrada sobre Git se puede encontrar en Git The Basics Tutorial .
Esta oración lo entiende:
En general, la forma de obtener lo mejor de ambos mundos es modificar los cambios locales que ha realizado, pero que aún no ha compartido, antes de presionarlos para limpiar su historia, pero nunca rebase nada que haya empujado a alguna parte .
Esta respuesta está ampliamente orientada en torno a Git Flow . Las tablas se han generado con el agradable Generador de tablas ASCII , y los árboles de historia con este maravilloso comando ( alias 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 consistentes con los árboles de historia. Vea también la diferencia entre git merge
y git merge --no-ff
primero (generalmente desea usarlo git merge --no-ff
ya que hace que su historial se vea más cerca de 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 combine características en desarrollo, nunca vuelva a desarrollar el desarrollo a partir de características . Esta es una consecuencia de la Regla de Oro de Rebasar :
La regla de oro
git rebase
es nunca usarlo en las ramas públicas .
Nunca rebajes nada que hayas empujado a alguna parte.
Yo personalmente agregaría: a menos que sea una rama de características Y usted y su equipo sean conscientes de las consecuencias .
Por lo tanto, la cuestión 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, dado que no estoy seguro de que haya una solución mejor ( existe un debate ), solo proporcionaré cómo se comportan ambos comandos. En mi caso, prefiero usarlo git rebase
ya que produce un árbol de historia más agradable :)
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
develop
a una rama característicagit 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 develop
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 develop
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
git cherry-pick
Cuando solo necesita una confirmación específica, git cherry-pick
es una buena solución (la -x
opción agrega una línea que dice " (recogido de la confirmación ...) " al cuerpo del mensaje de confirmación original, por lo que generalmente es una buena idea usarlo, 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, bien explicado aquí . Pero simplemente, si lo habilita, ya no tendrá que resolver el mismo conflicto varias veces.
El libro Pro Git tiene una muy buena explicación en la página de rebase .
Básicamente, una fusión tomará dos commits y los combinará.
Un rebase irá al antepasado común en los dos y aplicará los cambios de forma incremental uno encima del otro. Esto lo convierte en un historial más "limpio" y más lineal.
Pero cuando rebasas, abandonas las confirmaciones anteriores y creas nuevas. Por lo tanto, nunca debe volver a crear un repositorio que sea público. Las otras personas que trabajan en el repositorio te odiarán.
Solo por esa razón me fusiono casi exclusivamente. El 99% de las veces mis ramas no difieren tanto, por lo que si hay conflictos, solo en uno o dos lugares.
Git rebase se utiliza para hacer que las rutas de ramificación en el historial sean más limpias y la estructura del repositorio sea lineal.
También se usa para mantener las ramas creadas por usted de manera privada, ya que después de volver a modificar y enviar los cambios al servidor, si elimina su rama, no habrá evidencia de la rama en la que ha trabajado. Entonces su sucursal es ahora su preocupación local.
Después de hacer rebase, también nos deshacemos de un commit adicional que solíamos ver si hacemos una fusión normal.
Y sí, uno aún necesita fusionarse después de un rebase exitoso ya que el comando rebase solo coloca su trabajo en la parte superior de la rama que mencionó durante el 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 traer cambios de esta rama a la rama maestra.
Algunos ejemplos prácticos, algo conectados con el desarrollo a gran escala donde Gerrit se utiliza para la revisión y la integración de entrega:
Me fusiono cuando elevo mi rama de características a un nuevo maestro remoto. Esto proporciona un trabajo de elevación mínimo y es fácil seguir la historia 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
Reescribo cuando mi confirmación de entrega falla la integración por cualquier razón, y necesito actualizarlo 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
Se explicó muchas veces qué rebase y qué fusión es, pero ¿cuándo debería usar qué?
¿Cuándo deberías usar rebase?
A medida que Git rebase cambia la historia. Por lo tanto, no debe usarlo cuando alguien más esté trabajando en la misma rama / si lo ha presionado. Pero si tiene una sucursal local, puede hacer una fusión de rebase master antes de fusionar su sucursal nuevamente en master para mantener un historial más limpio. Al hacer esto, después de fusionarse con la rama maestra, no será visible que usó una rama en la rama maestra: el historial es "más limpio" ya que no ha generado automáticamente "fusionado ..", pero aún tiene el historial completo en su rama maestra sin haber confirmado "fusionado .." autogenerado.
Sin embargo, asegúrese de usar git merge feature-branch --ff-only
para asegurarse de que no haya conflictos al crear una sola confirmación al fusionar su función con main. Esto es interesante si está utilizando ramas de características para cada tarea en la que trabaja a medida que obtiene el historial de la rama de características, pero no una confirmación "combinada ..."
Un segundo escenario sería, si se bifurca desde una rama y quiere saber qué ha cambiado en la rama principal. Rebase le brinda la información, ya que incluye cada confirmación individual.
¿Cuándo deberías usar merge?
Cuando no necesita o quiere tener todo el historial de una rama de características en su rama maestra o si otros están trabajando en la misma rama / la ha presionado. Si aún desea tener el historial, solo combine master en la rama de características antes de fusionar la rama de características en master. Esto dará como resultado una fusión de avance rápido donde tiene el historial de la rama de características en su maestro (incluida la confirmación de fusión que estaba en su rama de características porque fusionó el maestro en ella).
¿Cuándo lo uso git rebase
? Casi nunca, porque reescribe la historia. git merge
es casi siempre la opción preferible, porque respeta lo que realmente sucedió en su proyecto.