¿Cómo puedo aplastar mis últimas confirmaciones X juntas en una confirmación usando Git?
¿Cómo puedo aplastar mis últimas confirmaciones X juntas en una confirmación usando Git?
Respuestas:
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
.
<after-this-commit>
?
<after-this-commit>
es commit X + 1, es decir, padre del commit más antiguo que desea aplastar.
rebase -i
enfoque y reset --soft
es, rebase -i
me permite retener al autor de confirmación, mientras reset --soft
que 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.
Puede hacerlo con bastante facilidad sin git rebase
o 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 -i
que 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").
git rebase --squash-recent
, o incluso git commit --amend-many
.
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.
push -f
pero por lo demás fue encantador, gracias.
git push --force
después para que tome el compromiso
Puede usar git merge --squash
para 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 status
esté limpio (ya git reset --hard
que 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 --squash
opció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 commit
sugerido 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.
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 merge
a hacer una de las cosas para las que git rebase
está específicamente diseñado?
git merge --squash
también es más fácil de usar en un script. Esencialmente, el razonamiento fue que no necesitas la "interactividad" git rebase -i
para nada.
git merge --squash
es 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, ¡
HEAD@{1}
solo estar seguro, por ejemplo, cuando su flujo de trabajo se interrumpe durante una hora por un corte de energía, etc.
Recomiendo evitar git reset
cuando 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 ...
git merge --squash (working branch name)
git commit
El mensaje de confirmación se rellenará previamente en función de la calabaza.
gitk
para 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).
git branch your-feature && git reset --hard HEAD~N
la forma más conveniente. Sin embargo, involucra git reset nuevamente, lo que esta respuesta intentó evitar.
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 ~/.gitconfig
ahora 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 N
confirmaciones, 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 --amend
modificarlo manualmente. (O edite el alias para que coincida con sus gustos).
git squash -m "New summary."
y he N
determinado automáticamente como el número de confirmaciones no apresuradas.
git commit --amend
para 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.
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~3
cuando 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 pick
a un comando de su elección.
Prefiero usar el comando fixup
ya 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 squash
o fixup
como 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.
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 log
o gitk
y 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 pick
a 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!
pick
en la línea 1. Si elige squash
o fixup
para 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'".
Si usa TortoiseGit, puede la función Combine to one commit
:
Show Log
Combine to one commit
desde el menú contextualEsta función ejecuta automáticamente todos los pasos necesarios de un solo git. Desafortunadamente, solo está disponible para Windows.
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 pick
una confirmación y squash
las 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
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'
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:
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
--continue
y vim :x
.
git add
configurado correctamente los archivos que usa git rebase --continue
para pasar al siguiente commit y comenzar a fusionar. :x
es un comando que guardará los cambios del archivo cuando use vim, vea esto
La respuesta de Anomies es buena, pero me sentí insegura al respecto, así que decidí agregar un par de capturas de pantalla.
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:
Ejecutar git rebase -i [your hash]
, en mi caso:
$ git rebase -i 2d23ea524936e612fae1ac63c95b705db44d937d
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:
Si seleccionó solo una confirmación y aplastó el resto, puede ajustar un mensaje de confirmación:
Eso es. Una vez que guarde esto ( :wq
), ya está. Míralo con git log
.
git log
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 --oneline
tambié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 nano
editor 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 pick
a la squash
que 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 nano
editor. Presione ctrl + o
y presione Enter
para guardar. Y luego presione ctrl + x
para salir del editor.
6) Luego, el nano
editor 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 ubuntu
shell. Si está utilizando un sistema operativo diferente ( Windows
o Mac
), los comandos anteriores son los mismos, excepto el editor. Puede obtener un editor diferente.
git add <files>
--fixup
opción y OLDCOMMIT
deberí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>
.
OLDCOMMIT
.git rebase --interactive --autosquash OLDCOMMIT^
Aquí ^
significa el compromiso anterior con OLDCOMMIT
. Este rebase
comando 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.
--amend
se 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í --amend
combina los nuevos cambios para la última confirmación cdababcd
y genera una nueva ID de confirmación1d4ab2e1
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
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:
Mira el repo dorado
git checkout golden_repo_name
Cree una nueva rama a partir de ella (repo de oro) de la siguiente manera
git checkout -b dev-branch
Squash se fusiona con su sucursal local que ya tiene
git merge --squash feature-branch
Confirma tus cambios (esta será la única confirmación que va en dev-branch)
git commit -m "My feature complete"
Empuje la rama a su repositorio local
git push origin dev-branch
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'
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ó .
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
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.
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}
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
(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:
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 --force
lo 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 log
no coincide con lo que ves en GitHub.
git checkout
ellasgit reset --soft
quegit commit
que se dobla directamente de la de la degit rebase -i HEAD^^
donde el número de ^ es X
(en este caso, aplasta los dos últimos commits)
Además de otras excelentes respuestas, me gustaría agregar cómo git rebase -i
siempre me confunde con el orden de confirmación: ¿más antiguo a más nuevo o viceversa? Entonces este es mi flujo de trabajo:
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~5
significaría "aplastar los últimos 5 commits en uno nuevo";¿Qué pasa con una respuesta a la pregunta relacionada con un flujo de trabajo como este?
merge --squash
despué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 rebase
correctamente, múltiples fusiones requerirían múltiples resoluciones de conflictos . ¡NO quiero ni siquiera pensar en eso!
Entonces, esto parece funcionar para nosotros.
git pull master
git checkout -b new-branch
git checkout -b new-branch-temp
git checkout new-branch
git merge --squash new-branch-temp
// pone todos los cambios en el escenariogit commit 'one message to rule them all'
git push
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 master
volver 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 -i
opción GNU sed , los usuarios de Mac y Linux deberían estar de acuerdo con esto).
git squash master
cuando nos desprotegen. que va a pasar vamos a ocultar el conflicto?
En cuestión podría ser ambiguo lo que se entiende por "último".
por ejemplo, git log --graph
genera 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 --mixed
se 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.