Squash my last X commits together using Git


3592

Comment puis-je écraser mes dernières validations X ensemble en une seule validation en utilisant Git?




2
@matt TortoiseGit est votre outil. Il fournit une seule fonction "Combiner pour un commit" qui appellera automatiquement toutes les étapes en arrière-plan. Malheureusement uniquement disponible pour Windows. Voir ma réponse ci-dessous.
Matthias M

1
Pour squashing jusqu'à LE premier commit voir ceci - stackoverflow.com/questions/1657017/…
goelakash

1
après le squash, il faut faire forcer push stackoverflow.com/questions/10298291/…
vikramvi

Réponses:


2092

Utilisez git rebase -i <after-this-commit>et remplacez "pick" sur le second commit et les suivants par "squash" ou "fixup", comme décrit dans le manuel .

Dans cet exemple, il <after-this-commit>s'agit du hachage SHA1 ou de l'emplacement relatif de la tête de la branche actuelle à partir de laquelle les validations sont analysées pour la commande de rebase. Par exemple, si l'utilisateur souhaite afficher 5 validations à partir de la tête courante dans le passé, la commande est git rebase -i HEAD~5.


260
Cela, je pense, répond un peu mieux à cette question stackoverflow.com/a/5201642/295797
Roy Truelove

92
Qu'entend-on par <after-this-commit>?
2540625

43
<after-this-commit>est commit X + 1, c'est-à-dire le parent du commit le plus ancien que vous souhaitez écraser.
joozek

340
J'ai trouvé cette réponse trop concise pour la comprendre sans ambiguïté. Un exemple aurait aidé.
Ian Ollmann

54
La différence entre cette rebase -iapproche et reset --softis, rebase -ime permet de conserver l'auteur du commit, tout en reset --softme permettant de recommencer. Parfois, j'ai besoin d'écraser les commits de demandes de tirage tout en conservant les informations sur l'auteur. Parfois, je dois réinitialiser le soft sur mes propres commits. Upvotes aux deux grandes réponses de toute façon.
zionyx

3834

Vous pouvez le faire assez facilement sans git rebaseou git merge --squash. Dans cet exemple, nous écraserons les 3 derniers commits.

Si vous souhaitez écrire le nouveau message de commit à partir de zéro, cela suffit:

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

Si vous souhaitez commencer à éditer le nouveau message de validation avec une concaténation des messages de validation existants (c'est-à-dire semblable à ce qu'une git rebase -iliste d'instructions pick / squash / squash /… / squash vous lancerait), alors vous devez extraire ces messages et passer à git commit:

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

Ces deux méthodes écrasent les trois dernières validations en une seule nouvelle validation de la même manière. La réinitialisation logicielle redirige simplement HEAD vers le dernier commit que vous ne voulez pas écraser. Ni l'index ni l'arborescence de travail ne sont touchés par la réinitialisation logicielle, laissant l'index dans l'état souhaité pour votre nouveau commit (c'est-à-dire qu'il a déjà toutes les modifications des commits que vous êtes sur le point de «jeter»).


163
Ha! J'aime cette méthode. C'est celui qui se rapproche de l'esprit du problème. Dommage que cela demande tant de vaudou. Quelque chose comme ça devrait être ajouté à l'une des commandes de base. Peut git rebase --squash-recent- être , ou même git commit --amend-many.
Adrian Ratnapala

13
@ABB: Si votre branche a un ensemble «en amont», alors vous pourrez peut-être utiliser branch@{upstream}(ou juste @{upstream}pour la branche actuelle; dans les deux cas, la dernière partie peut être abrégée en @{u}; voir gitrevisions ). Cela peut différer de votre «dernier commit poussé» (par exemple, si quelqu'un d'autre a poussé quelque chose qui a construit au-dessus de votre dernier push et que vous l'avez récupéré), mais il semble que cela pourrait être proche de ce que vous voulez.
Chris Johnsen

104
Ce genre de choses m'obligeait à le faire, push -fmais sinon c'était charmant, merci.
2rs2ts

39
@ 2rs2ts git push -f son dangereux. Veillez à ne supprimer que les commits locaux. Ne touchez jamais aux commits poussés!
Matthias M

20
Je dois aussi utiliser git push --forceensuite pour que ça prenne le commit
Zach Saucier

740

Vous pouvez utiliser git merge --squashpour cela, qui est légèrement plus élégant que git rebase -i. Supposons que vous soyez maître et que vous vouliez écraser les 12 derniers commits en un seul.

AVERTISSEMENT: assurez-vous d'abord de valider votre travail - vérifiez qu'il git statusest propre (car git reset --hardil supprimera les modifications par étapes et non par étapes)

Alors:

# 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 documentation degit merge décrit l' --squashoption plus en détail.


Mise à jour: le seul véritable avantage de cette méthode par rapport à la plus simple git reset --soft HEAD~12 && git commitsuggérée par Chris Johnsen dans sa réponse est que vous obtenez le message de validation pré-rempli avec chaque message de validation que vous écrasez.


16
Vous dites que c'est plus «élégant» que git rebase -i, mais vous n'en donnez pas la raison. Provisoirement -1, car il me semble qu'en fait le contraire est vrai et c'est un hack; N'exécutez-vous pas plus de commandes que nécessaire juste pour forcer git mergeà faire l'une des choses git rebasespécifiquement conçues pour?
Mark Amery

78
@Mark Amery: Il y a plusieurs raisons pour lesquelles j'ai dit que c'était plus élégant. Par exemple, cela n'implique pas de générer inutilement un éditeur, puis de rechercher et de remplacer une chaîne dans le fichier "à faire". L'utilisation git merge --squashest également plus facile à utiliser dans un script. Essentiellement, le raisonnement était que vous n'avez pas besoin du tout d '"interactivité" git rebase -ipour cela.
Mark Longair

12
Un autre avantage est qu'il git merge --squashest moins susceptible de produire des conflits de fusion face aux mouvements / suppressions / renommages par rapport au rebasage, surtout si vous fusionnez à partir d'une branche locale. (Avertissement: basé sur une seule expérience, corrigez-moi si ce n'est pas vrai dans le cas général!)
Cheezmeister

2
Je suis toujours très réticent en ce qui concerne les réinitialisations matérielles - j'utiliserais une balise temporelle au lieu d'être HEAD@{1}juste du bon côté, par exemple lorsque votre flux de travail est interrompu pendant une heure par une panne de courant, etc.
Tobias Kienzler

8
@BT: Vous avez détruit votre commit? :( Je ne suis pas sûr de ce que vous entendez par là. Tout ce que vous avez commis, vous pourrez facilement revenir à partir du reflog de git. Si vous aviez du travail non engagé, mais que les fichiers étaient organisés, vous devriez toujours pouvoir obtenir leur contenu en arrière, bien que ce sera plus de travail . Si votre travail n'a même pas été mis en scène, cependant, je crains qu'il n'y ait pas grand-chose qui puisse être fait; c'est pourquoi la réponse dit d'avance: "Vérifiez d'abord que le statut de git est propre (depuis la réinitialisation de git --hard supprimera les modifications échelonnées et non échelonnées) ".
Mark Longair

218

Je recommande d'éviter git resetautant que possible - en particulier pour les novices Git. À moins que vous n'ayez vraiment besoin d'automatiser un processus basé sur un certain nombre de validations, il existe une méthode moins exotique ...

  1. Mettez les commits à écraser sur une branche qui fonctionne (s'ils ne le sont pas déjà) - utilisez gitk pour cela
  2. Consultez la branche cible (par exemple, «maître»)
  3. git merge --squash (working branch name)
  4. git commit

Le message de validation sera prérempli en fonction du squash.


4
C'est la méthode la plus sûre: pas de reset soft / hard (!!), ni reflog utilisé!
TeChn4K

14
Ce serait formidable si vous développiez sur (1).
Adam

2
@Adam: Fondamentalement, cela signifie utiliser l'interface graphique de gitkpour étiqueter la ligne de code que vous écrasez et étiqueter également la base sur laquelle écraser. Dans le cas normal, ces deux étiquettes existent déjà, donc l'étape (1) peut être ignorée.
nobar

3
Notez que cette méthode ne marque pas la branche de travail comme étant complètement fusionnée, donc la supprimer nécessite de forcer la suppression. :(
Kyrstellaine

2
Pour (1), j'ai trouvé git branch your-feature && git reset --hard HEAD~Nle moyen le plus pratique. Cependant, cela implique à nouveau git reset, ce que cette réponse a tenté d'éviter.
Eis

134

Sur la base de la réponse de Chris Johnsen ,

Ajouter un alias global "squash" depuis bash: (ou Git Bash sous 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'

... ou à l'aide de l'invite de commandes 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"


Votre ~/.gitconfigdevrait maintenant contenir cet alias:

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


Usage:

git squash N

... qui écrase automatiquement les derniers Ncommits, inclus.

Remarque: Le message de validation résultant est une combinaison de toutes les validations écrasées, dans l'ordre. Si vous n'êtes pas satisfait de cela, vous pouvez toujours git commit --amendle modifier manuellement. (Ou, modifiez l'alias en fonction de vos goûts.)


6
Intéressant, mais je préfère taper le message de validation écrasé moi-même, comme un résumé descriptif de mes multiples validations, plutôt que de le saisir automatiquement pour moi. Je préfère donc spécifier git squash -m "New summary."et Ndéterminer automatiquement le nombre de commits non poussés.
Acumenus

1
@ABB, Cela ressemble à une question distincte. (Je ne pense pas que ce soit exactement ce que le PO demandait; je n'en ai jamais ressenti le besoin dans mon flux de travail de git squash.)
EthanB

3
C'est assez doux. Personnellement, j'aimerais une version qui utilise le message de validation de la première des validations écrasées. Serait bon pour des choses comme les réglages d'espaces.
funroll

@funroll Accepté. Laisser tomber le dernier msg de validation est un besoin super commun pour moi. On devrait pouvoir imaginer ça ...
Steve Clay

2
@ABB vous pouvez utiliser git commit --amendpour modifier davantage le message, mais cet alias vous permet de prendre un bon départ sur ce qui devrait être dans le message de validation.
dashesy

131

Grâce à ce billet de blog pratique, j'ai trouvé que vous pouvez utiliser cette commande pour écraser les 3 derniers commits:

git rebase -i HEAD~3

C'est pratique car cela fonctionne même lorsque vous êtes sur une succursale locale sans informations de suivi / repo à distance.

La commande ouvrira l'éditeur de rebase interactif qui vous permettra ensuite de réorganiser, écraser, reformuler, etc. comme d'habitude.


Utilisation de l'éditeur de rebase interactif:

L'éditeur de rebase interactif affiche les trois derniers commits. Cette contrainte a été déterminée par l' HEAD~3exécution de la commande git rebase -i HEAD~3.

Le commit le plus récent,, HEADest affiché en premier sur la ligne 1. Les lignes commençant par a #sont des commentaires / documentation.

La documentation affichée est assez claire. Sur une ligne donnée, vous pouvez changer la commande de picken une commande de votre choix.

Je préfère utiliser la commande fixupcar cela "écrase" les modifications du commit dans le commit sur la ligne ci-dessus et rejette le message du commit.

Comme le commit sur la ligne 1 est HEAD, dans la plupart des cas, vous laisseriez cela comme pick. Vous ne pouvez pas utiliser squashou fixupcar il n'y a pas d'autre commit pour écraser le commit.

Vous pouvez également modifier l'ordre des validations. Cela vous permet d'annuler ou de corriger les commits qui ne sont pas adjacents chronologiquement.

éditeur de rebase interactif


Un exemple pratique au quotidien

J'ai récemment validé une nouvelle fonctionnalité. Depuis lors, j'ai commis deux corrections de bugs. Mais maintenant, j'ai découvert un bug (ou peut-être juste une faute d'orthographe) dans la nouvelle fonctionnalité que j'ai validée. Comme c'est ennuyeux! Je ne veux pas qu'un nouveau commit pollue mon historique de commit!

La première chose que je fais est de corriger l'erreur et de faire un nouveau commit avec le commentaire squash this into my new feature! .

Je lance ensuite git logou gitket j'obtiens le commit SHA de la nouvelle fonctionnalité (dans ce cas1ff9460 ).

Ensuite, je lance l'éditeur de rebase interactif avec git rebase -i 1ff9460~. le~ après la validation SHA indique l'éditeur d'inclure cette validation dans l'éditeur.

Ensuite, je déplace la validation contenant le correctif ( fe7f1e0) sous la validation de la fonctionnalité et je change pickenfixup .

À la fermeture de l'éditeur, le correctif sera écrasé dans la validation de la fonctionnalité et mon historique de validation sera joli et propre!

Cela fonctionne bien lorsque toutes les validations sont locales, mais si vous essayez de modifier les validations déjà poussées vers la télécommande, vous pouvez vraiment causer des problèmes aux autres développeurs qui ont extrait la même branche!

entrez la description de l'image ici


5
devez-vous choisir celui du haut et écraser le reste? Vous devez modifier votre réponse pour expliquer plus en détail comment utiliser l'éditeur de rebase interactif
Kolob Canyon

2
Oui, laissez picken ligne 1. Si vous choisissez squashou fixuppour le commit sur la ligne 1, git affichera un message disant "erreur: ne peut pas" réparer "sans un commit précédent". Ensuite, il vous donnera la possibilité de le corriger: "Vous pouvez résoudre ce problème avec 'git rebase --edit-todo' puis exécuter 'git rebase --continue'." ou vous pouvez simplement abandonner et recommencer: "Ou vous pouvez abandonner le rebase avec 'git rebase --abort'.".
br3nt

56

Si vous utilisez TortoiseGit, vous pouvez la fonction Combine to one commit:

  1. Ouvrir le menu contextuel de TortoiseGit
  2. Sélectionner Show Log
  3. Marquer les validations pertinentes dans la vue du journal
  4. Sélectionnez Combine to one commitdans le menu contextuel

Combiner les validations

Cette fonction exécute automatiquement toutes les étapes git simples nécessaires. Malheureusement, uniquement disponible pour Windows.


Pour autant que je sache, cela ne fonctionnera pas pour les validations de fusion.
Thorkil Holm-Jacobsen

1
Bien qu'il ne soit pas commenté par d'autres, cela fonctionne même pour les commits qui ne sont pas à la tête. Par exemple, mon besoin était d'écraser certains commits WIP que j'ai faits avec une description plus saine avant de pousser. Fonctionne magnifiquement. Bien sûr, j'espère toujours pouvoir apprendre à le faire par des commandes.
Charles Roberto Canato

55

Pour ce faire, vous pouvez utiliser la commande git suivante.

 git rebase -i HEAD~n

n (= 4 ici) est le numéro du dernier commit. Ensuite, vous avez les options suivantes,

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

Mettre à jour comme ci-dessous pickun commit et squashles autres dans le plus récent,

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

Pour plus de détails, cliquez sur le lien


48

Basé sur cet article j'ai trouvé cette méthode plus facile pour mon cas d'utilisation.

Ma branche «dev» devançait «origin / dev» de 96 commits (donc ces commits n'étaient pas encore poussés vers la télécommande).

Je voulais écraser ces commits en un seul avant de pousser le changement. Je préfère réinitialiser la branche à l'état «origine / dev» (cela laissera toutes les modifications des 96 validations non mises en scène), puis valider les modifications à la fois:

git reset origin/dev
git add --all
git commit -m 'my commit message'

1
Juste ce dont j'avais besoin. Squash down commits from my feature branch, and then I git cherry pick that commit in my master.
David Victor

1
Cela n'écrase pas les commits précédents!
IgorGanapolsky

pourriez-vous élaborer un peu plus @igorGanapolsky?
trudolf

3
@trudolf Ce n'est pas vraiment du squash (choisir des commits individuels pour le squash). Il s'agit davantage de valider toutes vos modifications à la fois.
IgorGanapolsky

15
oui, donc il écrase tous vos commits en un seul. toutes nos félicitations!
trudolf

45

Dans la branche sur laquelle vous souhaitez combiner les validations, exécutez:

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

exemple:

git rebase -i HEAD~1

Cela ouvrira l'éditeur de texte et vous devez changer le 'pick' devant chaque commit avec 'squash' si vous souhaitez que ces commits soient fusionnés ensemble. De la documentation:

p, pick = utiliser commit

s, squash = utiliser la validation, mais fusionner avec la validation précédente

Par exemple, si vous cherchez à fusionner toutes les validations en une seule, la «sélection» est la première validation que vous avez effectuée et toutes les futures (placées sous la première) doivent être définies sur «squash». Si vous utilisez vim, utilisez : x en mode insertion pour enregistrer et quitter l'éditeur.

Puis pour continuer le rebase:

git rebase --continue

Pour en savoir plus à ce sujet et sur d'autres façons de réécrire votre historique de validation, consultez cet article utile


2
Veuillez également expliquer ce que font --continueet vim :x.
not2qubit

Le rebase se produira par blocs au fur et à mesure des validations sur votre branche, après que vous ayez git addla bonne configuration dans vos fichiers que vous utilisez git rebase --continuepour passer à la prochaine validation et commencer à fusionner. :xest une commande qui enregistrera les modifications du fichier lors de l'utilisation de vim voir ceci
aabiro

32

Anomies réponse est bonne, mais je ne me sentais pas en sécurité à ce sujet, j'ai donc décidé d'ajouter quelques captures d'écran.

Étape 0: journal git

Voyez où vous en êtes git log. Plus important encore, trouvez le hachage de validation du premier commit que vous ne voulez pas écraser. Donc seulement:

entrez la description de l'image ici

Étape 1: git rebase

Exécutez git rebase -i [your hash], dans mon cas:

$ git rebase -i 2d23ea524936e612fae1ac63c95b705db44d937d

Étape 2: choisissez / écrasez ce que vous voulez

Dans mon cas, je veux tout écraser sur le commit qui était le premier dans le temps. L'ordre est du premier au dernier, donc exactement dans l'autre sens que dans git log. Dans mon cas, je veux:

entrez la description de l'image ici

Étape 3: ajuster le (s) message (s)

Si vous avez sélectionné un seul commit et écrasé le reste, vous pouvez ajuster un message de commit:

entrez la description de l'image ici

C'est ça. Une fois que vous avez enregistré ceci ( :wq), vous avez terminé. Jetez-y un œil avec git log.


2
serait bien de voir le résultat final, par exemple,git log
Timothy LJ Stewart

2
Non merci. C'est exactement comme ça que j'ai perdu mes engagements.
Axalix

@Axalix Avez-vous supprimé toutes vos lignes? Voilà comment vous perdez vos commits.
a3y3

31

Procédure 1

1) Identifier le hachage court de validation

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

Ici même git log --oneline pouvez également utiliser pour obtenir un hachage court.

2) Si vous voulez écraser (fusionner) les deux derniers commit

# git rebase -i deab3412 

3) Cela ouvre un nanoéditeur pour la fusion. Et ça ressemble à ci-dessous

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

4) renommer le mot pickpour squashlequel est présent avant abcd1234. Après avoir renommé, il devrait être comme ci-dessous.

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

5) Maintenant, enregistrez et fermez l' nanoéditeur. Appuyez sur ctrl + oet appuyez sur Enterpour enregistrer. Et puis appuyez surctrl + x pour quitter l'éditeur.

6) Ensuite nano éditeur s'ouvre à nouveau pour mettre à jour les commentaires, si nécessaire, mettez-le à jour.

7) Maintenant, il est écrasé avec succès, vous pouvez le vérifier en vérifiant les journaux.

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

8) Maintenant, poussez pour repo. Remarque pour ajouter un +signe avant le nom de la branche. Cela signifie une poussée forcée.

# git push origin +master

Remarque: Ceci est basé sur l'utilisation de git sur ubuntushell. Si vous utilisez différents systèmes d'exploitation ( Windowsou Mac), les commandes ci-dessus sont les mêmes, sauf l'éditeur. Vous pourriez obtenir un éditeur différent.

Procédure 2

  1. Ajoutez d'abord les fichiers requis pour la validation
git add <files>
  1. Ensuite, validez en utilisant l' --fixupoption et le OLDCOMMITdevrait être sur lequel nous devons fusionner (écraser) ce commit.
git commit --fixup=OLDCOMMIT

Maintenant, cela crée un nouveau commit au-dessus de HEAD avec fixup1 <OLDCOMMIT_MSG>.

  1. Exécutez ensuite la commande ci-dessous pour fusionner (écraser) le nouveau commit dans le fichier OLDCOMMIT.
git rebase --interactive --autosquash OLDCOMMIT^

Ici ^signifie le précédent commit OLDCOMMIT. Cette rebasecommande ouvre une fenêtre interactive sur un éditeur (vim ou nano) sur lequel nous n'avons pas besoin de faire quoi que ce soit juste enregistrer et sortir est suffisant. Parce que l'option transmise à ceci déplacera automatiquement le dernier commit à côté de l'ancien commit et changera l'opération en fixup(équivalent à squash). Ensuite, le rebase continue et se termine.

Procédure 3

  1. Si besoin d'ajouter de nouvelles modifications au dernier commit, des moyens --amendpeuvent être utilisés avec 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
    ....  

--amendFusionne ici les nouvelles modifications au dernier commit cdababcdet génère un nouvel ID de commit1d4ab2e1

Conclusion

  • L'avantage de la 1ère procédure est d'écraser plusieurs commits et de réorganiser. Mais cette procédure sera difficile si nous devons fusionner un correctif avec un très ancien commit.
  • Ainsi, la 2ème procédure permet de fusionner facilement le commit en un très ancien commit.
  • Et la 3ème procédure est utile dans un cas pour écraser de nouveaux changements au dernier commit.

Il ne met à jour que les deux derniers commits même si je remets à zéro un ID de commit au 6ème dernier commit, je ne sais pas pourquoi
Carlos Liu

Même vous pouvez réorganiser l'ordre de validation. Ça fonctionne bien.
rashok

29

Pour écraser les 10 derniers commits en 1 seul commit:

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

Si vous souhaitez également mettre à jour la branche distante avec la validation écrasée:

git push -f

--force est dangereux lorsque plusieurs personnes travaillent sur une branche partagée car il met à jour aveuglément la télécommande avec votre copie locale. --force-with-lease aurait pu être mieux car il s'assure que remote n'a aucun commit des autres depuis votre dernière récupération.
bharath

27

Si vous êtes sur une branche distante (appelée feature-branch) clonée à partir d'un référentiel doré ( golden_repo_name), voici la technique pour écraser vos commits en une seule:

  1. Commander le repo doré

    git checkout golden_repo_name
    
  2. Créez-en une nouvelle branche (repo doré) comme suit

    git checkout -b dev-branch
    
  3. Fusion de squash avec votre succursale locale que vous avez déjà

    git merge --squash feature-branch
    
  4. Validez vos modifications (ce sera la seule validation qui va dans dev-branch)

    git commit -m "My feature complete"
    
  5. Poussez la branche vers votre référentiel local

    git push origin dev-branch
    

Comme je viens d'écraser ~ 100 commits (pour synchroniser les branches svn via git-svn), c'est beaucoup plus rapide que le rebasage interactif!
sage

1
En lisant, je vois le commentaire de @ Chris, ce que je faisais (rebase --soft ...) - dommage que stackoverflow ne mette plus la réponse avec des centaines de votes positifs en haut ...
sage

1
d'accord avec vous @sage, espérons qu'ils pourraient le faire à l'avenir
Sandesh Kumar

C'est le bon chemin. L'approche de rebase est bonne, mais ne devrait être utilisée que pour le squash comme solution de dernier recours.
Axalix

20

Ce qui peut être vraiment pratique:
trouvez le hachage de validation que vous souhaitez écraser, par exempled43e15 .

Maintenant, utilisez

git reset d43e15
git commit -am 'new commit name'

2
Cette. Pourquoi plus de gens n'utilisent-ils pas cela? C'est beaucoup plus rapide que de rebaser et d'écraser les commits individuels.
a3y3

17

C'est super bavard, mais d'une manière plutôt cool, donc je vais juste le jeter dans le 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

Traduction: fournir un nouvel "éditeur" pour git qui, si le nom de fichier à éditer est git-rebase-todo (l'invite de rebase interactive) change tout sauf le premier "pick" en "squash", et génère autrement vim - de sorte que lorsque vous y êtes invité pour modifier le message de validation écrasé, vous obtenez vim. (Et évidemment, j'écrasais les cinq derniers commits sur branch foo, mais vous pouvez changer cela comme bon vous semble.)

Je ferais probablement ce que Mark Longair a suggéré .


7
+1: c'est amusant et instructif, en ce qu'il n'était pas du tout évident pour moi que vous puissiez mettre quelque chose de plus complexe que le nom d'un programme dans la variable d'environnement GIT_EDITOR.
Mark Longair

16

Si vous voulez écraser chaque commit en un seul commit (par exemple lorsque vous publiez un projet publiquement pour la première fois), essayez:

git checkout --orphan <new-branch>
git commit

15

2020 Solution simple sans rebase:

git reset --soft HEAD~2

git commit -m "new commit message"

git push --force

2 signifie que les deux derniers commits seront écrasés. Vous pouvez le remplacer par n'importe quel numéro


14

Je pense que la façon la plus simple de le faire est de créer une nouvelle branche hors de master et de faire une fusion - squash de la branche de fonctionnalité.

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

Ensuite, vous avez toutes les modifications prêtes à être validées.


12

One-liner simple qui fonctionne toujours, étant donné que vous êtes actuellement sur la branche que vous souhaitez écraser, master est la branche dont il est issu et le dernier commit contient le message de commit et l'auteur que vous souhaitez utiliser:

git reset --soft $(git merge-base HEAD master) && git commit --reuse-message=HEAD@{1}

4
J'ai été absolument livide de frustration à propos du squashing des commits et à quel point il est stupidement compliqué - il suffit d'utiliser le dernier message et de les écraser tous en un seul commit! Pourquoi est-ce si difficile ???? Cette doublure fait ça pour moi. Merci du fond du cœur en colère.
Locane

12

si par exemple vous voulez écraser les 3 dernières validations en une seule validation dans une branche (référentiel distant) dans par exemple: https://bitbucket.org

Ce que j'ai fait c'est

  1. git reset --soft Head ~ 3 &&
  2. git commit
  3. git push origin (nom_branche) --force

3
Soyez prudent, car si vous utilisez la force, il n'y a aucun moyen de récupérer les commits précédents depuis que vous les avez supprimés
Albert Ruelan

12

⚠️ AVERTISSEMENT: «Mon dernier X valide» peut être ambigu.

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

Dans cette histoire très abrégée du référentiel https://github.com/fleetwood-mac/band-history , vous avez ouvert une pull request pour fusionner le Bill Clinton commit dans l'original (MASTER commit Fleetwood Mac ).

Vous avez ouvert une demande de pull et sur GitHub vous voyez ceci:

Quatre commits:

  • Ajouter Danny Kirwan
  • Ajouter Christine Perfect
  • LA1974
  • Bill Clinton

Penser que personne ne se soucierait jamais de lire l'historique complet du référentiel. (Il y a en fait un dépôt, cliquez sur le lien ci-dessus!) Vous décidez d'écraser ces commits. Alors tu vas courir git reset --soft HEAD~4 && git commit. Alors vousgit push --force sur GitHub pour nettoyer votre PR.

Et que se passe-t-il? Vous venez de faire un seul commit qui va de Fritz à Bill Clinton. Parce que vous avez oublié qu'hier vous travailliez sur la version Buckingham Nicks de ce projet. Et git logne correspond pas à ce que vous voyez sur GitHub.

🐻 MORALE DE L'HISTOIRE

  1. Trouver les fichiers exacts que vous souhaitez obtenir à , etgit checkout les
  2. Trouvez l'engagement préalable exact que vous souhaitez conserver dans l'historique, et git reset --soft que
  3. Faire un git commitque funes directement à partir du de la à

1
C'est 100% la façon la plus simple de le faire. Si votre HEAD actuel est dans le bon état, vous pouvez ignorer # 1.
Stan

C'est le seul moyen que je connaisse qui permette de réécrire le premier historique de commit.
Vadorequest

9

Si vous ne vous souciez pas des messages de validation des validations intermédiaires, vous pouvez utiliser

git reset --mixed <commit-hash-into-which-you-want-to-squash>
git commit -a --amend

7

Si vous travaillez avec GitLab, vous pouvez simplement cliquer sur l'option Squash dans la demande de fusion comme indiqué ci-dessous. Le message de validation sera le titre de la demande de fusion.

entrez la description de l'image ici


6
git rebase -i HEAD^^

où le nombre de ^ est X

(dans ce cas, écrasez les deux derniers commits)


6

En plus d'autres excellentes réponses, j'aimerais ajouter comment git rebase -ime confond toujours avec l'ordre de validation - du plus ancien au plus récent ou vice versa? Voici donc mon workflow:

  1. git rebase -i HEAD~[N], où N est le nombre de validations que je veux rejoindre, en commençant par la plus récente . Cela git rebase -i HEAD~5signifierait donc "écraser les 5 derniers commits en un nouveau";
  2. l'éditeur apparaît, affichant la liste des validations que je veux fusionner. Maintenant, ils sont affichés dans l'ordre inverse : l'ancien commit est en haut. Marquer comme "squash" ou "s" tous les commits dedans sauf le premier / ancien : il sera utilisé comme point de départ. Enregistrez et fermez l'éditeur;
  3. l'éditeur réapparaît avec un message par défaut pour le nouveau commit: modifiez-le selon vos besoins, enregistrez et fermez. Squash terminé!

Sources et lectures supplémentaires: # 1 , # 2 .


6

Qu'en est-il d'une réponse à la question liée à un flux de travail comme celui-ci?

  1. de nombreuses validations locales, mélangées à plusieurs fusions de master ,
  2. enfin une poussée à distance,
  3. PR et fusionner TO master par le réviseur . (Oui, il serait plus facile pour le développeur merge --squashaprès le PR, mais l'équipe a pensé que cela ralentirait le processus.)

Je n'ai pas vu de flux de travail comme celui-ci sur cette page. (Cela peut être mes yeux.) Si je comprends rebasebien, plusieurs fusions nécessiteraient plusieurs résolutions de conflits . Je ne veux même pas y penser!

Donc, cela semble fonctionner pour nous.

  1. git pull master
  2. git checkout -b new-branch
  3. git checkout -b new-branch-temp
  4. éditez et engagez beaucoup en local, fusionnez le master régulièrement
  5. git checkout new-branch
  6. git merge --squash new-branch-temp // met tous les changements en scène
  7. git commit 'one message to rule them all'
  8. git push
  9. Le réviseur fait des relations publiques et fusionne pour maîtriser.

De nombreuses opinions, j'aime votre approche. C'est très pratique et rapide
Artem Solovev

5

Je trouve qu'une solution plus générique ne consiste pas à spécifier les «N» commits, mais plutôt la branche / commit-id que vous souhaitez écraser au-dessus. C'est moins sujet aux erreurs que de compter les validations jusqu'à une validation spécifique - spécifiez simplement la balise directement, ou si vous voulez vraiment compter, vous pouvez spécifier HEAD ~ N.

Dans mon flux de travail, je démarre une branche, et mon premier commit sur cette branche résume l'objectif (c'est-à-dire que c'est généralement ce que je vais pousser comme message «final» pour la fonctionnalité dans le référentiel public.) Donc, quand j'ai terminé, tout Je veux faire est git squash master revenir au premier message et puis je suis prêt à pousser.

J'utilise l'alias:

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

Cela videra l'historique écrasé avant de le faire - cela vous donne une chance de récupérer en récupérant un ancien ID de validation de la console si vous souhaitez revenir. (Les utilisateurs de Solaris notent qu'il utilise l' -ioption sed GNU , les utilisateurs Mac et Linux devraient être d'accord avec cela.)


J'ai essayé l'alias mais je ne sais pas si les remplacements sed ont un effet. Que devraient-ils faire?
raine

Le premier sed décharge simplement l'historique sur la console. Le deuxième sed remplace tous les 'pick' par 'f' (fixup) et réécrit le fichier éditeur sur place (l'option -i). Le second fait donc tout le travail.
Ethan

Vous avez raison, compter N-nombre de validations spécifiques est très sujet aux erreurs. Cela m'a foutu plusieurs fois et perdu des heures à essayer de défaire le rebase.
IgorGanapolsky

Salut Ethan, je voudrais savoir si ce workflow cachera d'éventuels conflits lors de la fusion. Veuillez donc considérer si vous avez deux branches maître et esclave. Si l'esclave a un conflit avec le maître et que nous utilisons git squash masterlorsque nous sommes contrôlés sur l'esclave. que se passera-t-il? cacherons-nous le conflit?
Sergio Bilello

@Sergio Il s'agit d'un cas de réécriture de l'historique, donc vous aurez probablement des conflits si vous écrasez les commits qui ont déjà été poussés, puis essayez de fusionner / rebaser la version écrasée. (Certains cas triviaux pourraient s'en tirer.)
Ethan

5

En question, il pourrait être ambigu ce que l'on entend par «dernier».

par exemple, git log --graphsort les éléments suivants (simplifiés):

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

Les dernières validations par le temps sont H0, merge, B0. Pour les écraser, vous devrez rebaser votre branche fusionnée lors de la validation H1.

Le problème est que H0 contient H1 et H2 (et généralement plus de validations avant fusion et après branchement) tandis que B0 n'en contient pas. Vous devez donc gérer les modifications de H0, fusion, H1, H2, B0 au moins.

Il est possible d'utiliser rebase mais de manière différente, puis dans d'autres réponses mentionnées:

rebase -i HEAD~2

Cela vous montrera les options de choix (comme mentionné dans d'autres réponses):

pick B1
pick B0
pick H0

Mettez la courge au lieu de choisir H0:

pick B1
pick B0
s H0

Après la sauvegarde et la sortie, le rebase appliquera les commits à son tour après H1. Cela signifie qu'il vous demandera de résoudre à nouveau les conflits (où HEAD sera H1 dans un premier temps, puis accumulera les validations au fur et à mesure de leur application).

Une fois le rebase terminé, vous pouvez choisir le message pour H0 et B0 écrasés:

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

PS Si vous faites juste une réinitialisation sur BO: (par exemple, l'utilisation reset --mixedest expliquée plus en détail ici https://stackoverflow.com/a/18690845/2405850 ):

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

puis vous vous écrasez dans les changements B0 de H0, H1, H2 (perdre complètement valide les changements après la ramification et avant la fusion.


4

1) git reset --soft HEAD ~ n

n - Numéro du commit, besoin de squash

2) git commit -m "nouveau message de commit"

3) git push origin nom_branche --force

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.