Mover los compromisos más recientes a una nueva sucursal con Git

git git-branch branching-and-merging


Me gustaría trasladar los últimos compromisos que me he comprometido a dominar a una nueva rama y llevar al maestro de vuelta a antes de que se hicieran esos compromisos.Desafortunadamente,mi Git-fu no es lo suficientemente fuerte todavía,¿alguna ayuda?

Es decir,¿cómo puedo pasar de esto

master A - B - C - D - E

a esto?

newbranch     C - D - E
             /
master A - B 



Answer 1 Duc Filan


Mudarse a una sucursal existente

Si desea mover sus confirmaciones a una rama existente , se verá así:

git checkout existingbranch
git merge master         # Bring the commits here
git checkout master
git reset --keep HEAD~3  # Move master back by 3 commits.
git checkout existingbranch

La opción --keep conserva cualquier cambio no confirmado que pueda tener en archivos no relacionados, o aborta si esos cambios tuvieran que sobrescribirse, de manera similar a lo que hace git checkout . Si aborta, git stash sus cambios y --hard a intentarlo, o use --hard para perder los cambios (¡incluso de archivos que no cambiaron entre los commits!)

Mudándose a una nueva sucursal

Este método funciona creando una nueva rama con el primer comando ( git branch newbranch ) pero sin cambiar a ella. Luego, retrocedemos la rama actual (maestra) y cambiamos a la nueva rama para continuar trabajando.

git branch newbranch      # Create a new branch, containing all current commits
git reset --keep HEAD~3   # Move master back by 3 commits (Make sure you know how many commits you need to go back)
git checkout newbranch    # Go to the new branch that still has the desired commits
# Warning: after this it's not safe to do a rebase in newbranch without extra care.

Pero asegúrese de cuántas confirmaciones para volver. Alternativamente, en lugar de HEAD~3 , simplemente puede proporcionar el hash de la confirmación (o la referencia como origin/master ) a la que desea volver, por ejemplo:

git reset --keep a1b2c3d4

ADVERTENCIA: con Git versión 2.0 y posterior, si luego git rebase la nueva rama en la rama original ( master ), es posible que necesite una --no-fork-point explícita sin punto de bifurcación durante la nueva versión para evitar perder las confirmaciones que movió de rama maestra Tener branch.autosetuprebase always configurado hace que esto sea más probable. Vea la respuesta de John Mellor para más detalles.




Answer 2 Ryan Lundy


Para aquellos que se preguntan por qué funciona (como yo al principio):

Quieres volver a C,y trasladar a D y E a la nueva sucursal.Esto es lo que parece al principio:

A-B-C-D-E (HEAD)
        ↑
      master

Después de git branch newBranch :

    newBranch
        ↓
A-B-C-D-E (HEAD)
        ↑
      master

Después de git reset --hard HEAD~2 :

    newBranch
        ↓
A-B-C-D-E (HEAD)
    ↑
  master

Como una rama es solo un puntero, el maestro señaló la última confirmación. Cuando creó newBranch , simplemente creó un nuevo puntero a la última confirmación. Luego, usando git reset , movió el puntero maestro hacia atrás dos commits. Pero como no movió newBranch , todavía señala la confirmación que hizo originalmente.




Answer 3 Ivan


En general...

El método expuesto por sykora es la mejor opción en este caso. Pero a veces no es lo más fácil y no es un método general. Para un método general, use git cherry-pick :

Para lograr lo que quiere OP,es un proceso de dos pasos:

Paso 1: newbranch qué confirmaciones del maestro desea en una rama nueva

Execute

git checkout master
git log

Tenga en cuenta los hashes de (digamos 3) confirmaciones que desee en newbranch . Aquí usaré:
C commit: 9aa1233
D commit: 453ac3d
E commit: 612ecb3

Nota: Puede usar los primeros siete caracteres o el hash de confirmación completo

Paso 2: newbranch en la rama nueva

git checkout newbranch
git cherry-pick 612ecb3
git cherry-pick 453ac3d
git cherry-pick 9aa1233

O (en Git 1.7.2+,usar rangos)

git checkout newbranch
git cherry-pick 612ecb3~1..9aa1233

git cherry-pick aplica esos tres commits a newbranch.




Answer 4 John Mellor


¡La mayoría de las respuestas anteriores están peligrosamente equivocadas!

No lo hagas:

git branch -t newbranch
git reset --hard HEAD~3
git checkout newbranch

¡Como la próxima vez que ejecute git rebase (o git pull --rebase ), esas 3 confirmaciones se descartarán silenciosamente de newbranch ! (ver explicación a continuación)

En cambio, haz esto:

git reset --keep HEAD~3
git checkout -t -b newbranch
git cherry-pick ..HEAD@{2}
  • Primero, descarta las 3 confirmaciones más recientes ( --keep es como --hard , pero más seguro, ya que falla en lugar de descartar cambios no confirmados).
  • Luego se bifurca de newbranch .
  • Luego, recoge los 3 commits nuevamente en newbranch . Dado que una rama ya no hace referencia a ellos, lo hace mediante el uso de refit de git : HEAD@{2} es el commit que HEAD solía referirse a 2 operaciones hace, es decir, antes de que 1. comprobáramos newbranch y 2. use git reset para descartar las 3 confirmaciones.

Advertencia: el reflog está habilitado de forma predeterminada, pero si lo ha deshabilitado manualmente (por ejemplo, utilizando un repositorio git "desnudo"), no podrá recuperar los 3 commits después de ejecutar git reset --keep HEAD~3 .

Una alternativa que no depende del reflog es:

# newbranch will omit the 3 most recent commits.
git checkout -b newbranch HEAD~3
git branch --set-upstream-to=oldbranch
# Cherry-picks the extra commits from oldbranch.
git cherry-pick ..oldbranch
# Discards the 3 most recent commits from oldbranch.
git branch --force oldbranch oldbranch~3

(si lo prefiere, puede escribir @{-1} , la rama desprotegida anteriormente, en lugar de oldbranch ).


Explicación técnica

¿Por qué git rebase descarta los 3 commits después del primer ejemplo? Es porque git rebase sin argumentos habilita la opción --fork-point de forma predeterminada, que utiliza el registro local para tratar de ser robusto contra la rama ascendente que se empuja a la fuerza.

Supongamos que te desviaste del origen/maestro cuando contenía confirmaciones M1,M2,M3,y luego hiciste tres confirmaciones tú mismo:

M1--M2--M3  <-- origin/master
         \
          T1--T2--T3  <-- topic

pero entonces alguien reescribe la historia empujando a la fuerza el origen/maestro para eliminar el M2:

M1--M3'  <-- origin/master
 \
  M2--M3--T1--T2--T3  <-- topic

Usando su reflog local, git rebase puede ver que se bifurcó de una encarnación anterior de la rama de origen / maestra, y por lo tanto, las confirmaciones de M2 ​​y M3 no son realmente parte de su rama de tema. Por lo tanto, se supone razonablemente que, dado que M2 se eliminó de la rama ascendente, ya no la quiere en su rama temática ni una vez que la rama temática se haya modificado:

M1--M3'  <-- origin/master
     \
      T1'--T2'--T3'  <-- topic (rebased)

Este comportamiento tiene sentido,y generalmente es lo correcto cuando se rebaja.

Así que la razón por la que los siguientes comandos fallan:

git branch -t newbranch
git reset --hard HEAD~3
git checkout newbranch

es porque dejan el reflog en el estado incorrecto. Git ve a newbranch como una bifurcación de la rama ascendente en una revisión que incluye los 3 commits, luego el reset --hard reescribe el historial del upstream para eliminar los commits, por lo que la próxima vez que ejecute git rebase los descartará como cualquier otro commit que ha sido eliminado de la corriente arriba.

Pero en este caso en particular queremos que esos 3 compromisos sean considerados como parte de la rama temática.Para lograrlo,necesitamos bifurcarnos en la revisión anterior que no incluye las 3 confirmaciones.Eso es lo que hacen mis soluciones sugeridas,por lo que ambas dejan el reflog en el estado correcto.

Para obtener más detalles, consulte la definición de --fork-point en los documentos git rebase y git merge-base .




Answer 5 aragaer


Otra forma de hacer esto,usando sólo dos comandos.También mantiene intacto el árbol de trabajo actual.

git checkout -b newbranch # switch to a new branch
git branch -f master HEAD~3 # make master point to some older commit

Versión anterior: antes de aprender sobre git branch -f

git checkout -b newbranch # switch to a new branch
git push . +HEAD~3:master # make master point to some older commit 

Ser capaz de push a . Es un buen truco para saber.