¿Cómo puedo combinar dos commits en uno si ya comencé a rebase?

1159

Estoy tratando de fusionar 2 commits en 1, así que seguí "aplastando commits con rebase" desde git ready .

corrí

git rebase --interactive HEAD~2

En el editor resultante, cambio picka squashy luego salvo-salgo, pero el rebase falla con el error

No se puede 'aplastar' sin una confirmación previa

Ahora que mi árbol de trabajo ha alcanzado este estado, tengo problemas para recuperarme.

El comando git rebase --interactive HEAD~2falla con:

Rebase interactivo ya comenzó

y git rebase --continuefalla con

No se puede 'aplastar' sin una confirmación previa

Miguel
fuente
22
Golpeé esto también. Mi error fue causado por el hecho de que git rebase -i enumera los commits en el orden opuesto de git log; ¡el último commit está en la parte inferior!
lmsurprenant

Respuestas:

1734

Resumen

El mensaje de error

No se puede 'aplastar' sin una confirmación previa

significa que probablemente intentaste "aplastar hacia abajo". Git siempre aplasta una confirmación más nueva en una confirmación más antigua o "hacia arriba" como se ve en la lista interactiva de tareas de rebase, es decir, en una confirmación en una línea anterior. Cambiar el comando en la primera línea de tu lista de tareas squashsiempre producirá este error, ya que no hay nada en lo que pueda comprometerse el primer commit.

La solución

Primero regrese a donde comenzó

$ git rebase --abort

Digamos que tu historia es

$ git log --pretty=oneline
a931ac7c808e2471b22b5bd20f0cad046b1c5d0d c
b76d157d507e819d7511132bdb5a80dd421d854f b
df239176e1a2ffac927d8b496ea00d5488481db5 a

Es decir, a fue el primer commit, luego b, y finalmente c. Después de cometer c, decidimos aplastar b y c juntos:

(Nota: Ejecutar git logcanaliza su salida en un buscapersonas, lessde manera predeterminada en la mayoría de las plataformas. Para salir del buscapersonas y volver al símbolo del sistema, presione la qtecla).

Correr git rebase --interactive HEAD~2te da un editor con

pick b76d157 b
pick a931ac7 c

# Rebase df23917..a931ac7 onto df23917
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

(Tenga en cuenta que esta lista de tareas está en el orden inverso en comparación con la salida de git log).

Cambiar las b picka squashresultará en el error que viste, pero si en cambio aplastas c en b (el más nuevo se compromete en el anterior o "aplasta hacia arriba") al cambiar la lista de tareas a

pick   b76d157 b
squash a931ac7 c

y guardar-salir de su editor, obtendrá otro editor cuyos contenidos son

# This is a combination of 2 commits.
# The first commit's message is:

b

# This is the 2nd commit message:

c

Cuando guarda y sale, el contenido del archivo editado se convierte en mensaje de confirmación de la nueva confirmación combinada:

$ git log --pretty=oneline
18fd73d3ce748f2a58d1b566c03dd9dafe0b6b4f b and c
df239176e1a2ffac927d8b496ea00d5488481db5 a

Nota sobre la reescritura del historial

El rebase interactivo reescribe la historia. Intentar pasar a un control remoto que contiene el historial anterior fallará porque no es un avance rápido.

Si la rama que usted reformuló es una rama de tema o característica en la que está trabajando solo , no es gran cosa. Empujar a otro repositorio requerirá la --forceopción, o alternativamente, según los permisos del repositorio remoto, es posible que pueda eliminar primero la rama anterior y luego empujar la versión modificada. Los ejemplos de esos comandos que potencialmente destruirán el trabajo están fuera del alcance de esta respuesta.

Reescribir el historial ya publicado en una rama en la que está trabajando con otras personas sin una muy buena razón, como filtrar una contraseña u otros detalles confidenciales, obliga a sus colaboradores a trabajar y es antisocial y molestará a otros desarrolladores. La sección "Recuperación de una base de datos ascendente" en la git rebasedocumentación explica, con énfasis adicional.

Rebasar (o cualquier otra forma de reescribir) una rama en la que otros han basado el trabajo es una mala idea: cualquiera que esté aguas abajo se ve obligado a corregir manualmente su historial. En esta sección se explica cómo hacer la corrección desde el punto de vista aguas abajo. La solución real, sin embargo, sería evitar rebasear el flujo ascendente en primer lugar. ...

Greg Bacon
fuente
Si uso rebase para aplastar un commit, se crea un nuevo commit "combinado" que contiene los dos conjuntos de cambios, pero el hash es diferente. ¿los git originales también se conservan por git?
fabsenet
@fabsenet Sí y no. Todavía se puede acceder a los commits originales, pero es probable que ya no se pueda acceder a ellos desde ninguna referencia (según los detalles de su historial). Las confirmaciones no referenciadas finalmente se eliminan mediante el proceso de recolección de basura.
Greg Bacon
solo estaba jugando ... lo hice git log hashoftheoldcommity funcionó, pero tenía curiosidad por ver una git log --graphcon todas estas confirmaciones inalcanzables incluidas
fabsenet
este squash es una buena herramienta para organizar los commits antes del push, pero si presiono uno de los commits, ¿no puedo aplastarlo? git dice: HEAD desarmado y actualizado correctamente.
Sérgio
No estoy obteniendo el editor en la segunda instancia, el git bash, parece congelado en algún proceso. ¿Qué hacer?
411

Si hay varias confirmaciones, puede usar git rebase -ipara aplastar dos confirmaciones en una.

Si solo hay dos confirmaciones que desea fusionar, y son las "dos más recientes", los siguientes comandos se pueden usar para combinar las dos confirmaciones en una:

git reset --soft "HEAD^"
git commit --amend
usuario3828059
fuente
66
¿Cuál es el inconveniente en comparación con rebase? Encuentro uno mucho más simple de usar.
Guillaume86
17
No puede unirse en un orden arbitrario, solo las dos últimas confirmaciones .
dr0i
50
@ dr0i Puede combinar tantos commits como desee, siempre que sean los últimos X commits y no en el medio. Simplemente ejecute git reset --soft HEAD~10, donde 10 es el número de confirmaciones que desea fusionar.
fregante
2
Esto se puede usar si no tiene un origen remoto establecido y solo tiene dos confirmaciones.
atedja 01 de
8
También puede restablecer a un determinado comprometerse si no quiere contar cuántas hay de la HEADusando git reset --soft 47b5c5..., donde 47b5c5...es el ID SHA1 de la confirmación.
dguay
112

Rebase: No lo vas a necesitar:

Una forma más sencilla para el escenario más frecuente.

En la mayoría de los casos:

En realidad, si todo lo que quiere es simplemente combinar varias confirmaciones recientes en una, pero no necesita drop, rewordy otro trabajo de rebase

simplemente puedes hacer:

git reset --soft "HEAD~n"
  • Suponiendo que ~nes el número de confirmaciones a suavemente un-commit (es decir ~1, ~2, ...)

Luego, use el siguiente comando para modificar el mensaje de confirmación.

git commit --amend

que es casi lo mismo que un largo rango de squashy uno pick.

Y funciona para n commits pero no solo para dos commits como se indica en la respuesta anterior.

pambda
fuente
3
Esto es bueno si desea hacer una limpieza adicional además de aplastar los commits, como eliminar 1 commit en el medio o cambiar una línea de código.
styfle
1
Suponiendo que ~nes el número de confirmaciones a suavemente un-commit (es decir ~1, ~2, ...)
Ray
1
¿Qué sucede si deseo fusionar no las núltimas confirmaciones, sino las nconfirmaciones en el medio? ¿Puedo hacer eso fácilmente?
chumakoff
1
Entonces git rebase -ies lo que necesitas para squashtrabajar. @chumakoff
pambda
3
Entonces, para unir nlos commits más recientes en un primer uso git reset --soft @~m, dondem = n - 1
Łukasz Rajchel
55

Primero debe verificar cuántas confirmaciones tiene:

git log

Hay dos estados:

Una es que solo hay dos confirmaciones:

Por ejemplo:

commit A
commit B

(En este caso, no puede usar git rebase para hacer) debe hacer lo siguiente.

$ git reset --soft HEAD^1

$ git commit --amend

Otra es que hay más de dos commits; desea fusionar commit C y D.

Por ejemplo:

commit A
commit B
commit C
commit D

(bajo esta condición, puede usar git rebase)

git rebase -i B

Y que usar "squash" para hacer. El resto es muy fácil. Si aún no lo sabe, lea http://zerodie.github.io/blog/2012/01/19/git-rebase-i/

Haimei
fuente
El reinicio --soft y commit --amend es la única forma que funciona si ya tiene una nueva versión en progreso (y eligió 'editar' en lugar de 'squash' para esta confirmación). +1
Jacek Lach
1
Fusionar el primer y solo dos commits en un repositorio, exactamente mi caso límite :-)
Chris Huang-Leaver
1
Agregue que git push -f origin masterpodría ser necesario.
Rishabh Agrahari
33

Suponiendo que estuvieras en tu propia rama temática. Si desea fusionar los últimos 2 commits en uno y parecerse a un héroe, ramifique el commit justo antes de realizar los últimos dos commits.

git checkout -b temp_branch HEAD^2

Luego, squash confirma la otra rama en esta nueva rama:

git merge branch_with_two_commits --squash

Eso traerá los cambios pero no los comprometerá. Así que solo confía en ellos y listo.

git commit -m "my message"

Ahora puede fusionar esta nueva rama de tema nuevamente en su rama principal.

Homan
fuente
55
En realidad, esta fue la respuesta más útil para mí, ya que no requirió un cambio de nombre manual, sino que simplemente aplasta todos los commits de una rama completa en un commit. Muy agradable.
Robert
¡Gracias por esto! ¡Así es como hacer que git haga lo que me imagino en mi cabeza!
Marjan Venema
respuesta asombrosa, mucho más simple que las alternativas
Al parecer, esta respuesta no es apropiada para el caso cuando ay crequiere que se fusionen entre sí y mantener btal cual es.
Talha Ashraf
2
¿Ha cambiado algo en las versiones recientes de git? Cuando intento el primer comando ( git checkout -b combine-last-two-commits "HEAD^2") en git versión 2.17, fatal: 'HEAD^2' is not a commit and a branch 'combine-last-two-commits' cannot be created from it
aparece
23

puedes cancelar el rebase con

git rebase --abort

y cuando ejecutas el comando de rebase interactivo nuevamente, 'squash; commit debe estar debajo del pick commit en la lista

Leom Burke
fuente
16

A menudo uso git reset --mixed para revertir una versión base antes de múltiples commits que desea fusionar, luego hago un nuevo commit, de esa manera podría permitir que su commit sea más nuevo, asegúrese de que su versión sea HEAD después de presionar al servidor.

commit ac72a4308ba70cc42aace47509a5e
Author: <[email protected]>
Date:   Tue Jun 11 10:23:07 2013 +0500

    Added algorithms for Cosine-similarity

commit 77df2a40e53136c7a2d58fd847372
Author: <[email protected]>
Date:   Tue Jun 11 13:02:14 2013 -0700

    Set stage for similar objects

commit 249cf9392da197573a17c8426c282
Author: Ralph <[email protected]>
Date:   Thu Jun 13 16:44:12 2013 -0700

    Fixed a bug in space world automation

Si quiero fusionar head dos commits en uno, primero uso:

git reset --mixed 249cf9392da197573a17c8426c282

"249cf9392da197573a17c8426c282" era la tercera versión, también es su versión base antes de fusionarse, después de eso, hago una nueva confirmación:

git add .
git commit -m 'some commit message'

Es todo, la esperanza es otra forma para todos.

FYI, de git reset --help:

 --mixed
     Resets the index but not the working tree (i.e., the changed files are
     preserved but not marked for commit) and reports what has not been
     updated. This is the default action.
VinceStyling
fuente
No he leído los documentos de '--mixed' pero estoy seguro de que otras personas leen la publicación y se preguntan lo mismo: ¿Cuál es la ventaja de usar --mixed? Podría mejorar su publicación para incluir un fragmento de la página de manual.
funroll
@funroll No lo sabía: mezclado mucho antes de escribir esta respuesta, según mi propia experiencia, la operación mixta convertirá la versión especificada que paso como argumento como la versión HEAD del repositorio, y nada se puede perder después de esa versión, para que podamos manejar esos cambios.
VinceStyling
14

$ git rebase --abort

Ejecute este código en cualquier momento si desea deshacer el git rebase

$ git rebase -i HEAD~2

Para volver a aplicar las dos últimas confirmaciones. El comando anterior abrirá un editor de código

  • [ La última confirmación estará en la parte inferior ]. Cambia el último commit a squash (s). Dado que la calabaza se fusionará con la confirmación previa.
  • Luego presione la tecla esc y escriba: wq para guardar y cerrar

Después: wq estará en modo de rebase activo

Nota : Obtendrá otro editor si no hay mensajes de advertencia / error. Si hay un error o una advertencia que no se mostrará otro editor, puede cancelar ejecutando $ git rebase --abortsi ve un error o advertencia, de lo contrario, simplemente continúe ejecutando$ git rebase --continue

Verá su mensaje de confirmación 2. Elija uno o escriba su propio mensaje de confirmación, guarde y salga [: wq]

Nota 2: es posible que deba forzar los cambios al repositorio remoto si ejecuta el comando rebase

$ git push -f

$ git push -f origin master

Gnanasekar S
fuente
1
Nota 2: git push -f origin/masteres lo que faltan otras respuestas. +1
Rishabh Agrahari
2

Desde que uso git cherry-pick para casi todo, para mí es natural hacerlo incluso aquí.

Dado que me he branchXretirado y hay dos confirmaciones en la punta, de las cuales quiero crear una confirmación combinando su contenido, hago esto:

git checkout HEAD^ // Checkout the privious commit
git cherry-pick --no-commit branchX // Cherry pick the content of the second commit
git commit --amend // Create a new commit with their combined content

Si quiero actualizar branchXtambién (y supongo que este es el lado negativo de este método) también tengo que:

git checkout branchX
git reset --hard <the_new_commit>
Martin G
fuente
1

Si su rama maestra se git logparece a la siguiente:

commit ac72a4308ba70cc42aace47509a5e
Author: <[email protected]>
Date:   Tue Jun 11 10:23:07 2013 +0500

    Added algorithms for Cosine-similarity

commit 77df2a40e53136c7a2d58fd847372
Author: <[email protected]>
Date:   Tue Jun 11 13:02:14 2013 -0700

    Set stage for similar objects

commit 249cf9392da197573a17c8426c282
Author: Ralph <[email protected]>
Date:   Thu Jun 13 16:44:12 2013 -0700

    Fixed a bug in space world automation

y desea fusionar las dos confirmaciones principales, simplemente siga estos sencillos pasos:

  1. Primero para estar en el lado seguro de pago, el segundo último compromiso en una rama separada. Puedes nombrar a la rama como quieras.git checkout 77df2a40e53136c7a2d58fd847372 -b merged-commits
  2. Ahora, simplemente selecciona tus cambios desde la última confirmación en esta nueva rama como: git cherry-pick -n -x ac72a4308ba70cc42aace47509a5e . (Resolver conflictos si surge alguno)
  3. Entonces, sus cambios en la última confirmación están allí en su segunda última confirmación. Pero aún tiene que comprometerse, así que primero agregue los cambios que acaba de seleccionar y luego ejecute git commit --amend.

Eso es. Puede insertar esta versión fusionada en la rama "merged-commits" si lo desea.

Además, ahora puede descartar las dos confirmaciones consecutivas en su rama maestra. Simplemente actualice su rama maestra como:

git checkout master
git reset --hard origin/master (CAUTION: This command will remove any local changes to your master branch)
git pull
Usman
fuente
0

Si desea combinar las dos confirmaciones más recientes y simplemente usar el mensaje de la confirmación anterior, puede automatizar el proceso utilizando expect.

Asumo:

  • Estás usando vi como tu editor
  • Sus compromisos son de una línea cada uno

Lo probé con git version 2.14.3 (Apple Git-98).


#!/usr/bin/env expect
spawn git rebase -i HEAD~2

# change the second "pick" to "squash"
# down, delete word, insert 's' (for squash), Escape, save and quit
send "jdwis \033:wq\r"

expect "# This is a"

# skip past first commit message (assumed to be one line), delete rest of file
# down 4, delete remaining lines, save and quit
send "4jdG\r:wq\r"

interact
erwaman
fuente
No está claro qué hace tu guión.
buhtz
@buhtz Agregué algunos comentarios más. Avíseme si aún lo encuentra confuso, y si es así, qué parte.
erwaman
Todavía no está claro qué hace su escrúpulo. También expectestá sin describir.
buhtz
@buhtz, ¿qué parte no está clara? Proporcioné un enlace a una página con más documentación para expect.
erwaman
Falta una descripción general del script. No está claro lo que hace. Ninguna parte no está clara. La intención del guión en sí no está clara.
buhtz