trabajando - Mueva los compromisos más recientes a una nueva rama con Git
seleccionar una rama git (11)
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: newbranch
confirmación del maestro que desea en un newbranch
Ejecutar
git checkout master
git log
Tenga en cuenta los hashes de (digamos 3) las confirmaciones que desea en newbranch
. Aquí voy a utilizar:
C commit: 9aa1233
D commit: 453ac3d
E commit: 612ecb3
Nota: Puede usar los primeros siete caracteres o todo el hash de confirmación.
Paso 2 - newbranch
en el newbranch
git checkout newbranch
git cherry-pick 612ecb3
git cherry-pick 453ac3d
git cherry-pick 9aa1233
O (en Git 1.7.2+, usa rangos)
git checkout newbranch
git cherry-pick 612ecb3~1..9aa1233
git cherry-pick aplica esos tres compromisos a newbranch.
Me gustaría mover los últimos compromisos que me he comprometido a dominar a una nueva rama y recuperar el maestro antes de que se realizaran esos compromisos. Desafortunadamente, mi Git-fu no es lo suficientemente fuerte todavía, ¿alguna ayuda?
Es decir, ¿cómo puedo ir de esto?
master A - B - C - D - E
¿a esto?
newbranch C - D - E
/
master A - B
Solución mucho más simple usando git stash
Si:
- Su objetivo principal es revertir
master
, y - Desea mantener los cambios pero no se preocupa especialmente por los compromisos individuales, y
- Aún no has empujado, y
- Quieres que esto sea fácil y no complicado con ramas temporales y otros dolores de cabeza
Entonces lo siguiente es mucho más simple (comenzando en el master
rama que tiene tres confirmaciones erróneas):
git reset HEAD~3
git stash
git checkout newbranch
git stash pop
Lo que hace esto, por número de línea.
- Deshace las últimas tres confirmaciones (y sus mensajes) para
master
, pero deja todos los archivos de trabajo intactos - Guarda todos los cambios del archivo de trabajo, haciendo que el árbol de trabajo
master
igual al estado HEAD ~ 3 - Cambia a una sucursal existente
newbranch
- Aplica los cambios ocultos a su directorio de trabajo y borra el alijo
Ahora puedes usar git add
y git commit
como lo harías normalmente. Todas las nuevas confirmaciones se agregarán a newbranch
.
Lo que esto no hace
- No deja ramas temporales aleatorias que abarrotan tu árbol.
- No conserva los mensajes de confirmación y confirmación erróneos, por lo que deberá agregar un nuevo mensaje de confirmación a esta nueva confirmación.
Metas
El OP declaró que el objetivo era "recuperar a Master antes de que se realizaran esas confirmaciones" sin perder los cambios y esta solución lo hace.
Hago esto al menos una vez a la semana cuando accidentalmente hago nuevos compromisos para master
lugar de develop
. Por lo general, solo tengo un compromiso para revertir, en cuyo caso usar git reset HEAD^
en la línea 1 es una forma más sencilla de revertir solo un compromiso.
No hagas esto si presionaste los cambios del maestro en sentido ascendente
Alguien más puede haber sacado esos cambios. Si solo está reescribiendo su maestro local, no hay ningún impacto cuando se empuja en sentido ascendente, pero empujar un historial reescrito a los colaboradores puede causar dolores de cabeza.
¡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 de newbranch
! (ver explicación abajo)
En su lugar, haga esto:
git reset --keep HEAD~3
git checkout -t -b newbranch
git cherry-pick ..HEAD@{2}
- Primero, descarta los 3 compromisos más recientes (
--keep
es como--hard
, pero más seguro, ya que falla en lugar de tirar los cambios no confirmados). - Luego se bifurca de
newbranch
. - Luego selecciona esos 3 compromisos de nuevo en
newbranch
. Dado que ya no están referenciados por una rama, lo hace al usar el reflog de git:HEAD@{2}
es el compromiso queHEAD
usaba para referirse a 2 operaciones anteriores, es decir, antes de que 1. comprobáramosnewbranch
y 2. usamosgit reset
para descartar las 3 confirmaciones.
Advertencia: la reinicialización está habilitada de forma predeterminada, pero si la ha desactivado manualmente (por ejemplo, utilizando un repositorio de git "simple"), no podrá recuperar los 3 confirmaciones después de ejecutar git reset --keep HEAD~3
.
Una alternativa que no se basa en el 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 retirada - en lugar de oldbranch
).
Explicacion tecnica
¿Por qué git rebase
descartaría las 3 confirmaciones después del primer ejemplo? Esto se debe a que git rebase
sin argumentos habilita la opción --fork-point
de forma predeterminada, que utiliza el reflog local para intentar ser robusto contra la derivación ascendente forzada.
Supongamos que bifurcaste el origen / maestro cuando contenía las confirmaciones M1, M2, M3 y luego realizaste tres asignaciones:
M1--M2--M3 <-- origin/master
/
T1--T2--T3 <-- topic
pero luego alguien vuelve a escribir la historia mediante el origen / maestro de empuje forzado 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 / maestro, y por lo tanto, las confirmaciones 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 querrá en su rama temática una vez que la rama temática se vuelva a basar:
M1--M3'' <-- origin/master
/
T1''--T2''--T3'' <-- topic (rebased)
Este comportamiento tiene sentido, y generalmente es lo correcto cuando se rebasa.
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 equivocado. Git considera que newbranch
ha desviado de la rama ascendente en una revisión que incluye las 3 confirmaciones, luego el reset --hard
reescribe el historial de la reset --hard
ascendente para eliminar las confirmaciones, por lo que la próxima vez que ejecute git rebase
, las descartará como cualquier otra confirmación que ha sido eliminado de la corriente arriba.
Pero en este caso particular, queremos que esos 3 compromisos se consideren como parte de la rama del tema. Para lograrlo, debemos desmontar 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 reflog en el estado correcto.
Para obtener más detalles, consulte la definición de --fork-point
en git rebase y git merge-base docs.
Mudarse a una nueva sucursal
ADVERTENCIA: este método funciona porque está creando una nueva rama con el primer comando: git branch newbranch
. Si desea mover confirmaciones a una rama existente , debe combinar sus cambios en la rama existente antes de ejecutar git reset --hard HEAD~3
(consulte Cómo moverse a una rama existente más abajo). Si no fusionas tus cambios primero, se perderán.
A menos que haya otras circunstancias involucradas, esto puede hacerse fácilmente bifurcándose y retrocediendo.
# Note: Any changes not committed will be lost.
git branch newbranch # Create a new branch, saving the desired commits
git reset --hard HEAD~3 # Move master back by 3 commits (GONE from master)
git checkout newbranch # Go to the new branch that still has the desired commits
Pero asegúrese de cuántos se compromete a volver. Alternativamente, puede en lugar de HEAD~3
, simplemente proporcionar el hash de la confirmación (o la referencia como origen / maestro ) que desea "volver a" en la rama maestra (/ actual), por ejemplo:
git reset --hard a1b2c3d4
* 1 Solo estarás "perdiendo" las confirmaciones de la rama maestra, pero no te preocupes, ¡tendrás esas confirmaciones en la nueva rama!
ADVERTENCIA: con la versión 2.0 de Git y posterior, si luego git rebase
la nueva rama en la rama original ( master
), es posible que necesite una --no-fork-point
explícita --no-fork-point
durante la rebase para evitar perder las confirmaciones acumuladas. Tener branch.autosetuprebase always
establecido hace que esto sea más probable. Vea la respuesta de John Mellor para más detalles.
Mudarse a una sucursal existente
Si desea mover sus compromisos a una rama existente , se verá así:
git checkout existingbranch
git merge master
git checkout master
git reset --hard HEAD~3 # Go back 3 commits. You *will* lose uncommitted work.
git checkout existingbranch
1) Cree una nueva rama, que mueva todos sus cambios a new_branch.
git checkout -b new_branch
2) Luego regresa a la rama vieja.
git checkout master
3) Hacer git rebase
git rebase -i <short-hash-of-B-commit>
4) Luego el editor abierto contiene los últimos 3 datos de confirmación.
...
pick <C''s hash> C
pick <D''s hash> D
pick <E''s hash> E
...
5) Cambiar pick
para drop
en todas esas 3 confirmaciones. Luego guarda y cierra el editor.
...
drop <C''s hash> C
drop <D''s hash> D
drop <E''s hash> E
...
6) Ahora se eliminan las últimas 3 confirmaciones de la rama actual ( master
). Ahora empuje la rama con fuerza, con el signo +
antes del nombre de la rama.
git push origin +master
Esto no los "mueve" en el sentido técnico, pero tiene el mismo efecto:
A--B--C (branch-foo)
/ ^-- I wanted them here!
/
D--E--F--G (branch-bar)
^--^--^-- Opps wrong branch!
While on branch-bar:
$ git reset --hard D # remember the SHAs for E, F, G (or E and G for a range)
A--B--C (branch-foo)
/
/
D-(E--F--G) detached
^-- (branch-bar)
Switch to branch-foo
$ git cherry-pick E..G
A--B--C--E''--F''--G'' (branch-foo)
/ E--F--G detached (This can be ignored)
/ /
D--H--I (branch-bar)
Now you won''t need to worry about the detached branch because it is basically
like they are in the trash can waiting for the day it gets garbage collected.
Eventually some time in the far future it will look like:
A--B--C--E''--F''--G''--L--M--N--... (branch-foo)
/
/
D--H--I--J--K--.... (branch-bar)
Otra forma 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 que aprendiera 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.
Para aquellos que se preguntan por qué funciona (como lo era al principio):
Desea volver a C y mover D y E a la nueva rama. 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
Dado que una rama es solo un puntero, el maestro apuntó a la última confirmación. Cuando hizo newBranch , simplemente hizo un nuevo puntero a la última confirmación. Luego, utilizando 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.
Para hacer esto sin volver a escribir el historial (es decir, si ya has presionado los confirmaciones):
git checkout master
git revert <commitID(s)>
git checkout -b new-branch
git cherry-pick <commitID(s)>
Ambas ramas pueden ser empujadas sin fuerza!
Tenía justo esta situación:
Branch one: A B C D E F J L M
/ (Merge)
Branch two: G I K N
Yo actué:
git branch newbranch
git reset --hard HEAD~8
git checkout newbranch
Esperaba que el compromiso fuera la CABEZA, pero el compromiso L es ahora ...
Para estar seguro de llegar al lugar correcto en la historia, es más fácil trabajar con el hash del commit
git branch newbranch
git reset --hard #########
git checkout newbranch
Usted puede hacer esto es solo 3 pasos simples que he usado.
1) Crea una nueva rama en la que quieras confirmar tu actualización reciente.
git branch <branch name>
2) Buscar ID de compromiso reciente para confirmar en una nueva rama.
git log
3) Copie la nota de identificación de confirmación que la lista de confirmación más reciente tiene lugar en la parte superior. para que puedas encontrar tu compromiso. También encuentras esto vía mensaje.
git cherry-pick d34bcef232f6c...
También puede proporcionar un rango de ID de confirmación.
git cherry-pick d34bcef...86d2aec
Ahora tu trabajo hecho. Si eligió la identificación correcta y la rama correcta, entonces tendrá éxito. Así que antes de hacer esto ten cuidado. de lo contrario puede ocurrir otro problema.
Ahora puedes empujar tu código
git push