Comment écraser tous les git commits en un seul?

481

Comment écraser l'intégralité de votre référentiel jusqu'au premier commit?

Je peux rebaser au premier commit, mais cela me laisserait avec 2 commits. Existe-t-il un moyen de référencer le commit avant le premier?

Verhogen
la source
8
"le commit avant le premier"?
innaM
31
@innaM - C'est l' engagement primordial qui a engendré le git . (L'espoir est que l'humour passe assez bien à travers l'interweb).
ripper234
11
Pour ceux qui viendront plus tard à cette question, assurez-vous d'utiliser la réponse la plus moderne .
Droogans
1
Lié, mais pas un doublon ( --rootn'est-ce pas la meilleure solution pour écraser toutes les validations s'il y en a beaucoup à écraser): combiner les deux premières validations d'un référentiel Git? .
2
Imo: c'est le meilleur de @MrTux: stackoverflow.com/questions/30236694/… .
J0hnG4lt

Réponses:

129

Le moyen le plus simple est peut-être de créer simplement un nouveau référentiel avec l'état actuel de la copie de travail. Si vous souhaitez conserver tous les messages de validation, vous pouvez d'abord le faire git log > original.log, puis le modifier pour votre message de validation initial dans le nouveau référentiel:

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

ou

git log > original.log
# edit original.log as desired
rm -rf .git
git init
git add .
git commit -F original.log
Pat Notz
la source
62
mais vous perdez des branches avec cette méthode
Olivier Refalo
150
Git a évolué depuis que cette réponse a été donnée. Non, il existe un moyen plus simple et mieux: git rebase -i --root. Voir: stackoverflow.com/a/9254257/109618
David J.12
5
Cela peut fonctionner dans certains cas, mais ce n'est essentiellement pas la réponse à la question. Avec cette recette, vous perdez également toute votre config et toutes les autres branches.
iwein
3
C'est une solution horrible qui est inutilement destructrice. Veuillez ne pas l'utiliser.
Daniel Kamil Kozar
5
cassera également les sous-modules. -1
Krum
694

Depuis git 1.6.2 , vous pouvez utiliser git rebase --root -i.

Pour chaque commit sauf le premier, passez pickà squash.

Jordan Lewis
la source
49
Veuillez ajouter un exemple de commande complet et fonctionnel qui répond à la question d'origine.
Jake
29
Je souhaite que je lise ceci avant de souffler tout mon référentiel comme le dit la réponse acceptée: /
Mike Chamberlain
38
Cette réponse est correcte , mais si vous rebasez interactivement plus de, disons, 20 commits, le rebase interactif sera probablement beaucoup trop lent et lourd. Vous aurez probablement du mal à écraser des centaines ou des milliers de commits. J'irais avec une réinitialisation douce ou mixte à la validation racine, puis recommencer, dans ce cas.
20
@Pred Ne pas utiliser squashpour toutes les validations . Le tout premier doit être pick.
Geert
14
Si vous avez de nombreux commits, il est difficile de changer manuellement «pick» en «squash». Utilisez :% s / pick / squash / g dans la ligne de commande VIM pour le faire plus rapidement.
eilas
315

Mise à jour

J'ai fait un alias git squash-all.
Exemple d' utilisation : git squash-all "a brand new start".

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

Attention : n'oubliez pas de fournir un commentaire, sinon le message de validation par défaut "Un nouveau départ" serait utilisé.

Ou vous pouvez créer l'alias avec la commande suivante:

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

Bon mot

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

Remarque : ici " A new start" n'est qu'un exemple, n'hésitez pas à utiliser votre propre langue.

TL; DR

Pas besoin de squash, utilisez git commit-treepour créer un commit orphelin et allez-y.

Explique

  1. créer un seul commit via git commit-tree

    Qu'est git commit-tree HEAD^{tree} -m "A new start"- ce que c'est:

    Crée un nouvel objet de validation basé sur l'objet d'arborescence fourni et émet le nouvel identifiant d'objet de validation sur stdout. Le message du journal est lu à partir de l'entrée standard, sauf si les options -m ou -F sont fournies.

    L'expression HEAD^{tree}signifie l'objet arbre correspondant à HEAD, à savoir la pointe de votre branche actuelle. voir Tree-Objects et Commit-Objects .

  2. réinitialiser la branche actuelle au nouveau commit

    Ensuite, git resetréinitialisez simplement la branche actuelle sur l'objet de validation nouvellement créé.

De cette façon, rien dans l'espace de travail n'est touché, ni besoin de rebase / squash, ce qui le rend vraiment rapide. Et le temps nécessaire n'est pas pertinent pour la taille du référentiel ou la profondeur de l'historique.

Variation: nouveau référentiel à partir d'un modèle de projet

Cela est utile pour créer le "commit initial" dans un nouveau projet en utilisant un autre référentiel comme modèle / archétype / graine / squelette. Par exemple:

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")

Cela évite d'ajouter le référentiel de modèles en tant que distant ( originou autre) et réduit l'historique du référentiel de modèles dans votre validation initiale.

ryenus
la source
6
La syntaxe de la révision de git (HEAD ^ {tree}) est expliquée ici au cas où quelqu'un d'autre se demanderait: jk.gs/gitrevisions.html
Colin Bowern
1
Cela réinitialise-t-il le référentiel local et distant, ou seulement l'un d'entre eux?
aleclarson
4
@aleclarson, cela ne réinitialise que la branche actuelle dans le référentiel local, utilisée git push -fpour la propagation.
ryenus
2
J'ai trouvé cette réponse en cherchant un moyen de démarrer un nouveau projet à partir d'un référentiel de modèles de projet qui n'impliquait pas git clone. Si vous ajoutez --hardà git resetet basculez HEADavec FETCH_HEADdans le, git commit-treevous pouvez créer un commit initial après avoir récupéré le référentiel de modèle. J'ai édité la réponse avec une section à la fin démontrant cela.
toolbear
4
Vous pouvez vous débarrasser de cette "mise en garde" mais en utilisant simplement${1?Please enter a message}
Elliot Cameron
172

Si tout ce que vous voulez faire, c'est écraser toutes vos validations vers la racine, alors tout

git rebase --interactive --root

peut fonctionner, il n'est pas pratique pour un grand nombre de validations (par exemple, des centaines de validations), car l'opération de rebase s'exécutera probablement très lentement pour générer la liste de validation de l'éditeur de rebase interactif, ainsi que pour exécuter la rebase elle-même.

Voici deux solutions plus rapides et plus efficaces lorsque vous écrasez un grand nombre de commits:

Solution alternative n ° 1: les branches orphelines

Vous pouvez simplement créer une nouvelle branche orpheline à la pointe (c'est-à-dire la validation la plus récente) de votre branche actuelle. Cette branche orpheline forme la validation racine initiale d'une arborescence d'historique de validation entièrement nouvelle et distincte, ce qui équivaut à écraser toutes vos validations:

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

Documentation:

Solution alternative # 2: réinitialisation logicielle

Une autre solution efficace consiste à simplement utiliser une réinitialisation mixte ou logicielle du commit racine <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

Documentation:


la source
22
Solution alternative # 1: branches orphelines - roches!
Thomas
8
Solution alternative # 1 FTW. Juste pour ajouter, si vous voulez pousser vos modifications vers la télécommande, faites git push origin master --force.
Eddy Verbruggen
1
ne devrait pas oubliergit push --force
NecipAllef
Ne faites pas de branche orpheline sur le code (c'est-à-dire ne faites pas la solution alternative n ° 1 ci-dessus) si vous êtes sur le point de pousser vers une requête pull Github ouverte !!! Github fermera votre PR car la tête actuelle n'est pas une descendante de la tête stockée .
Andrew Mackie
La solution alternative n ° 1 évite également les conflits de fusion qui peuvent survenir lors d'un
squashing
52
echo "message" | git commit-tree HEAD^{tree}

Cela va créer un commit orphelin avec l'arborescence de HEAD, et afficher son nom (SHA-1) sur stdout. Ensuite, réinitialisez simplement votre succursale.

git reset SHA-1
kusma
la source
27
git reset $(git commit-tree HEAD^{tree} -m "commit message")le rendrait plus facile.
ryenus
4
^ CECI! - devrait être une réponse. Je ne sais pas vraiment si c'était l'intention de l'auteur, mais c'était la mienne (avait besoin d'un dépôt vierge avec un seul commit, et cela fait le travail).
chesterbr
@ryenus, votre solution a fait exactement ce que je cherchais. Si vous ajoutez votre commentaire comme réponse, je l'accepterai.
tldr
2
La raison pour laquelle je n'ai pas suggéré moi-même la variante de sous-shell, c'est qu'elle ne fonctionnera pas sur cmd.exe dans Windows.
kusma
Dans les invites Windows, vous devrez peut-être citer le dernier paramètre: echo "message" | git commit-tree "HEAD^{tree}"
Bernard
41

Voici comment j'ai fini par le faire, juste au cas où cela fonctionnerait pour quelqu'un d'autre:

N'oubliez pas qu'il y a toujours un risque à faire des choses comme ça, et ce n'est jamais une mauvaise idée de créer une branche de sauvegarde avant de commencer.

Commencez par vous connecter

git log --oneline

Faites défiler jusqu'au premier commit, copiez SHA

git reset --soft <#sha#>

Remplacer <#sha#>avec le SHA copié à partir du journal

git status

Assurez-vous que tout est vert, sinon exécutez git add -A

git commit --amend

Modifier toutes les modifications actuelles au premier commit actuel

Maintenant, forcez à pousser cette branche et elle écrasera ce qui s'y trouve.

Logan
la source
1
Excellente option! Vraiment simple.
deux fois le
1
C'est plus que fantastique! Merci!
Matt Komarnicki
1
Notez que cela semble en fait laisser l'histoire autour. Vous l'avez rendu orphelin, mais il est toujours là.
Brad
Réponse très utile ... mais sachez qu'après la commande amend vous vous retrouverez dans l'éditeur vim avec sa syntaxe spéciale. ESC, ENTER,: x est votre ami.
Erich Kuester
Excellente option!
danivicario
36

J'ai lu quelque chose sur l'utilisation des greffes, mais je n'ai jamais beaucoup étudié la question.

Quoi qu'il en soit, vous pouvez écraser manuellement les 2 derniers commits avec quelque chose comme ceci:

git reset HEAD~1
git add -A
git commit --amend
R. Martinho Fernandes
la source
4
C'est en fait la réponse que je cherchais, j'aurais aimé qu'elle soit acceptée!
Jay
C'est une réponse tellement incroyable
Maître Yoda
36

La façon la plus simple est d'utiliser la commande 'plomberie' update-ref pour supprimer la branche actuelle.

Vous ne pouvez pas utiliser git branch -D car il a une soupape de sécurité pour vous empêcher de supprimer la branche actuelle.

Cela vous remet dans l'état «commit initial» où vous pouvez commencer avec un nouveau commit initial.

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

En une ligne de 6 mots

git checkout --orphan new_root_branch  &&  git commit
kyb
la source
@AlexanderMills, vous devriez lire au git help checkoutsujet--orphan
KYB
pouvez-vous créer un lien vers les documents pour que tous ceux qui le lisent n'aient pas à le rechercher manuellement?
Alexander Mills
1
facile. le voicigit help checkout --orphan
kyb
8

créer une sauvegarde

git branch backup

réinitialiser au commit spécifié

git reset --soft <#root>

puis ajoutez tous les fichiers à la mise en scène

git add .

valider sans mettre à jour le message

git commit --amend --no-edit

pousser une nouvelle branche avec des engagements écrasés pour repo

git push -f
David Morton
la source
Cela préservera-t-il les messages de validation précédents?
not2qubit
1
@ not2qubit no cela ne conservera pas les messages de commit précédents, au lieu de commit # 1, commit # 2, commit # 3, vous obtiendrez toutes les modifications de ces commits dans un seul commit # 1. Le <root>commit # 1 sera le commit sur lequel vous avez réinitialisé. git commit --amend --no-editva valider toutes les modifications apportées à la validation actuelle, <root>sans avoir à modifier le message de validation.
David Morton
5

Pour écraser à l'aide de greffes

Ajouter un fichier .git/info/grafts , mettez là le hash de commit que vous voulez devenir votre root

git log va maintenant commencer à partir de ce commit

Pour en faire une course «réelle» git filter-branch

dimus
la source
1

Cette réponse améliore un couple ci-dessus (veuillez les voter), en supposant qu'en plus de créer le seul commit (pas de parents, pas d'historique), vous souhaitez également conserver toutes les données de commit de ce commit:

  • Auteur (nom et email)
  • Date de création
  • Commiter (nom et email)
  • Date d'engagement
  • Message de validation du journal

Bien sûr, le commit-SHA du nouveau / single commit changera, car il représente un nouvel (non) historique, devenant un parentless / root-commit.

Cela peut être fait en lisant git loget en définissant certaines variables pour git commit-tree. En supposant que vous souhaitez créer un seul commit à partir masterd'une nouvelle branche one-commit, en conservant les données de commit ci-dessus:

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
la source
1

Pour ce faire, vous pouvez réinitialiser votre référentiel git local au premier hashtag de validation, de sorte que toutes vos modifications après cette validation ne seront pas mises en scène, puis vous pourrez valider avec l'option --amend.

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

Modifiez ensuite le premier nom de validation si nécessaire et enregistrez le fichier.

T.EL MOUSSAOUI
la source
1

Pour moi, cela a fonctionné comme ceci: j'ai eu 4 commits au total et j'ai utilisé un rebase interactif:

git rebase -i HEAD~3

Le tout premier commit reste et j'ai pris 3 derniers commits.

Dans le cas où vous êtes coincé dans l'éditeur qui apparaît ensuite, vous voyez smth comme:

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

Vous devez d'abord valider et écraser les autres dessus. Ce que vous devriez avoir, c'est:

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

Pour cela, utilisez la touche INSERT pour changer le mode 'insert' et 'edit'.

Pour enregistrer et quitter l'éditeur, utilisez :wq. Si votre curseur se trouve entre ces lignes de validation ou ailleurs, appuyez sur ÉCHAP et réessayez.

En conséquence, j'ai eu deux commits: le tout premier qui est resté et le second avec le message "Ceci est une combinaison de 3 commits.".

Vérifiez les détails ici: https://makandracards.com/makandra/527-squash-several-git-commits-into-a-single-commit

Vlad
la source
0

Je le fais habituellement comme ceci:

  • Assurez-vous que tout est validé et notez le dernier ID de validation en cas de problème, ou créez une branche distincte comme sauvegarde

  • Exécutez git reset --soft `git rev-list --max-parents=0 --abbrev-commit HEAD`pour réinitialiser votre tête au premier commit, mais laissez votre index inchangé. Toutes les modifications depuis le premier commit apparaîtront maintenant prêtes à être validées.

  • Exécutez git commit --amend -m "initial commit"pour modifier votre commit sur le premier commit et modifier le message de commit, ou si vous souhaitez conserver le message de commit existant, vous pouvez exécutergit commit --amend --no-edit

  • Courez git push -fpour forcer à pousser vos changements

Kent Munthe Caspersen
la source