¿Cómo aplastar todos los git commits en uno?

481

¿Cómo aplastas todo tu repositorio hasta la primera confirmación?

Puedo cambiar la base a la primera confirmación, pero eso me dejaría con 2 confirmaciones. ¿Hay alguna manera de hacer referencia al commit antes del primero?

Verhogen
fuente
8
¿"el compromiso antes del primero"?
innaM el
31
@innaM: es el compromiso primordial que engendró al git . (Espera que el humor pase lo suficientemente bien a través de la red).
ripper234
11
Para aquellos que vengan después a esta pregunta, asegúrese de usar la respuesta más moderna .
Droogans
1
Relacionado, pero no un duplicado (en --rootrealidad no es la mejor solución para aplastar todos los commits si hay muchos de ellos para aplastar): ¿ Combinar los dos primeros commits de un repositorio Git? .
2
Imo: este es el mejor de @MrTux: stackoverflow.com/questions/30236694/… .
J0hnG4lt

Respuestas:

129

Quizás la forma más fácil es crear un nuevo repositorio con el estado actual de la copia de trabajo. Si desea conservar todos los mensajes de confirmación que puede hacer primero git log > original.logy luego editarlos para su mensaje de confirmación inicial en el nuevo repositorio:

rm -rf .git
git init
git add .
git commit

o

git log > original.log
# edit original.log as desired
rm -rf .git
git init
git add .
git commit -F original.log
Pat Notz
fuente
6262
pero estás perdiendo ramas con este método
Olivier Refalo
150
Git ha evolucionado desde que se dio esta respuesta. No, no hay una manera más sencilla y mejor: git rebase -i --root. Ver: stackoverflow.com/a/9254257/109618
David J.
55
Esto podría funcionar para algunos casos, pero esencialmente no es la respuesta a la pregunta. Con esta receta, pierdes toda tu configuración y todas las demás ramas también.
Iwein
3
Esta es una solución horrible que es innecesariamente destructiva. Por favor no lo uses.
Daniel Kamil Kozar
55
También romperá submódulos. -1
Krum
694

A partir de git 1.6.2 , puedes usar git rebase --root -i.

Para cada confirmación, excepto la primera, cambie picka squash.

Jordan Lewis
fuente
49
Agregue un ejemplo de comando de trabajo completo que responda a la pregunta original.
Jake
29
Desearía haber leído esto antes de volar todo mi repositorio como dice la respuesta aceptada: /
Mike Chamberlain
38
Esta respuesta está bien , pero si está rebatiendo interactivamente más de, digamos, 20 confirmaciones, la rebase interactiva probablemente será demasiado lenta y difícil de manejar. Probablemente le resulte difícil tratar de aplastar cientos o miles de commits. Iría con un reinicio suave o mixto a la confirmación de raíz, luego volvería a comprometer, en ese caso.
20
@Pred No lo use squashpara todos los commits. El primero tiene que ser pick.
Geert
14
Si tiene muchos commits, es difícil cambiar manualmente 'pick' a 'squash'. Use :% s / pick / squash / g en la línea de comando VIM para hacerlo más rápido.
eilas
315

Actualizar

He hecho un alias git squash-all.
Ejemplo de uso : git squash-all "a brand new start".

[alias]
  squash-all = "!f(){ git reset $(git commit-tree HEAD^{tree} -m \"${1:-A new start}\");};f"

Advertencia : recuerde proporcionar un comentario; de lo contrario, se usará el mensaje de confirmación predeterminado "Un nuevo comienzo".

O puede crear el alias con el siguiente comando:

git config --global alias.squash-all '!f(){ git reset $(git commit-tree HEAD^{tree} -m "${1:-A new start}");};f'

Un trazador de líneas

git reset $(git commit-tree HEAD^{tree} -m "A new start")

Nota : aquí " A new start" es solo un ejemplo, siéntase libre de usar su propio idioma.

TL; DR

No es necesario aplastar, usar git commit-treepara crear una confirmación huérfana e ir con ella.

Explique

  1. crear una única confirmación a través de git commit-tree

    Lo que git commit-tree HEAD^{tree} -m "A new start"hace es:

    Crea un nuevo objeto de confirmación basado en el objeto de árbol proporcionado y emite el nuevo ID del objeto de confirmación en stdout. El mensaje de registro se lee desde la entrada estándar, a menos que se den las opciones -m o -F.

    La expresión HEAD^{tree}significa el objeto del árbol correspondiente HEAD, es decir, la punta de su rama actual. ver Tree-Objects y Commit-Objects .

  2. restablecer la rama actual a la nueva confirmación

    Luego, git resetsimplemente restablezca la rama actual al objeto de confirmación recién creado.

De esta manera, no se toca nada en el espacio de trabajo, ni hay necesidad de rebase / squash, lo que lo hace realmente rápido. Y el tiempo necesario es irrelevante para el tamaño del repositorio o la profundidad del historial.

Variación: Nuevo repositorio de una plantilla de proyecto

Esto es útil para crear el "commit inicial" en un nuevo proyecto usando otro repositorio como template / archetype / seed / skeleton. Por ejemplo:

cd my-new-project
git init
git fetch --depth=1 -n https://github.com/toolbear/panda.git
git reset --hard $(git commit-tree FETCH_HEAD^{tree} -m "initial commit")

Esto evita agregar el repositorio de plantillas como un control remoto ( origino de otro modo) y contrae el historial del repositorio de plantillas en su confirmación inicial.

ryenus
fuente
66
La sintaxis de revisión de git (HEAD ^ {tree}) se explica aquí en caso de que alguien más se preguntara: jk.gs/gitrevisions.html
Colin Bowern
1
¿Esto restablece el repositorio local y remoto, o solo uno de ellos?
aleclarson
44
@aleclarson, esto solo restablece la rama actual en el repositorio local, uso git push -fpara propagación.
ryenus
2
Encontré esta respuesta mientras buscaba una manera de comenzar un nuevo proyecto desde un repositorio de plantillas de proyecto que no involucrara git clone. Si se agrega --harda git resete interruptor HEADcon FETCH_HEADel git commit-treeque puede crear una inicial confirmación después de ir a buscar el repositorio de plantillas. He editado la respuesta con una sección al final que demuestra esto.
toolbear
44
Podrías deshacerte de esa "Advertencia" pero solo usando${1?Please enter a message}
Elliot Cameron
172

Si todo lo que quiere hacer es aplastar todas sus confirmaciones hasta la confirmación raíz, entonces mientras

git rebase --interactive --root

puede funcionar, no es práctico para una gran cantidad de confirmaciones (por ejemplo, cientos de confirmaciones), porque la operación de rebase probablemente se ejecutará muy lentamente para generar la lista interactiva de confirmaciones del editor de rebase, así como ejecutar la rebase en sí.

Aquí hay dos soluciones más rápidas y más eficientes cuando está aplastando una gran cantidad de confirmaciones:

Solución alternativa # 1: ramas huérfanas

Simplemente puede crear una nueva rama huérfana en la punta (es decir, la confirmación más reciente) de su rama actual. Esta rama huérfana forma la confirmación raíz inicial de un árbol de historial de confirmación completamente nuevo y separado, que es efectivamente equivalente a aplastar todas sus confirmaciones:

git checkout --orphan new-master master
git commit -m "Enter commit message for your new initial commit"

# Overwrite the old master branch reference with the new one
git branch -M new-master master

Documentación:

Solución alternativa # 2: restablecimiento parcial

Otra solución eficiente es simplemente usar un reinicio mixto o suave en la confirmación raíz <root>:

git branch beforeReset

git reset --soft <root>
git commit --amend

# Verify that the new amended root is no different
# from the previous branch state
git diff beforeReset

Documentación:


fuente
22
Solución alternativa # 1: ramas huérfanas - rocas!
Thomas
8
Solución alternativa # 1 FTW. Solo para agregar, si desea enviar sus cambios al control remoto, hágalo git push origin master --force.
Eddy Verbruggen
1
no debe olvidargit push --force
NecipAllef
¡No hagas una ramificación huérfana en el código (es decir, no hagas la solución alternativa n. ° 1 anterior) si estás a punto de empujar a una solicitud de extracción de Github abierta! Github cerrará tu PR porque la cabeza actual no es un descendiente del sha de cabeza almacenado .
Andrew Mackie
La solución alternativa n. ° 1 también evita los conflictos de fusión que pueden ocurrir al aplastar los commits
FernAndr
52
echo "message" | git commit-tree HEAD^{tree}

Esto creará una confirmación huérfana con el árbol de HEAD y generará su nombre (SHA-1) en stdout. Luego simplemente reinicie su rama allí.

git reset SHA-1
kusma
fuente
27
git reset $(git commit-tree HEAD^{tree} -m "commit message")Lo haría más fácil.
ryenus
44
^ ¡ESTO! - Debería ser una respuesta. No estoy completamente seguro de si era la intención del autor, pero era mía (necesitaba un repositorio prístino con un solo compromiso, y eso hace el trabajo).
chesterbr
@ryenus, tu solución hizo exactamente lo que estaba buscando. Si agrega su comentario como respuesta, lo aceptaré.
tldr
2
La razón por la que no sugerí la variante subshell es que no funcionará en cmd.exe en Windows.
kusma
En las solicitudes de Windows, es posible que tenga que citar el último parámetro: echo "message" | git commit-tree "HEAD^{tree}"
Bernard
41

Así es como terminé haciendo esto, en caso de que funcione para otra persona:

Recuerde que siempre existe el riesgo de hacer cosas como esta, y nunca es una mala idea crear una rama de salvar antes de comenzar.

Comience por iniciar sesión

git log --oneline

Desplácese hasta la primera confirmación, copie SHA

git reset --soft <#sha#>

Reemplace <#sha#>con el SHA copiado del registro

git status

Asegúrese de que todo esté verde, de lo contrario, ejecute git add -A

git commit --amend

Modifique todos los cambios actuales a la primera confirmación actual

Ahora forzar a empujar esta rama y sobrescribirá lo que hay allí.

Logan
fuente
1
Excelente opción! Muy simple
twicejr
1
¡Esto es más que fantástico! ¡Gracias!
Matt Komarnicki el
1
Tenga en cuenta que esto realmente parece dejar la historia. Lo dejas huérfano, pero sigue ahí.
Brad
Respuesta muy útil ... pero debe tener en cuenta que después del comando modificar se encontrará en el editor vim con su sintaxis especial. ESC, ENTER,: x es tu amigo.
Erich Kuester
Excelente opción!
danivicario
36

Leí algo sobre el uso de injertos pero nunca lo investigé mucho.

De todos modos, puedes aplastar esos 2 últimos commits manualmente con algo como esto:

git reset HEAD~1
git add -A
git commit --amend
R. Martinho Fernandes
fuente
44
Esta es en realidad la respuesta que estaba buscando, ¡ojalá fuera la aceptada!
Jay
Esta es una respuesta asombrosa
Maestro Yoda
36

La forma más fácil es usar el comando 'plomería' update-refpara eliminar la rama actual.

No puede usarlo git branch -Dya que tiene una válvula de seguridad para evitar que elimine la rama actual.

Esto lo devuelve al estado de 'confirmación inicial' donde puede comenzar con una nueva confirmación inicial.

git update-ref -d refs/heads/master
git commit -m "New initial commit"
CB Bailey
fuente
15

En una línea de 6 palabras

git checkout --orphan new_root_branch  &&  git commit
kyb
fuente
@AlexanderMills, deberías leer git help checkoutsobre--orphan
kyb
¿Puedes vincularlo a los documentos para que todos los que lean esto no tengan que buscarlo manualmente?
Alexander Mills
1
fácil. aquí estágit help checkout --orphan
kyb
8

crear una copia de seguridad

git branch backup

restablecer a commit especificado

git reset --soft <#root>

luego agregue todos los archivos a la puesta en escena

git add .

confirmar sin actualizar el mensaje

git commit --amend --no-edit

empujar una nueva rama con compromisos aplastados para repo

git push -f
David Morton
fuente
¿Esto preservará los mensajes de confirmación anteriores?
not2qubit
1
@ not2qubit no, esto no conservará los mensajes de confirmación anteriores, en lugar de confirmar # 1, confirmar # 2, confirmar # 3, obtendrá todos los cambios en esas confirmaciones agrupadas en una sola confirmación # 1. El <root>commit # 1 será el commit al que restablecerá nuevamente. git commit --amend --no-editconfirmará todos los cambios en la confirmación actual <root>sin necesidad de editar el mensaje de confirmación.
David Morton el
5

Para aplastar usando injertos

Agregue un archivo .git/info/grafts, coloque allí el hash de confirmación que desea convertir en su raíz

git log ahora comenzará desde ese commit

Para hacerlo correr 'real' git filter-branch

dimus
fuente
1

Esta respuesta mejora en un par de arriba (por favor, vótelos), suponiendo que, además de crear una confirmación (sin padres, sin historial), también desea conservar todos los datos de confirmación de esa confirmación:

  • Autor (nombre y correo electrónico)
  • Fecha de autor
  • Compromiso (nombre y correo electrónico)
  • Fecha comprometida
  • Enviar mensaje de registro

Por supuesto, el commit-SHA del commit nuevo / único cambiará, porque representa un nuevo (no) historial, convirtiéndose en un commit sin padre / root.

Esto se puede hacer leyendo git logy configurando algunas variables para git commit-tree. Suponiendo que desea crear una única confirmación desde masteruna nueva rama one-commit, conservando los datos de confirmación anteriores:

git checkout -b one-commit master ## create new branch to reset
git reset --hard \
$(eval "$(git log master -n1 --format='\
COMMIT_MESSAGE="%B" \
GIT_AUTHOR_NAME="%an" \
GIT_AUTHOR_EMAIL="%ae" \
GIT_AUTHOR_DATE="%ad" \
GIT_COMMITTER_NAME="%cn" \
GIT_COMMITTER_EMAIL="%ce" \
GIT_COMMITTER_DATE="%cd"')" 'git commit-tree master^{tree} <<COMMITMESSAGE
$COMMIT_MESSAGE
COMMITMESSAGE
')
javabrett
fuente
1

Para hacer esto, puede restablecer su repositorio git local al primer hashtag de confirmación, para que todos sus cambios después de esa confirmación no se realicen, y luego puede confirmar con la opción --amend.

git reset your-first-commit-hashtag
git add .
git commit --amend

Y luego edite el primer commit nam si es necesario y guarde el archivo.

T.EL MOUSSAOUI
fuente
1

Para mí funcionó así: tenía 4 commits en total, y usé rebase interactivo:

git rebase -i HEAD~3

El primer commit permanece y tomé los 3 últimos commits.

En caso de que esté atascado en el editor que aparece a continuación, verá algo como:

pick fda59df commit 1
pick x536897 commit 2
pick c01a668 commit 3

Tienes que tomar el primer compromiso y aplastar a los demás. Lo que debes tener es:

pick fda59df commit 1
squash x536897 commit 2
squash c01a668 commit 3

Para eso use la tecla INSERT para cambiar el modo 'insertar' y 'editar'.

Para guardar y salir del editor use :wq. Si su cursor está entre esas líneas de confirmación o en otro lugar, presione ESC e intente nuevamente.

Como resultado, tuve dos commits: el primero que permaneció y el segundo con el mensaje "Esta es una combinación de 3 commits".

Verifique los detalles aquí: https://makandracards.com/makandra/527-squash-several-git-commits-into-a-single-commit

Vlad
fuente
0

Usualmente lo hago así:

  • Asegúrese de que todo esté confirmado y escriba la última ID de confirmación en caso de que algo salga mal, o cree una rama separada como copia de seguridad

  • Ejecute git reset --soft `git rev-list --max-parents=0 --abbrev-commit HEAD`para restablecer su cabeza a la primera confirmación, pero deje su índice sin cambios. Todos los cambios desde la primera confirmación ahora aparecerán listos para ser confirmados.

  • Ejecute git commit --amend -m "initial commit"para modificar su confirmación en la primera confirmación y cambie el mensaje de confirmación, o si desea conservar el mensaje de confirmación existente, puede ejecutargit commit --amend --no-edit

  • Corre git push -fpara forzar tus cambios

Kent Munthe Caspersen
fuente