Aplasta mis últimas X confirmaciones usando Git

3590

¿Cómo puedo aplastar mis últimas confirmaciones X juntas en una confirmación usando Git?

Markdorison
fuente
12
Pregunta similar: stackoverflow.com/questions/7275508/…
koppor
2
@matt TortoiseGit es tu herramienta. Proporciona una única función "Combinar en una confirmación" que llamará a todos los pasos automáticamente en segundo plano. Lamentablemente solo está disponible para Windows. Vea mi respuesta a continuación.
Matthias M
1
Para aplastar hasta el primer commit, vea esto - stackoverflow.com/questions/1657017/…
goelakash
1
post squash uno necesita hacer fuerza push stackoverflow.com/questions/10298291/…
vikramvi

Respuestas:

2091

Use git rebase -i <after-this-commit>y reemplace "pick" en la segunda y posteriores confirmaciones con "squash" o "arreglo", como se describe en el manual .

En este ejemplo, <after-this-commit>es el hash SHA1 o la ubicación relativa del HEAD de la rama actual desde la cual se analizan los commits para el comando rebase. Por ejemplo, si el usuario desea ver 5 confirmaciones del HEAD actual en el pasado, el comando es git rebase -i HEAD~5.

Anomia
fuente
260
Esto, creo, responde a esta pregunta un poco mejor stackoverflow.com/a/5201642/295797
Roy Truelove
92
¿Qué se entiende por <after-this-commit>?
2540625
43
<after-this-commit>es commit X + 1, es decir, padre del commit más antiguo que desea aplastar.
joozek
340
Esta respuesta me pareció demasiado escueta para entenderla sin ambigüedades. Un ejemplo hubiera ayudado.
Ian Ollmann
54
La diferencia entre este rebase -ienfoque y reset --softes, rebase -ime permite retener al autor de confirmación, mientras reset --softque me permite volver a comprometerme. A veces necesito aplastar las confirmaciones de solicitudes de extracción pero manteniendo la información del autor. A veces necesito restablecer soft en mis propios commits. Votos a favor de ambas grandes respuestas de todos modos.
zionyx
3834

Puede hacerlo con bastante facilidad sin git rebaseo git merge --squash. En este ejemplo, aplastaremos los últimos 3 commits.

Si desea escribir el nuevo mensaje de confirmación desde cero, esto es suficiente:

git reset --soft HEAD~3 &&
git commit

Si desea comenzar a editar el nuevo mensaje de confirmación con una concatenación de los mensajes de confirmación existentes (es decir, similar a lo git rebase -ique comenzaría con una lista de instrucciones pick / squash / squash / ... / squash ), entonces debe extraer esos mensajes y pasar ellos a git commit:

git reset --soft HEAD~3 && 
git commit --edit -m"$(git log --format=%B --reverse HEAD..HEAD@{1})"

Ambos métodos aplastan los últimos tres commits en un solo nuevo commit de la misma manera. El restablecimiento parcial solo vuelve a apuntar HEAD a la última confirmación que no desea aplastar. Ni el índice ni el árbol de trabajo son tocados por el restablecimiento parcial, dejando el índice en el estado deseado para su nueva confirmación (es decir, ya tiene todos los cambios de las confirmaciones que está a punto de "descartar").

Chris Johnsen
fuente
163
¡Decir ah! Me gusta este metodo. Es el que se cierra al espíritu del problema. Es una pena que requiera tanto vudú. Algo como esto debería agregarse a uno de los comandos básicos. Posiblemente git rebase --squash-recent, o incluso git commit --amend-many.
Adrian Ratnapala
13
@ABB: Si su rama tiene un conjunto "ascendente", entonces puede usar branch@{upstream}(o solo @{upstream}para la rama actual; en ambos casos, la última parte se puede abreviar @{u}; vea gitrevisions ). Esto puede diferir de su "último envío empujado" (por ejemplo, si alguien más empujó algo que se construyó sobre su empuje más reciente y luego lo obtuvo), pero parece que podría estar cerca de lo que desea.
Chris Johnsen el
104
Esto me exigió un poco, push -fpero por lo demás fue encantador, gracias.
2rs2ts
39
@ 2rs2ts git push -f suena peligroso. Tenga cuidado de aplastar solo los commits locales. ¡Nunca toque commits empujados!
Matthias M
20
También necesito usar git push --forcedespués para que tome el compromiso
Zach Saucier
740

Puede usar git merge --squashpara esto, que es un poco más elegante que git rebase -i. Supongamos que estás en master y quieres aplastar los últimos 12 commits en uno.

ADVERTENCIA: Primero asegúrese de comprometer su trabajo, verifique que git statusesté limpio (ya git reset --hardque descartará los cambios por etapas y por etapas)

Entonces:

# Reset the current branch to the commit just before the last 12:
git reset --hard HEAD~12

# HEAD@{1} is where the branch was just before the previous command.
# This command sets the state of the index to be as it would just
# after a merge from that commit:
git merge --squash HEAD@{1}

# Commit those squashed changes.  The commit message will be helpfully
# prepopulated with the commit messages of all the squashed commits:
git commit

La documentación degit merge describe la --squashopción con más detalle.


Actualización: la única ventaja real de este método sobre el más simple git reset --soft HEAD~12 && git commitsugerido por Chris Johnsen en su respuesta es que obtiene el mensaje de confirmación rellenado previamente con cada mensaje de confirmación que está aplastando.

Mark Longair
fuente
16
Dices que esto es más 'elegante' que git rebase -i, pero no das una razón por la cual. Tentativamente estoy haciendo esto porque me parece que, de hecho, lo contrario es cierto y esto es un truco; ¿no estás ejecutando más comandos de los necesarios solo para forzar git mergea hacer una de las cosas para las que git rebaseestá específicamente diseñado?
Mark Amery
78
@ Mark Amery: Hay varias razones por las que dije que esto es más elegante. Por ejemplo, no implica generar innecesariamente un editor y luego buscar y reemplazar una cadena en el archivo "a hacer". Usar git merge --squashtambién es más fácil de usar en un script. Esencialmente, el razonamiento fue que no necesitas la "interactividad" git rebase -ipara nada.
Mark Longair
12
Otra ventaja es que git merge --squashes menos probable que produzca conflictos de fusión frente a movimientos / eliminaciones / cambios de nombre en comparación con el rebase, especialmente si se está fusionando desde una sucursal local. (descargo de responsabilidad: basado en una sola experiencia, ¡
corríjame
2
Siempre soy muy reacio cuando se trata de reinicios duros: usaría una etiqueta temporal en lugar de HEAD@{1}solo estar seguro, por ejemplo, cuando su flujo de trabajo se interrumpe durante una hora por un corte de energía, etc.
Tobias Kienzler
8
@BT: ¿Destruiste tu compromiso? :( No estoy seguro de lo que quieres decir con eso. Cualquier cosa que hayas cometido podrás volver fácilmente desde el registro de git. Si tuviste un trabajo no comprometido, pero los archivos fueron organizados, aún deberías poder obtener su contenido volverá, aunque eso será más trabajo . Sin embargo, si su trabajo ni siquiera fue organizado, me temo que hay poco que se pueda hacer; por eso la respuesta dice por adelantado: "Primero verifique que el estado de git esté limpio (desde git reset --hard tirará los cambios en escena y en escena) ".
Mark Longair
218

Recomiendo evitar git resetcuando sea posible, especialmente para los novatos de Git. A menos que realmente necesite automatizar un proceso basado en una serie de confirmaciones, hay una forma menos exótica ...

  1. Ponga los commits a ser aplastados en una rama de trabajo (si aún no lo están): use gitk para esto
  2. Echa un vistazo a la rama de destino (por ejemplo, 'maestro')
  3. git merge --squash (working branch name)
  4. git commit

El mensaje de confirmación se rellenará previamente en función de la calabaza.

sin bar
fuente
44
Este es el método más seguro: ¡no hay reinicio suave / duro (!!), o no se usa el registro!
TeChn4K
14
Sería genial si expandieras en (1).
Adam
2
@Adam: Básicamente, esto significa usar la interfaz GUI de gitkpara etiquetar la línea de código que está aplastando y también etiquetar la base sobre la cual se debe aplastar. En el caso normal, ambas etiquetas ya existirán, por lo que se puede omitir el paso (1).
nobar
3
Tenga en cuenta que este método no marca la rama de trabajo como totalmente fusionada, por lo que eliminarla requiere forzar la eliminación. :(
Kyrstellaine
2
Para (1), he encontrado git branch your-feature && git reset --hard HEAD~Nla forma más conveniente. Sin embargo, involucra git reset nuevamente, lo que esta respuesta intentó evitar.
EIS
134

Según la respuesta de Chris Johnsen ,

Agregue un alias global de "squash" desde bash: (o Git Bash en Windows)

git config --global alias.squash '!f(){ git reset --soft HEAD~${1} && git commit --edit -m"$(git log --format=%B --reverse HEAD..HEAD@{1})"; };f'

... o usando el símbolo del sistema de Windows:

git config --global alias.squash "!f(){ git reset --soft HEAD~${1} && git commit --edit -m\"$(git log --format=%B --reverse HEAD..HEAD@{1})\"; };f"


Su ~/.gitconfigahora debe contener este alias:

[alias]
    squash = "!f(){ git reset --soft HEAD~${1} && git commit --edit -m\"$(git log --format=%B --reverse HEAD..HEAD@{1})\"; };f"


Uso:

git squash N

... Que aplasta automáticamente las últimas Nconfirmaciones, inclusive.

Nota: El mensaje de confirmación resultante es una combinación de todas las confirmaciones aplastadas, en orden. Si no está satisfecho con eso, siempre puede git commit --amendmodificarlo manualmente. (O edite el alias para que coincida con sus gustos).

EthanB
fuente
66
Interesante, pero prefiero escribir el mensaje de confirmación aplastado yo mismo, como un resumen descriptivo de mis múltiples confirmaciones, que tenerlo ingresado automáticamente para mí. Así que prefiero especificar git squash -m "New summary."y he Ndeterminado automáticamente como el número de confirmaciones no apresuradas.
Acumenus
1
@ABB, esto suena como una pregunta separada. (No creo que es exactamente lo que el PO estaba pidiendo; nunca he sentido la necesidad de que en mi git calabaza flujo de trabajo.)
EthanB
3
Esto es muy dulce Personalmente, me gustaría una versión que use el mensaje de confirmación de la primera de las confirmaciones agrupadas. Sería bueno para cosas como ajustes de espacios en blanco.
funroll
@funroll De acuerdo. Simplemente soltar el último mensaje de confirmación es una necesidad súper común para mí. Deberíamos poder idear eso ...
Steve Clay
2
@ABB puede usar git commit --amendpara cambiar aún más el mensaje, pero este alias le permite tener un buen comienzo sobre lo que debería estar en el mensaje de confirmación.
dashesy
131

Gracias a esta útil publicación de blog , descubrí que puedes usar este comando para aplastar las últimas 3 confirmaciones:

git rebase -i HEAD~3

Esto es útil ya que funciona incluso cuando se encuentra en una sucursal local sin información de seguimiento / repositorio remoto.

El comando abrirá el editor interactivo de rebase, que luego le permite reordenar, aplastar, reformular, etc., como de costumbre.


Usando el editor de rebase interactivo:

El editor de rebase interactivo muestra los últimos tres commits. Esta restricción fue determinada por HEAD~3cuando se ejecuta el comando git rebase -i HEAD~3.

El commit más reciente HEAD, se muestra primero en la línea 1. Las líneas que comienzan con a #son comentarios / documentación.

La documentación que se muestra es bastante clara. En cualquier línea puede cambiar el comando de picka un comando de su elección.

Prefiero usar el comando fixupya que esto "aplasta" los cambios del commit en el commit en la línea de arriba y descarta el mensaje del commit.

Como el commit en la línea 1 es HEAD, en la mayoría de los casos dejaría esto como pick. No puede usar squasho fixupcomo no hay otro commit para aplastar el commit.

También puede cambiar el orden de las confirmaciones. Esto le permite aplastar o reparar confirmaciones que no son adyacentes cronológicamente.

editor de rebase interactivo


Un práctico ejemplo cotidiano.

Recientemente he comprometido una nueva característica. Desde entonces, he cometido dos correcciones de errores. Pero ahora he descubierto un error (o tal vez solo un error de ortografía) en la nueva función que cometí. ¡Que molesto! ¡No quiero un nuevo commit que contamine mi historial de commit!

Lo primero que hago es corregir el error y hacer una nueva confirmación con el comentario squash this into my new feature!.

Luego ejecuto git logo gitky obtengo el SHA de confirmación de la nueva característica (en este caso 1ff9460).

A continuación, traigo el editor interactivo de rebase con git rebase -i 1ff9460~. El ~cuando la confirmación SHA dice el editor para incluir que cometen en el editor.

A continuación, muevo la confirmación que contiene el arreglo ( fe7f1e0) debajo de la característica de confirmación y cambio picka fixup.

Al cerrar el editor, la solución quedará aplastada en la función commit y mi historial de commit se verá bien y limpio.

Esto funciona bien cuando todos los commits son locales, pero si intenta cambiar cualquier commit ya enviado al control remoto, ¡realmente puede causar problemas a otros desarrolladores que hayan verificado la misma rama!

ingrese la descripción de la imagen aquí

br3nt
fuente
55
¿tienes que elegir el mejor y aplastar el resto? Debería editar su respuesta para explicar cómo usar el editor de rebase interactivo con más detalle
Kolob Canyon
2
Sí, déjelo picken la línea 1. Si elige squasho fixuppara la confirmación en la línea 1, git mostrará un mensaje que dice "error: no se puede" arreglar "sin una confirmación previa". Luego le dará la opción de arreglarlo: "Puede arreglar esto con 'git rebase --edit-todo' y luego ejecutar 'git rebase --continue'". o simplemente puede abortar y comenzar de nuevo: "O puede abortar el rebase con 'git rebase --abort'".
br3nt
56

Si usa TortoiseGit, puede la función Combine to one commit:

  1. Abrir el menú contextual de TortoiseGit
  2. Seleccione Show Log
  3. Marque las confirmaciones relevantes en la vista de registro
  4. Seleccionar Combine to one commitdesde el menú contextual

Combina commits

Esta función ejecuta automáticamente todos los pasos necesarios de un solo git. Desafortunadamente, solo está disponible para Windows.

Matthias M
fuente
Hasta donde yo sé, esto no funcionará para los commits de fusión.
Thorkil Holm-Jacobsen
1
Aunque no ha sido comentado por ningún otro, esto incluso funciona para commits que no están en HEAD. Por ejemplo, mi necesidad era aplastar algunas confirmaciones de WIP que hice con una descripción más sensata antes de presionar. Funcionó muy bien. Por supuesto, todavía espero poder aprender cómo hacerlo mediante comandos.
Charles Roberto Canato
55

Para hacer esto, puede usar el siguiente comando git.

 git rebase -i HEAD~n

n (= 4 aquí) es el número de la última confirmación. Entonces tienes las siguientes opciones,

pick 01d1124 Message....
pick 6340aaa Message....
pick ebfd367 Message....
pick 30e0ccb Message....

Actualice como abajo pickuna confirmación y squashlas otras en la más reciente,

p 01d1124 Message....
s 6340aaa Message....
s ebfd367 Message....
s 30e0ccb Message....

Para más detalles, haga clic en el enlace

Jakir Hosen Khan
fuente
48

Basado en este artículo , encontré este método más fácil para mi caso de uso.

Mi rama 'dev' estaba por delante de 'origin / dev' en 96 commits (por lo que estos commits aún no se enviaron al control remoto).

Quería aplastar estos commits en uno antes de impulsar el cambio. Prefiero restablecer la rama al estado de 'origen / desarrollo' (esto dejará sin cambios todos los cambios de las 96 confirmaciones) y luego los confirmará de inmediato:

git reset origin/dev
git add --all
git commit -m 'my commit message'
trudolf
fuente
1
Justo lo que necesitaba. Aplasta las confirmaciones de mi rama de características, y luego obtengo esa selección en mi maestro.
David Victor
1
¡Esto no aplasta los commits anteriores!
IgorGanapolsky
¿podría explicarlo un poco más @igorGanapolsky?
trudolf
3
@trudolf Esto no es realmente aplastar (elegir compromisos individuales para aplastar). Esto se trata más de comprometer todos sus cambios a la vez.
IgorGanapolsky
15
Sí, por lo tanto, aplasta todos tus commits en uno. ¡Felicidades!
trudolf
45

En la rama en la que desea combinar las confirmaciones, ejecute:

git rebase -i HEAD~(n number of commits back to review)

ejemplo:

git rebase -i HEAD~1

Esto abrirá el editor de texto y debe cambiar la 'selección' frente a cada confirmación con 'squash' si desea que estas confirmaciones se fusionen. De la documentación:

p, pick = usar commit

s, squash = usar commit, pero fusionarse con el commit anterior

Por ejemplo, si está buscando fusionar todos los commits en uno, el 'pick' es el primer commit que realizó y todos los futuros (colocados debajo del primero) deben establecerse en 'squash'. Si usa vim, use : x en modo de inserción para guardar y salir del editor.

Luego para continuar el rebase:

git rebase --continue

Para obtener más información sobre esta y otras formas de reescribir su historial de confirmación, consulte esta publicación útil

aabiro
fuente
2
Por favor, explique también qué hace --continuey vim :x.
not2qubit
El rebase ocurrirá en bloques a medida que avanza a través de los commits en su rama, después de que haya git addconfigurado correctamente los archivos que usa git rebase --continuepara pasar al siguiente commit y comenzar a fusionar. :xes un comando que guardará los cambios del archivo cuando use vim, vea esto
aabiro
32

La respuesta de Anomies es buena, pero me sentí insegura al respecto, así que decidí agregar un par de capturas de pantalla.

Paso 0: registro de git

Mira dónde estás git log. Lo más importante es encontrar el hash de confirmación de la primera confirmación que no desea eliminar. Entonces solo el:

ingrese la descripción de la imagen aquí

Paso 1: git rebase

Ejecutar git rebase -i [your hash], en mi caso:

$ git rebase -i 2d23ea524936e612fae1ac63c95b705db44d937d

Paso 2: elige / aplasta lo que quieras

En mi caso, quiero aplastar todo en el commit que fue primero en el tiempo. El orden es del primero al último, así que exactamente al revés git log. En mi caso, quiero:

ingrese la descripción de la imagen aquí

Paso 3: ajustar los mensajes

Si seleccionó solo una confirmación y aplastó el resto, puede ajustar un mensaje de confirmación:

ingrese la descripción de la imagen aquí

Eso es. Una vez que guarde esto ( :wq), ya está. Míralo con git log.

Martin Thoma
fuente
2
sería bueno ver el resultado final, por ejemplo,git log
Timothy LJ Stewart
2
No, gracias. Así es exactamente como pierdo mis commits.
Axalix
@Axalix ¿Eliminaste todas tus líneas? Así es como pierdes tus compromisos.
a3y3
31

Procedimiento 1

1) Identificar el hash corto de confirmación

# git log --pretty=oneline --abbrev-commit
abcd1234 Update to Fix for issue B
cdababcd Fix issue B
deab3412 Fix issue A
....

Aquí incluso git log --onelinetambién se puede usar para obtener hash corto.

2) Si quieres aplastar (fusionar) las últimas dos confirmaciones

# git rebase -i deab3412 

3) Esto abre un nanoeditor para fusionar. Y se ve a continuación

....
pick cdababcd Fix issue B
pick abcd1234 Update to Fix for issue B
....

4) Cambie el nombre de la palabra picka la squashque está presente antes abcd1234. Después de cambiar el nombre, debería ser como a continuación.

....
pick cdababcd Fix issue B
squash abcd1234 Update to Fix for issue B
....

5) Ahora guarda y cierra el nanoeditor. Presione ctrl + oy presione Enterpara guardar. Y luego presione ctrl + xpara salir del editor.

6) Luego, el nanoeditor se abre nuevamente para actualizar los comentarios; si es necesario, actualícelo.

7) Ahora está aplastado con éxito, puedes verificarlo revisando los registros.

# git log --pretty=oneline --abbrev-commit
1122abcd Fix issue B
deab3412 Fix issue A
....

8) Ahora presiona para repo. Nota para agregar +signo antes del nombre de la sucursal. Esto significa empuje forzado.

# git push origin +master

Nota: Esto se basa en el uso de git en ubuntushell. Si está utilizando un sistema operativo diferente ( Windowso Mac), los comandos anteriores son los mismos, excepto el editor. Puede obtener un editor diferente.

Procedimiento 2

  1. Primero agregue los archivos necesarios para confirmar
git add <files>
  1. Luego confirme usando la --fixupopción y OLDCOMMITdebería estar en la que necesitamos fusionar (aplastar) esta confirmación.
git commit --fixup=OLDCOMMIT

Ahora esto crea una nueva confirmación sobre HEAD con fixup1 <OLDCOMMIT_MSG>.

  1. Luego ejecute el siguiente comando para fusionar (aplastar) el nuevo commit al OLDCOMMIT.
git rebase --interactive --autosquash OLDCOMMIT^

Aquí ^significa el compromiso anterior con OLDCOMMIT. Este rebasecomando abre una ventana interactiva en un editor (vim o nano) en el que no necesitamos hacer nada, solo guardar y salir es suficiente. Debido a que la opción pasada a esto moverá automáticamente la última confirmación al lado de la confirmación anterior y cambiará la operación a fixup(equivalente a squash). Luego rebase continúa y termina.

Procedimiento 3

  1. Si es necesario agregar nuevos cambios a la última confirmación, --amendse pueden usar medios con git-commit.
    # git log --pretty=oneline --abbrev-commit
    cdababcd Fix issue B
    deab3412 Fix issue A
    ....
    # git add <files> # New changes
    # git commit --amend
    # git log --pretty=oneline --abbrev-commit
    1d4ab2e1 Fix issue B
    deab3412 Fix issue A
    ....  

Aquí --amendcombina los nuevos cambios para la última confirmación cdababcdy genera una nueva ID de confirmación1d4ab2e1

Conclusión

  • La ventaja del primer procedimiento es aplastar múltiples confirmaciones y reordenar. Pero este procedimiento será difícil si necesitamos fusionar una solución para un commit muy antiguo.
  • Por lo tanto, el segundo procedimiento ayuda a combinar fácilmente el compromiso con el compromiso muy antiguo.
  • Y el tercer procedimiento es útil en un caso para aplastar nuevos cambios a la última confirmación.
rashok
fuente
Solo actualiza las últimas dos confirmaciones, incluso si restablezco una Id de confirmación a la sexta última confirmación, no sé por qué
Carlos Liu
Incluso usted puede reorganizar el orden de confirmación. Funciona bien.
rashok
29

Para aplastar los últimos 10 commits en 1 solo commit:

git reset --soft HEAD~10 && git commit -m "squashed commit"

Si también desea actualizar la rama remota con la confirmación aplastada:

git push -f
Ayan
fuente
--force es peligroso cuando varias personas trabajan en una rama compartida, ya que actualiza ciegamente el control remoto con su copia local. --force-with-lease podría haber sido mejor ya que se asegura de que el control remoto no tenga confirmaciones de otros desde la última vez que lo obtuvo.
Bharat
27

Si está en una rama remota (llamada feature-branch) clonada desde un Golden Repository ( golden_repo_name), entonces esta es la técnica para aplastar sus confirmaciones en una:

  1. Mira el repo dorado

    git checkout golden_repo_name
    
  2. Cree una nueva rama a partir de ella (repo de oro) de la siguiente manera

    git checkout -b dev-branch
    
  3. Squash se fusiona con su sucursal local que ya tiene

    git merge --squash feature-branch
    
  4. Confirma tus cambios (esta será la única confirmación que va en dev-branch)

    git commit -m "My feature complete"
    
  5. Empuje la rama a su repositorio local

    git push origin dev-branch
    
Sandesh Kumar
fuente
Como solo estaba aplastando ~ 100 commits (para sincronizar ramas svn a través de git-svn), ¡esto es mucho más rápido que el rebase interactivo!
sabio
1
Leyendo, veo el comentario de @ Chris, que es lo que solía hacer (rebase --soft ...) - lástima que stackoverflow ya no ponga la respuesta con cientos de votos a la cabeza ...
sabio
1
de acuerdo con usted @sage, esperemos que puedan hacerlo en algún momento en el futuro
Sandesh Kumar
Esta es la manera correcta. El enfoque de rebase es bueno, pero solo debe usarse para squash como solución de último recurso.
Axalix
20

Lo que puede ser realmente conveniente:
encuentre el hash de confirmación que desea aplastar, por ejemplo d43e15.

Ahora usa

git reset d43e15
git commit -am 'new commit name'
Ariel Gabizon
fuente
2
Esta. ¿Por qué no más gente usa esto? Es mucho más rápido que rebajar y aplastar los compromisos individuales.
a3y3
17

Esto es super duper kludgy, pero de una manera genial, así que lo arrojaré al ring:

GIT_EDITOR='f() { if [ "$(basename $1)" = "git-rebase-todo" ]; then sed -i "2,\$s/pick/squash/" $1; else vim $1; fi }; f' git rebase -i foo~5 foo

Traducción: proporcione un nuevo "editor" para git que, si el nombre de archivo que se va a editar es git-rebase-todo(la solicitud de rebase interactiva) cambia todo excepto la primera "selección" a "squash", y de lo contrario genera vim, de modo que cuando se le solicite para editar el mensaje de confirmación aplastado, obtienes vim. (Y obviamente estaba aplastando los últimos cinco commits en branch foo, pero puedes cambiar eso como quieras).

Sin embargo, probablemente haría lo que Mark Longair me sugirió .

Cascabel
fuente
77
+1: eso es divertido e instructivo, ya que para mí no era nada obvio que se pueda poner algo más complejo que el nombre de un programa en la variable de entorno GIT_EDITOR.
Mark Longair
16

Si desea dividir cada commit en un solo commit (por ejemplo, al lanzar un proyecto públicamente por primera vez), intente:

git checkout --orphan <new-branch>
git commit
William Denniss
fuente
15

Solución simple 2020 sin rebase:

git reset --soft HEAD~2

git commit -m "new commit message"

git push --force

2 significa que los dos últimos commits serán aplastados. Puedes reemplazarlo por cualquier número

Xys
fuente
14

Creo que la forma más fácil de hacer esto es creando una nueva rama fuera de master y fusionando --quasión de la rama característica.

git checkout master
git checkout -b feature_branch_squashed
git merge --squash feature_branch

Entonces tiene todos los cambios listos para confirmar.

usuario1376350
fuente
12

One-liner simple que siempre funciona, dado que actualmente está en la rama que desea aplastar, master es la rama de la que se originó, y la última confirmación contiene el mensaje de confirmación y el autor que desea utilizar:

git reset --soft $(git merge-base HEAD master) && git commit --reuse-message=HEAD@{1}
ColinM
fuente
44
He estado absolutamente furioso con la frustración por aplastar los commits y lo estúpidamente complicado que es: ¡solo usar el último mensaje y aplastarlos a todos en un solo commit! ¿Por qué es tan difícil? Este trazador de líneas hace eso por mí. Gracias desde el fondo de mi corazón enojado.
Locane
12

si, por ejemplo, desea aplastar las últimas 3 confirmaciones a una única confirmación en una rama (repositorio remoto) en, por ejemplo: https://bitbucket.org

Lo que hice es

  1. git reset --Soft Head ~ 3 &&
  2. git commit
  3. git push origin (branch_name) --force
Albert Ruelan
fuente
3
Solo tenga cuidado, ya que si usa la fuerza, entonces no hay forma de recuperar las confirmaciones anteriores desde que la eliminó
Albert Ruelan
12

⚠️ ADVERTENCIA: "Mis últimas confirmaciones de X" pueden ser ambiguas.

  (MASTER)  
Fleetwood Mac            Fritz
      ║                    ║
  Add Danny  Lindsey     Stevie       
    Kirwan  Buckingham    Nicks                                              
      ║         ╚═══╦══════╝     
Add Christine       ║          
   Perfect      Buckingham
      ║           Nicks            
    LA1974══════════╝                                    
      ║                  
      ║                  
    Bill <══════ YOU ARE EDITING HERE
  Clinton        (CHECKED OUT, CURRENT WORKING DIRECTORY)              

En esta historia muy abreviada del depósito https://github.com/fleetwood-mac/band-history , ha abierto una solicitud de extracción para fusionarse en el compromiso de Bill Clinton en el compromiso original ( MASTER) de Fleetwood Mac.

Abriste una solicitud de extracción y en GitHub ves esto:

Cuatro commits:

  • Agregar Danny Kirwan
  • Añadir Christine Perfect
  • LA1974
  • Bill Clinton

Pensando que a nadie le importaría leer el historial completo del repositorio. (En realidad hay un repositorio, ¡haga clic en el enlace de arriba!) Decide aplastar estas confirmaciones. Entonces vas y corres git reset --soft HEAD~4 && git commit. Luego git push --forcelo colocas en GitHub para limpiar tu PR.

¿Y que pasa? Acabas de hacer un solo compromiso que pasa de Fritz a Bill Clinton. Porque olvidaste que ayer estabas trabajando en la versión de Buckingham Nicks de este proyecto. Y git logno coincide con lo que ves en GitHub.

🐻 MORAL DE LA HISTORIA

  1. Encuentra los archivos exactos que desea llegar a , y git checkoutellas
  2. Encuentre el compromiso previo exacto que desea mantener en el historial, y git reset --softque
  3. Hacer una git commitque se dobla directamente de la de la de
William Entriken
fuente
1
Esta es 100% la forma más fácil de hacer esto. Si su HEAD actual es el estado correcto que desea, puede omitir el n. ° 1.
Stan
Esta es la única manera que sé que permite reescribir el primer historial de confirmación.
Vadorequest
9

Si no le importan los mensajes de confirmación de las confirmaciones intermedias, puede usar

git reset --mixed <commit-hash-into-which-you-want-to-squash>
git commit -a --amend
Zsolt Z.
fuente
7

Si está trabajando con GitLab, puede hacer clic en la opción Squash en la Solicitud de fusión como se muestra a continuación. El mensaje de confirmación será el título de la Solicitud de fusión.

ingrese la descripción de la imagen aquí

arush436
fuente
6
git rebase -i HEAD^^

donde el número de ^ es X

(en este caso, aplasta los dos últimos commits)

Yoaz Menda
fuente
6

Además de otras excelentes respuestas, me gustaría agregar cómo git rebase -isiempre me confunde con el orden de confirmación: ¿más antiguo a más nuevo o viceversa? Entonces este es mi flujo de trabajo:

  1. git rebase -i HEAD~[N], donde N es el número de confirmaciones a las que quiero unirme, comenzando por la más reciente . Entonces git rebase -i HEAD~5significaría "aplastar los últimos 5 commits en uno nuevo";
  2. aparece el editor, mostrando la lista de confirmaciones que quiero fusionar. Ahora se muestran en orden inverso : la confirmación anterior está en la parte superior. Marque como "squash" o "s" todas las confirmaciones allí, excepto la primera / anterior : se utilizará como punto de partida. Guarda y cierra el editor;
  3. el editor vuelve a aparecer con un mensaje predeterminado para la nueva confirmación: cámbielo según sus necesidades, guárdelo y ciérrelo. Squash completado!

Fuentes y lecturas adicionales: # 1 , # 2 .

Ignorante
fuente
6

¿Qué pasa con una respuesta a la pregunta relacionada con un flujo de trabajo como este?

  1. muchos commits locales, combinados con múltiples fusiones de master ,
  2. finalmente un empujón al control remoto,
  3. PR y fusionar TO master por revisor . (Sí, sería más fácil para el desarrollador merge --squashdespués del RP, pero el equipo pensó que eso retrasaría el proceso).

No he visto un flujo de trabajo como ese en esta página. (Esos pueden ser mis ojos). Si entiendo rebasecorrectamente, múltiples fusiones requerirían múltiples resoluciones de conflictos . ¡NO quiero ni siquiera pensar en eso!

Entonces, esto parece funcionar para nosotros.

  1. git pull master
  2. git checkout -b new-branch
  3. git checkout -b new-branch-temp
  4. editar y comprometerse mucho localmente, fusionar master regularmente
  5. git checkout new-branch
  6. git merge --squash new-branch-temp // pone todos los cambios en el escenario
  7. git commit 'one message to rule them all'
  8. git push
  9. El revisor hace relaciones públicas y se fusiona para dominar.
allenjom
fuente
Desde muchas opiniones me gusta tu enfoque. Es muy conveniente y rápido
Artem Solovev
5

Creo que una solución más genérica no es especificar confirmaciones 'N', sino más bien la rama / commit-id que desea aplastar. Esto es menos propenso a errores que contar las confirmaciones hasta una confirmación específica: simplemente especifique la etiqueta directamente, o si realmente desea contar puede especificar HEAD ~ N.

En mi flujo de trabajo, comienzo una rama, y ​​mi primer compromiso en esa rama resume el objetivo (es decir, generalmente es lo que presionaré como el mensaje 'final' para la característica al repositorio público). Entonces, cuando haya terminado, todo Quiero git squash mastervolver al primer mensaje y luego estoy listo para presionar.

Yo uso el alias:

squash = !EDITOR="\"_() { sed -n 's/^pick //p' \"\\$1\"; sed -i .tmp '2,\\$s/^pick/f/' \"\\$1\"; }; _\"" git rebase -i

Esto volcará el historial que se está aplastando antes de que lo haga; esto le brinda la oportunidad de recuperarse al obtener una ID de confirmación anterior de la consola si desea revertirla. (Los usuarios de Solaris notan que usa la -iopción GNU sed , los usuarios de Mac y Linux deberían estar de acuerdo con esto).

Ethan
fuente
Probé el alias pero no estoy seguro de si los reemplazos de sed tienen algún efecto. ¿Que deberían hacer?
Raine
El primer sed simplemente volca el historial a la consola. El segundo sed reemplaza todo el 'pick' con 'f' (arreglo) y reescribe el archivo del editor en el lugar (la opción -i). Entonces el segundo hace todo el trabajo.
Ethan
Tiene razón, contar N-número de confirmaciones específicas es muy propenso a errores. Me ha jodido varias veces y ha perdido horas tratando de deshacer el rebase.
IgorGanapolsky
Hola Ethan, me gustaría saber si este flujo de trabajo ocultará posibles conflictos en la fusión. Por lo tanto, considere si tiene dos ramas, maestro y esclavo. Si el esclavo tiene un conflicto con el maestro y lo usamos git squash mastercuando nos desprotegen. que va a pasar vamos a ocultar el conflicto?
Sergio Bilello
@Sergio Este es un caso de reescritura del historial, por lo que probablemente tendrá conflictos si aplasta las confirmaciones que ya se han presionado, y luego intenta fusionar / volver a crear la versión aplastada. (Algunos casos triviales podrían salirse con la suya.)
Ethan
5

En cuestión podría ser ambiguo lo que se entiende por "último".

por ejemplo, git log --graphgenera lo siguiente (simplificado):

* commit H0
|
* merge
|\
| * commit B0
| |
| * commit B1
| | 
* | commit H1
| |
* | commit H2
|/
|

Luego, las últimas confirmaciones por tiempo son H0, fusionar, B0. Para aplastarlos, deberá volver a crear la rama fusionada en commit H1.

El problema es que H0 contiene H1 y H2 (y generalmente más confirmaciones antes de la fusión y después de la ramificación) mientras que B0 no. Por lo tanto, debe administrar los cambios desde H0, fusionar, H1, H2, B0 al menos.

Es posible usar rebase pero de manera diferente que en otras respuestas mencionadas:

rebase -i HEAD~2

Esto le mostrará las opciones de elección (como se menciona en otras respuestas):

pick B1
pick B0
pick H0

Ponga la calabaza en lugar de recoger a H0:

pick B1
pick B0
s H0

Después de guardar y salir, rebase aplicará commits a su vez después de H1. Eso significa que le pedirá que resuelva los conflictos nuevamente (donde HEAD será H1 al principio y luego acumulará confirmaciones a medida que se apliquen).

Una vez que finalice el rebase, puede elegir el mensaje para H0 y B0 aplastados:

* commit squashed H0 and B0
|
* commit B1
| 
* commit H1
|
* commit H2
|

PD: si solo hace un reinicio a BO: (por ejemplo, el uso reset --mixedse explica con más detalle aquí https://stackoverflow.com/a/18690845/2405850 ):

git reset --mixed hash_of_commit_B0
git add .
git commit -m 'some commit message'

luego se comprimen los cambios B0 de H0, H1, H2 (perder completamente los cambios después de la ramificación y antes de la fusión.

littlewhywhat
fuente
4

1) reinicio de git --soft HEAD ~ n

n - Número de la confirmación, es necesario aplastar

2) git commit -m "nuevo mensaje de confirmación"

3) git push origin branch_name --force

Poonkodi
fuente