Pregunta Mueva los commit (s) más recientes a una nueva rama con Git


Me gustaría mover los últimos commits que me he comprometido a dominar a una nueva rama y recuperar el master antes de que se hicieran los commits. Desafortunadamente, mi Git-fu aún no es lo suficientemente fuerte, ¿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 

3753
2017-10-27 03:07


origen


Respuestas:


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 un sucursal existente necesita fusionar sus cambios en la rama existente antes de ejecutar git reset --hard HEAD~3 (ver Mudarse a una sucursal existente abajo). Si no fusiona sus cambios primero, se perderán.

A menos que haya otras circunstancias involucradas, esto se puede hacer fácilmente mediante la bifurcación y el retroceso.

# 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úrate de cuántos commits volver. Alternativamente, puede en lugar de HEAD~3, simplemente proporcione el hash de la confirmación (o la referencia como origen / maestro) desea "volver a" en el dominar rama (/ actual), por ejemplo:

git reset --hard a1b2c3d4

* 1 Lo harás solamente "perder" commits de la rama master, pero no te preocupes, ¡tendrás esos commits en newbranch!

ADVERTENCIA: Con Git versión 2.0 y posterior, si usted más tarde git rebase la nueva rama sobre el original (master) rama, es posible que necesite un explícito --no-fork-point opción durante la rebase para evitar perder los commits transferidos. Teniendo branch.autosetuprebase always establecer hace que esto sea más probable. Ver La respuesta de John Mellor para detalles.

Mudarse a una sucursal existente

Si desea mover sus confirmaciones a un sucursal 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

4778
2017-07-22 22:37



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 bifurcación. Esto es lo que parece 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

Como una rama es solo un puntero, dominar señaló el último compromiso. Cuando hiciste nueva sucursal, simplemente hizo un nuevo puntero al último compromiso. Luego usando git reset moviste el dominar puntero hacia atrás dos confirmaciones. Pero como no te moviste nueva sucursal, todavía apunta a la confirmación que hizo originalmente.


846
2018-02-07 16:58



En general...

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

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

Paso 1 - Nota qué commits de master quieres en un newbranch

Ejecutar

git checkout master
git log

Tenga en cuenta los valores hash de (por ejemplo 3) confirmaciones que desea newbranch. Aquí usaré:
C commit: 9aa1233
D commit: 453ac3d
E commit: 612ecb3 

Nota: Puedes usar los primeros siete caracteres o   todo el commit hash

Paso 2: colócalos en el newbranch

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 commits a newbranch.


342
2018-03-26 08:13



Otra forma de hacerlo es usar solo 2 comandos. También mantiene intacto tu á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 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 

Siendo capaz de push a . es un buen truco para saber.


251
2018-04-06 22:38



¡La mayoría de las respuestas anteriores son peligrosamente erróneas!

No hagas esto:

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

Como la próxima vez que corras git rebase (o git pull --rebase) esos 3 commits serían descartados silenciosamente newbranch! (ver explicación abajo)

En lugar de hacer esto:

git reset --keep HEAD~3
git checkout -t -b newbranch
git cherry-pick ..HEAD@{2}
  • Primero descarta los 3 commits 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 newbranch.
  • Luego selecciona esos 3 commits nuevamente newbranch. Como ya no están referenciados por una sucursal, lo hace mediante el uso de git reflog: HEAD@{2} es el compromiso que HEAD usado para referirse a 2 operaciones atrás, es decir, antes de que 1. analizáramos newbranch y 2. usado git reset para descartar los 3 commits.

Advertencia: el reflog está habilitado por defecto, pero si lo has deshabilitado manualmente (por ejemplo, usando un repositorio "simple" de git), no podrás 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 previamente desprotegida - en lugar de oldbranch)


Explicación técnica

Por qué git rebase descartar los 3 commits después del primer ejemplo? Eso es porque git rebase sin argumentos habilita el --fork-point opción por defecto, que usa el reflog local para tratar de ser robusto contra la rama ascendente empujada por la fuerza.

Supongamos que se ha bifurcado de origen / principal cuando contiene commits M1, M2, M3, y luego ha realizado tres commits usted mismo:

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

pero luego alguien reescribe la historia empujando con fuerza origen / master para eliminar M2:

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

Usando su reflog local, git rebase puede ver que ha bifurcado desde una anterior encarnación de la rama de origen / principal, y por lo tanto que las confirmaciones M2 y M3 no son realmente parte de su rama de tema. Por lo tanto, supone razonablemente que, dado que M2 se eliminó de la rama ascendente, ya no lo desea en su rama de tema una vez que la rama de tema se ha vuelto a establecer como base:

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

Este comportamiento tiene sentido, y generalmente es lo correcto al volver a basar.

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 como haber bifurcado la rama ascendente en una revisión que incluye los 3 commits, entonces el reset --hard reescribe el historial de la corriente ascendente para eliminar las confirmaciones, por lo que la próxima vez que ejecute git rebase los descarta como cualquier otra confirmación que haya sido eliminada del upstream.

Pero en este caso particular queremos que esos 3 compromisos se consideren como parte de la rama temática. Para lograr eso, tenemos que desembolsar el upstream en la revisión anterior que no incluye los 3 commits. Eso es lo que hacen las soluciones sugeridas, por lo tanto, ambas dejan el reflog en el estado correcto.

Para más detalles, vea la definición de --fork-point en el git rebase y git merge-basedocs.


209
2017-10-19 14:12



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)

27
2018-01-21 16:10



Para hacerlo sin reescribir el historial (es decir, si ya ha enviado las confirmaciones):

git checkout master
git revert <commitID(s)>
git checkout -b new-branch
git cherry-pick <commitID(s)>

¡Ambas ramas pueden empujarse sin fuerza!


19
2017-09-20 10:17



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 me comprometiera a ser la CABEZA, pero me comprometo con L, ¿es ahora?

Para estar seguro de aterrizar en el lugar correcto en la historia es más fácil trabajar con el hash de la confirmación

git branch newbranch 
git reset --hard #########
git checkout newbranch

12
2018-05-01 07:50



Solución mucho más simple

Si:

  1. Tu objetivo principal es retroceder mastery
  2. Desea mantener los cambios pero no le importa particularmente compromisos individuales, y
  3. Aún no has empujado,

Entonces, lo siguiente es mucho más simple:

git reset HEAD~3
git stash
git checkout otherbranch
git stash pop

Que hace esto?

  1. Deshace las últimas tres confirmaciones (y sus mensajes), pero deja intactos todos los archivos de trabajo
  2. Elimina todos los cambios de archivos de trabajo, haciendo que master árbol de trabajo igual al estado HEAD ~ 3.
  3. Cambiar a una sucursal existente otherbranch
  4. Extrae los archivos modificados de su escondite y los ingresa en su directorio de trabajo.

Ahora puedes usar git add y git commit como lo harías normalmente Todos los cambios serán cometidos en otherbranch.

Esto no conserva los commits equivocados y los mensajes de commit. Deberá agregar un nuevo mensaje de confirmación a esta nueva confirmación, recreando los mensajes de confirmación anteriores. Sin embargo, el OP indicó que el objetivo era "recuperar el maestro antes de que se hicieran los commits" sin perder los cambios, y esta solución lo hace.

Hago esto al menos una vez a la semana cuando accidentalmente hago nuevos commits para master en lugar de develop. Por lo general, solo tengo un compromiso para deshacer, en cuyo caso git reset HEAD^ funciona para deshacer solo una confirmación.


6
2018-05-25 14:14