Mueva las confirmaciones más recientes a una nueva rama con Git

Resuelto Mark A. Nicolosi asked hace 15 años • 23 respuestas

¿Cómo muevo mis confirmaciones recientes mastera una nueva rama y restablezco el maestro antes de que se realizaran esas confirmaciones? por ejemplo, de esto:

master A - B - C - D - E

A esto:

newbranch     C - D - E
             /
master A - B 
Mark A. Nicolosi avatar Oct 27 '09 10:10 Mark A. Nicolosi
Aceptado

Mudarse a una sucursal existente

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

git checkout existingbranch
git merge branchToMoveCommitFrom
git checkout branchToMoveCommitFrom
git reset --hard HEAD~3 # Go back 3 commits. You *will* lose uncommitted work.
git checkout existingbranch

Puedes almacenar ediciones no confirmadas en tu alijo antes de hacer esto, usando git stash. Una vez completado, puede recuperar las ediciones ocultas no confirmadas congit stash pop

Mudarse a una nueva sucursal

ADVERTENCIA: Este método funciona porque estás creando una nueva rama con el primer comando: git branch newbranch. Si desea mover confirmaciones a una rama existente, debe fusionar sus cambios en la rama existente antes de ejecutarlos git reset --hard HEAD~3(consulte Mover a una rama existente arriba). Si no combina sus cambios primero, se perderán.

A menos que existan otras circunstancias involucradas, esto se puede hacer fácilmente ramificando y retrocediendo.

# Note: Any changes not committed will be lost.
git branch newbranch      # Create a new branch, saving the desired commits
git checkout master       # checkout master, this is the place you want to go back
git reset --hard 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

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

git reset --hard a1b2c3d4

Nota: Sólo " perderás " confirmaciones de la rama maestra, pero no te preocupes, ¡las tendrás en la nueva rama! Una manera fácil de verificar que, después de completar la secuencia de comandos de 4 pasos anterior, es mirar git log -n4cuál mostrará el historial de newbranch que realmente retuvo las 3 confirmaciones (y la razón es que newbranch se creó en el momento en que esos cambios ya se confirmaron). ¡en maestro!). Solo se eliminaron de master, ya que git resetsolo afectaron a la rama que se desprotegió en el momento de su ejecución, es decir, master (consulte la descripción de git reset : Restablecer HEAD actual al estado especificado ). git statussin embargo, no mostrará ningún pago en la nueva sucursal, lo que puede resultar sorprendente al principio, pero en realidad es lo esperado.

Por último, es posible que tengas que forzar la transferencia de tus últimos cambios al repositorio principal:

git push origin master --force

ADVERTENCIA: Con Git versión 2.0 y posteriores, si posteriormente realiza git rebasela nueva rama sobre la masterrama original (), es posible que necesite una --no-fork-pointopción explícita durante la rebase para evitar perder las confirmaciones transferidas. Haberlo branch.autosetuprebase alwaysconfigurado hace que esto sea más probable. Consulte la respuesta de John Mellor para obtener más detalles.

Duc Filan avatar Oct 27 '2009 03:10 Duc Filan

Para aquellos que se preguntan por qué funciona (como me ocurrió a mí al principio):

Quiere volver a C y mover D y E a la nueva rama. Así es como se ve al principio:

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

Después git branch newBranch:

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

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

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

Dado que una rama es solo un puntero, el maestro señaló la última confirmación. Cuando creaste newBranch , simplemente creaste un nuevo puntero a la última confirmación. Luego, al usar git reset, movió el puntero maestro hacia atrás dos confirmaciones. Pero como no moviste newBranch , todavía apunta a la confirmación que hizo originalmente.

Ryan Lundy avatar Jul 22 '2011 22:07 Ryan Lundy

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 OP quiere, es un proceso de 2 pasos:

Paso 1: observe qué confirmaciones del maestro desea en unnewbranch

Ejecutar

git checkout master
git log

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

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

Paso 2: colóquelos en elnewbranch

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

O (en Git 1.7.2+, use rangos)

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

git cherry-pick aplica esos tres compromisos a newbranch.

Ivan avatar Feb 07 '2012 16:02 Ivan

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

No hagas esto:

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 newbranch. (ver explicación a continuación)

En lugar de eso, 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 ( --keepes como --hard, pero más seguro, ya que falla en lugar de descartar los cambios no confirmados).
  • Luego se bifurca newbranch.
  • Luego selecciona esas 3 confirmaciones nuevamente en newbranch. Dado que una rama ya no hace referencia a ellos, lo hace usando reflog de git : HEAD@{2}es la confirmación que HEADsolía referirse a hace 2 operaciones, es decir, antes de que 1. revisáramos newbranchy 2. usáramos git resetpara descartar las 3 confirmaciones.

Advertencia: el reflog está habilitado de forma predeterminada, pero si lo ha deshabilitado manualmente (por ejemplo, usando un repositorio git "básico"), no podrá recuperar las 3 confirmaciones 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 prefieres puedes escribir @{-1}- la rama previamente retirada - en lugar de oldbranch).


Explicación técnica

¿Por qué git rebasedescartaría las 3 confirmaciones después del primer ejemplo? Esto se debe a que git rebasesin argumentos se habilita la --fork-pointopción de forma predeterminada, que utiliza el reflog local para tratar de ser robusto frente a la fuerza de la rama ascendente.

Supongamos que se bifurcó en origen/maestro cuando contenía las confirmaciones M1, M2, M3 y luego realizó tres confirmaciones usted mismo:

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

pero luego alguien reescribe el historial forzando origen/maestro para eliminar M2:

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

Usando su reflog local, git rebasepuede ver que se bifurcó de una encarnación anterior de la rama origin/master y, por lo tanto, que las confirmaciones M2 y M3 no son realmente parte de su rama temática. Por lo tanto, se supone razonablemente que, dado que M2 se eliminó de la rama ascendente, ya no lo desea en su rama temática una vez que se cambia la base de la rama temática:

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

Este comportamiento tiene sentido y, en general, es lo correcto al realizar un cambio de base.

Entonces la razón por la que fallan los siguientes comandos:

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

es porque dejan el reflog en el estado incorrecto. Git considera newbranchque se ha bifurcado la rama ascendente en una revisión que incluye las 3 confirmaciones, luego reescribe reset --hardel historial de la cadena ascendente para eliminar las confirmaciones, por lo que la próxima vez que las ejecute, git rebaselas descarta como cualquier otra confirmación que se haya eliminado de la cadena ascendente.

Pero en este caso particular queremos que esas 3 confirmaciones se consideren parte de la rama temática. Para lograr eso, necesitamos deshacernos del upstream 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-pointen los documentos de git rebase y git merge-base .

John Mellor avatar Apr 06 '2016 22:04 John Mellor

Otra forma más de hacer esto, usando solo 2 comandos. También mantiene intacto su á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 antigua : antes de enterarmegit 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 pushhacerlo .es un buen truco de saber.

aragaer avatar Mar 26 '2014 08:03 aragaer