¡La mayoría de las respuestas anteriores son peligrosamente incorrectas!
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
) esos 3 commits serían descartados en silencio 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 los cambios no confirmados).
- Luego se bifurca
newbranch
.
- Luego, elige los 3 commits nuevamente
newbranch
. Dado que una rama ya no hace referencia a ellos, lo hace mediante el uso de refit de git : HEAD@{2}
es el compromiso que HEAD
solía referirse a 2 operaciones hace, es decir, antes de 1. desprotegido newbranch
y 2. utilizado git reset
para descartar los 3 compromisos.
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 ejecutarlo 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 previamente extraída, en lugar de oldbranch
).
Explicación técnica
¿Por qué git rebase
descartar los 3 commits después del primer ejemplo? Es porque git rebase
sin argumentos habilita la --fork-point
opción por defecto, que usa el registro local para tratar de ser robusto contra la rama ascendente que se empuja a la fuerza.
Supongamos que ramificó origen / maestro cuando contenía 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 presionando forzar origin / master 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 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 lo desea en su rama temática una vez que la rama temática se haya modificado:
M1--M3' <-- origin/master
\
T1'--T2'--T3' <-- topic (rebased)
Este comportamiento tiene sentido y, por lo general, es lo que se debe hacer al rebasar.
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 ve newbranch
que se ha bifurcado de la rama ascendente en una revisión que incluye los 3 commits, luego reset --hard
reescribe el historial del upstream para eliminar los commits, por lo que la próxima vez que git rebase
lo ejecutes los descarta como cualquier otro commit que se haya eliminado del upstream.
Pero en este caso particular, queremos que esas 3 confirmaciones se consideren como parte de la rama del tema. Para lograr eso, necesitamos bifurcar el flujo ascendente en la revisión anterior que no incluye las 3 confirmaciones. Eso es lo que hacen mis soluciones sugeridas, por lo tanto, ambos dejan el registro 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 .