Mueva las confirmaciones más recientes a una nueva rama con Git
¿Cómo muevo mis confirmaciones recientes master
a 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
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~3
simplemente 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 -n4
cuá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 reset
solo 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 status
sin 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 rebase
la nueva rama sobre la master
rama original (), es posible que necesite una --no-fork-point
opción explícita durante la rebase para evitar perder las confirmaciones transferidas. Haberlo branch.autosetuprebase always
configurado hace que esto sea más probable. Consulte la respuesta de John Mellor para obtener más detalles.
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.
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.
¡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 (
--keep
es 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 queHEAD
solía referirse a hace 2 operaciones, es decir, antes de que 1. revisáramosnewbranch
y 2. usáramosgit reset
para 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 rebase
descartaría las 3 confirmaciones después del primer ejemplo? Esto se debe a que git rebase
sin argumentos se habilita la --fork-point
opció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 rebase
puede 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 newbranch
que se ha bifurcado la rama ascendente en una revisión que incluye las 3 confirmaciones, luego reescribe reset --hard
el historial de la cadena ascendente para eliminar las confirmaciones, por lo que la próxima vez que las ejecute, git rebase
las 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-point
en los documentos de git rebase y git merge-base .
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 push
hacerlo .
es un buen truco de saber.