¿Cómo elegir un rango de commits y fusionarlos en otra rama?

Tengo el siguiente diseño de repositorio:

  • rama maestra (producción)
  • integración
  • de trabajo

Lo que quiero conseguir es seleccionar un rango de commits de la rama de trabajo y fusionarlo con la rama de integración. Soy bastante nuevo en git y no puedo averiguar cómo hacer esto exactamente (la selección de rangos de commits en una operación, no la fusión) sin desordenar el repositorio. ¿Algún consejo o idea sobre esto? Gracias.

Solución

Cuando se trata de un rango de commits, el cherry-picking es *no es práctico.

Como se menciona a continuación por Keith Kim, Git 1.7.2+ introdujo la capacidad de hacer cherry-pick de un rango de commits (pero todavía hay que ser consciente de la consecuencia de hacer cherry-pick para futuras fusiones)

git cherry-pick" aprendió a escoger un rango de commits (p.ej. "Cherry-pick A..B" y "Cherry-pick --stdin"), también lo hizo "git revert"; aunque estos no soportan el control de secuenciación más agradable que tiene "Rebase [-i]`".

damian comentarios y nos advierte:

En la forma "A..B", **Adebe ser mayor queB`.
Si están en el orden incorrecto el comando fallará silenciosamente**.

Si quieres escoger el rango B hasta D (inclusive) sería B^..D.
Ver "¿Git crear rama a partir de un rango de commits anteriores?" como ilustración.

Como menciona Jubobs en los comentarios:

Esto asume que B no es un commit raíz; usted' obtendrá un "revisión desconocida`" error de lo contrario.

Nota: a partir de Git 2.9.x/2.10 (Q3 2016), se puede hacer cherry-pick de un rango de commit directamente en una rama huérfana (cabeza vacía): ver "Cómo hacer que una rama existente sea huérfana en git".


Respuesta original (enero de 2010)

Una rebase --onto sería mejor, donde se reproduce el rango de commit dado sobre su rama de integración, como Charles Bailey describió aquí.
(también, busca "Así es como se trasplanta una rama temática basada en una rama a otra" en la página man de git rebase, para ver un ejemplo práctico de git rebase --onto)

Si tu rama actual es la de integración:

`lang-all: lang-sh -->

# Checkout a new temporary branch at the current location
git checkout -b tmp

# Move the integration branch to the head of the new patchset
git branch -f integration last_SHA-1_of_working_branch_range

# Rebase the patchset onto tmp, the old location of integration
git rebase --onto tmp first_SHA-1_of_working_branch_range~1 integration

Eso reproducirá todo lo que hay entre ellos:

  • después del padre de first_SHA-1_of_working_branch_range (de ahí el ~1): la primera confirmación que quieres reproducir
  • hasta "La integración" (que apunta a la última confirmación que desea reproducir, de la ramaworking`)

hasta "Tmp" (que apunta a donde apuntaba antesintegración`)

Si hay algún conflicto cuando se reproduce uno de esos commits

  • o bien lo resuelve y ejecuta "git rebase --continue`".
  • o saltar este parche, y en su lugar ejecutar "git rebase --skip`";
  • o cancelar todo con un "git rebase --abort" (y volver a poner la ramaintegraciónen la ramatmp`)

Después de ese rebase --onto, integration volverá a estar en el último commit de la rama de integración (es decir "tmp" rama + todos los commits reproducidos)

Con cherry-picking o rebase --onto, no olvide que tiene consecuencias en las fusiones posteriores, como se describe aquí.


Una solución pura de cherry-pick" es discutida aquí, e implicaría algo como:

Si desea utilizar un enfoque de parches entonces "git format-patch|git am" y "git cherry" son sus opciones.
Actualmente, git cherry-pick sólo acepta un único commit, pero si quieres escoger el rango de B a D eso sería B^..D en la jerga de git, así que

git rev-list --reverse --topo-order B^..D | while read rev 
do 
  git cherry-pick $rev || break 
done 

Pero de todos modos, cuando necesites "repetir" un rango de confirmaciones, la palabra "repetir" debería empujarte a usar la función "rebase" de Git.

Comentarios (17)

¿Estás seguro de que no quieres fusionar las ramas? Si la rama de trabajo tiene algunos commits recientes que no quieres, puedes simplemente crear una nueva rama con un HEAD en el punto que quieras.

Ahora bien, si realmente quieres seleccionar un rango de commits, por la razón que sea, una forma elegante de hacerlo es simplemente tirar de un conjunto de parches y aplicarlo a tu nueva rama de integración:

git format-patch A..B
git checkout integration
git am *.patch

Esto es esencialmente lo que git-rebase hace de todos modos, pero sin la necesidad de jugar. Puedes añadir --3way a git-am si necesitas fusionar. Asegúrate de que no hay otros archivos *.patch ya en el directorio donde haces esto, si sigues las instrucciones al pie de la letra...

Comentarios (1)

He envuelto el código de VonC's en un corto script bash, git-multi-cherry-pick, para facilitar su ejecución:

#!/bin/bash

if [ -z $1 ]; then
    echo "Equivalent to running git-cherry-pick on each of the commits in the range specified.";
    echo "";
    echo "Usage:  $0 start^..end";
    echo "";
    exit 1;
fi

git rev-list --reverse --topo-order $1 | while read rev 
do 
  git cherry-pick $rev || break 
done 

Actualmente estoy usando esto mientras reconstruyo la historia de un proyecto que tenía código de terceros y personalizaciones mezcladas en el mismo trunk de svn. Ahora estoy separando el código de terceros, los módulos de terceros y las personalizaciones en sus propias ramas git para una mejor comprensión de las personalizaciones en el futuro. El git-cherry-pick es útil en esta situación ya que tengo dos árboles en el mismo repositorio, pero sin un ancestro compartido.

Comentarios (0)