¿Cómo inyectar un commit entre unos dos commits arbitrarios en el pasado?

126

Supongamos que tengo el siguiente historial de confirmación en mi sucursal solo local:

A -- B -- C

¿Cómo inserto una nueva confirmación entre Ay B?

BartoszKP
fuente
3
cometer llamado Ą ? :)
Antek
Tengo una pregunta muy similar sobre cómo insertar una nueva versión en el pasado en lugar de una confirmación.
Pavel P

Respuestas:

178

Es incluso más fácil que en la respuesta de OP.

  1. git rebase -i <any earlier commit>. Esto muestra una lista de confirmaciones en su editor de texto configurado.
  2. Encuentre la confirmación que desea insertar después (supongamos que es a1b2c3d). En su editor, para esa línea, cambie picka edit.
  3. Comience el rebase cerrando su editor de texto (guarde sus cambios). Esto lo deja en el símbolo del sistema con la confirmación que eligió anteriormente ( a1b2c3d) como si acabara de confirmarse .
  4. Realiza tus cambios y git commit( NO enmiendas, a diferencia de la mayoría de los edits). Esto crea una nueva confirmación después de la que eligió.
  5. git rebase --continue. Esto reproduce las confirmaciones sucesivas, dejando su nueva confirmación insertada en el lugar correcto.

Tenga en cuenta que esto reescribirá la historia y destruirá a cualquier otra persona que intente tirar.

SLaks
fuente
1
Esto agregó el nuevo commit después del commit, que es después del que estaba haciendo un rebase (también el último commit), en lugar de justo después del que estaba haciendo el rebase. El resultado fue el mismo que si hubiera realizado una nueva confirmación al final con los cambios que quería insertar. Mi historia se convirtió en A -- B -- C -- Dlugar de lo deseado A -- D -- B -- C.
Xedin Desconocido
2
@XedinUnknown: Entonces no usaste Rebase correctamente.
SLaks
3
Ahora Dpodría ser un compromiso en cualquier lugar. Supongamos que tenemos A - B - Cy tenemos alguna confirmación Dque ni siquiera está en esta rama. Sin embargo, sabemos que es SHA. Podemos hacerlo git rebase -i HEAD~3. Ahora entre las líneas Ay B pick, insertamos una nueva pick línea que dice pick SHA, dando el hash de lo deseado D. No tiene por qué ser el hash completo, solo el acortado. git rebase -isolo cherry selecciona las confirmaciones que se enumeran por picklíneas en el búfer; no tienen que ser los originales que enumeró para usted.
Kaz
1
@Kaz Esto parece una respuesta válida diferente.
BartoszKP
3
Aún más fácil, puede usar la breakpalabra clave en el editor en su propia línea entre dos confirmaciones (o en la primera línea, para insertar una confirmación antes de la confirmación especificada).
SimonT
30

Resulta ser bastante simple, la respuesta se encuentra aquí . Supongamos que estás en una rama branch. Realice estos pasos:

  • cree una rama temporal desde el commit después de que desee insertar el nuevo commit (en este caso, commit A):

    git checkout -b temp A
    
  • realiza los cambios y los confirma, creando un commit, llamémoslo N:

    git commit -a -m "Message"
    

    (o git addseguido de git commit)

  • vuelva a redactar las confirmaciones que desea tener después de la nueva confirmación (en este caso, confirmaciones By C) en la nueva confirmación:

    git rebase temp branch
    

(posiblemente deba usar -ppara preservar las fusiones, si hubiera alguna, gracias a un comentario ya no existente de ciekawy )

  • eliminar la rama temporal:

    git branch -d temp
    

Después de esto, la historia se ve de la siguiente manera:

A -- N -- B -- C

Por supuesto, es posible que aparezcan algunos conflictos durante el rebase.

En caso de que su sucursal no sea solo local, esto introducirá el historial de reescritura, por lo que podría causar serios problemas.

BartoszKP
fuente
2
No pude seguir la respuesta aceptada por SLaks, pero esto funcionó para mí. Después de obtener el historial de confirmación que quería, tuve git push --forceque cambiar el repositorio remoto.
escapecharacter
1
Cuando se usa rebase usando la opción -Xtheirs, se resuelven los conflictos automáticamente, entonces git rebase temp branch -Xtheirs. ¡Respuesta útil para inyectar en un guión!
David C
Para novatos como yo, me gustaría agregar eso después git rebase temp branch, pero antes git branch -d temp, todo lo que tiene que hacer es arreglar y organizar la fusión de conflictos y problemas git rebase --continue, es decir, no es necesario cometer nada, etc.
Pugsley
19

Solución aún más fácil:

  1. Crea tu nueva confirmación al final, D. Ahora tienes:

    A -- B -- C -- D
    
  2. Entonces corre:

    $ git rebase -i hash-of-A
    
  3. Git abrirá su editor y se verá así:

    pick 8668d21 B
    pick 650f1fc C
    pick 74096b9 D
    
  4. Simplemente mueva D a la parte superior de esta manera, luego guarde y salga

    pick 74096b9 D
    pick 8668d21 B
    pick 650f1fc C
    
  5. Ahora tendrás:

    A -- D -- B -- C
    
Mateo
fuente
66
Buena idea, sin embargo, puede ser difícil introducir D en C, cuando pretende que estos cambios sean wrt. a A.
BartoszKP
Tengo una situación en la que tengo 3 commits que quiero reajustar juntos y un commit en el medio que no está relacionado. Es súper agradable poder mover esa confirmación antes o después por la línea de confirmaciones.
despliega el
13

Suponiendo que el historial de confirmación es preA -- A -- B -- C, si desea insertar una confirmación entre Ay B, los pasos son los siguientes:

  1. git rebase -i hash-of-preA

  2. Git abrirá tu editor. El contenido puede gustar esto:

    pick 8668d21 A
    pick 650f1fc B
    pick 74096b9 C
    

    Cambia el primero picka edit:

    edit 8668d21 A
    pick 650f1fc B
    pick 74096b9 C
    

    Guardar y Salir.

  3. Modifica tu código y luego git add . && git commit -m "I"

  4. git rebase --continue

Ahora tu historial de commit de Git es preA -- A -- I -- B -- C


Si encuentra un conflicto, Git se detendrá en esta confirmación. Puede utilizar git diffpara localizar marcadores de conflicto y resolverlos. Después de resolver todos los conflictos, debe usar git add <filename>para decirle a Git que el conflicto se ha resuelto y luego volver a ejecutarlo git rebase --continue.

Si desea deshacer el rebase, use git rebase --abort.

haolee
fuente
10

Aquí hay una estrategia que evita hacer un "hack de edición" durante el rebase visto en las otras respuestas que he leído.

Al usar git rebase -i, obtienes una lista de confirmaciones desde esa confirmación. Simplemente agregue un "salto" en la parte superior del archivo, esto hará que el rebase se rompa en ese punto.

break
pick <B's hash> <B's commit message>
pick <C's hash> <C's commit message>

Una vez lanzado, git rebaseahora se detendrá en el punto del "descanso". Ahora puede editar sus archivos y crear su confirmación normalmente. Luego puede continuar el rebase con git rebase --continue. Esto puede causar conflictos que tendrá que solucionar. Si te pierdes, no olvides que siempre puedes abortar el uso git rebase --abort.

Esta estrategia se puede generalizar para insertar un compromiso en cualquier lugar, simplemente coloque el "salto" en el lugar donde desea insertar un compromiso.

Después de reescribir el historial, no te olvides git push -f. Se aplican las advertencias habituales sobre otras personas que buscan su rama.

axerologementy
fuente
Lo siento, pero tengo problemas para entender cómo es esto "evitar rebase". Usted está ejecutando rebaseaquí. No hay mucha diferencia si creará el commit durante el rebase o de antemano.
BartoszKP
Woops, quise decir evitar el "hack de edición" durante el rebase, supongo que lo expresé mal.
axerologementy
Correcto. Mi respuesta tampoco usa la función "editar" de rebase. Aún así, este es otro enfoque válido, ¡gracias! :-)
BartoszKP
6

Muchas buenas respuestas aquí ya. Solo quería agregar una solución "sin rebase", en 4 sencillos pasos.


Resumen

git checkout A
git commit -am "Message for commit D"
git cherry-pick A..C
git branch -f master HEAD

Explicación

(Nota: una ventaja de esta solución es que no toca su rama hasta el paso final, cuando está 100% seguro de que está de acuerdo con el resultado final, por lo que tiene un paso muy útil de "confirmación previa" permitiendo pruebas AB ).


Estado inicial (asumí masterel nombre de su sucursal)

A -- B -- C <<< master <<< HEAD

1) Comience señalando HEAD en el lugar correcto

git checkout A

     B -- C <<< master
    /
   A  <<< detached HEAD

(Opcionalmente aquí, en lugar de separar HEAD, podríamos haber creado una rama temporal con la git checkout -b temp Aque tendríamos que eliminar al final del proceso. Ambas variantes funcionan, haga lo que prefiera ya que todo lo demás sigue igual)


2) Cree el nuevo commit D para insertar

# at this point, make the changes you wanted to insert between A and B, then

git commit -am "Message for commit D"

     B -- C <<< master
    /
   A -- D <<< detached HEAD (or <<< temp <<< HEAD)

3) Luego traiga copias de los últimos commits faltantes B y C (sería la misma línea si hubiera más commits)

git cherry-pick A..C

# (if any, resolve any potential conflicts between D and these last commits)

     B -- C <<< master
    /
   A -- D -- B' -- C' <<< detached HEAD (or <<< temp <<< HEAD)

(Prueba AB cómoda aquí si es necesario)

Ahora es el momento de inspeccionar su código, probar cualquier cosa que necesite ser probada, y también puede diferenciar / comparar / inspeccionar lo que tenía y lo que obtendría después de las operaciones.


4) Dependiendo de sus pruebas entre Cy C', está bien o es KO.

(CUALQUIERA) 4-OK) Finalmente, mueva la referencia demaster

git branch -f master HEAD

     B -- C <<< (B and C are candidates for garbage collection)
    /
   A -- D -- B' -- C' <<< master

(O) 4-KO) Solo déjelo mastersin cambios

Si creó una bifurcación temporal, simplemente elimínela con git branch -d <name>, pero si optó por la ruta HEAD separada, no es necesaria ninguna acción en este momento, las nuevas confirmaciones serán elegibles para la recolección de basura justo después de volver HEADa unir con ungit checkout master

En ambos casos (OK o KO), en este punto simplemente mastervuelva a pagar para volver a adjuntar HEAD.

RomainValeri
fuente