web-dev-qa-db-fra.com

Squash mon dernier X commet ensemble en utilisant Git

Comment puis-je réduire mes derniers commits X en un seul en utilisant Git?

2727
markdorison

Utilisez git rebase -i <after-this-commit> et remplacez "pick" lors de la deuxième validation et des suivantes par "squash" ou "fixup", comme décrit dans le manuel .

Dans cet exemple, <after-this-commit> est soit le hachage SHA1, soit l'emplacement relatif à partir de HEAD de la branche en cours à partir de laquelle les validations sont analysées pour la commande rebase. Par exemple, si l'utilisateur souhaite visualiser 5 validations à partir du HEAD actuel, la commande est git rebase -i HEAD~5

1541
Anomie

Vous pouvez le faire assez facilement sans git rebase ni git merge --squash. Dans cet exemple, nous allons écraser les 3 derniers commits.

Si vous voulez é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 similaire à la liste d'instructions pick/squash/squash /…/squash git rebase -i), vous devez extraire ces messages et transmettez-les à git commit:

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

Ces deux méthodes réduisent les trois derniers commits en un seul nouveau de la même manière. La réinitialisation logicielle re-pointe simplement HEAD sur le dernier commit que vous ne voulez pas écraser. Ni l’index ni l’arbre de travail ne sont touchés par la réinitialisation logicielle, ce qui laisse l’indice dans l’état souhaité pour votre nouveau commit (c’est-à-dire qu’il contient déjà tous les changements des commit que vous êtes sur le point de «jeter»).

2987
Chris Johnsen

Vous pouvez utiliser git merge --squash pour 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: Commencez par vous assurer que votre travail est bien validé. Vérifiez que git status est propre (puisque git reset --hard éliminera les modifications planifiées et non planifiées).

Ensuite:

# 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 pour git merge décrit l’option --squash de manière plus détaillée.


Update: le seul avantage réel de cette méthode par rapport au git reset --soft HEAD~12 && git commit plus simple suggéré par Chris Johnsen dans sa réponse est que le message de validation est pré-rempli avec chaque message de validation que vous écrasez.

633
Mark Longair

Basé sur la réponse de Chris Johnsen ,

Ajouter un alias "squash" global de 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 en utilisant l'invite de commande 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 ~/.gitconfig devrait 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

... Ce qui écrase automatiquement les derniers N commits inclusivement.

Remarque: le message de validation résultant est une combinaison de tous les validations écrasées, dans l'ordre. Si vous n'êtes pas satisfait de cela, vous pouvez toujours git commit --amend pour le modifier manuellement. (Ou modifiez l'alias pour qu'il corresponde à vos goûts.)

110
EthanB

Merci à cet article de blog pratique J'ai découvert 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 vous trouvez sur une branche locale sans information de suivi/référentiel à distance.

La commande ouvrira l'éditeur de rebase interactif qui vous permettra ensuite de réorganiser, squash, 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 HEAD~3 lors de l'exécution de la commande git rebase -i HEAD~3.

La validation la plus récente, HEAD, est affichée en premier sur la ligne 1. Les lignes commençant par # sont des commentaires/de la documentation.

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

Je préfère utiliser la commande fixup car ceci "écrase" les modifications du commit dans le commit de la ligne ci-dessus et supprime le message du commit. 

Comme le commit de la ligne 1 est HEAD, dans la plupart des cas, vous le laisseriez comme pick. Vous ne pouvez pas utiliser squash ou fixup car il n’existe aucun autre commit pour écraser le commit.

 interactive rebase editor

79
br3nt

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

  1. Ouvrir le menu contextuel TortoiseGit
  2. Sélectionnez Show Log
  3. Marquez les commits pertinents dans la vue du journal
  4. Sélectionnez Combine to one commit dans le menu contextuel

 Combine commits

Cette fonction exécute automatiquement toutes les étapes nécessaires à un seul git.

52
Matthias M

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 (ces commits n'étaient donc pas encore poussés vers la télécommande).

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

git reset Origin/dev
git add --all
git commit -m 'my commit message'
39
trudolf

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

 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,

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

Pour plus de détails, cliquez sur Lien

Bonne chance!!

32
Jakir Hosen Khan

1) Identifier le hash court de commit

# 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 peut également être utilisé pour obtenir un hachage court.

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

# git rebase -i deab3412 

3) Ceci ouvre un éditeur nano pour la fusion. Et on dirait ci-dessous

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

4) Renommez le mot pick en squash qui est présent avant abcd1234. Après avoir renommé, cela devrait être comme ci-dessous.

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

5) Enregistrez et fermez l'éditeur nano. Appuyez sur ctrl + o et appuyez sur Enter pour enregistrer. Et puis appuyez sur ctrl + x pour quitter l'éditeur.

6) Ensuite, l'éditeur nano s'ouvre à nouveau pour mettre à jour les commentaires, le cas échéant, mettez-le à jour.

7) Maintenant que sa compression est réussie, vous pouvez le vérifier en consultant les journaux.

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

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

# git Push Origin +master

Note: Ceci est basé sur l'utilisation de git sur ubuntu Shell. Si vous utilisez différents systèmes d'exploitation (Windows ou Mac), les commandes ci-dessus sont identiques, à l'exception de editor. Vous pourriez obtenir un éditeur différent.

29
rashok

Anomies answer est bon, mais je ne me sentais pas en sécurité à ce sujet, alors j'ai décidé d'ajouter quelques captures d'écran.

Étape 0: journal git

Voyez où vous en êtes avec git log. Le plus important est de trouver le hash de commit du premier commit que vous ne voulez pas écraser. Donc seulement le:

 enter image description here

É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 comme dans git log. Dans mon cas, je veux:

 enter image description here

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

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

 enter image description here

C'est tout. Une fois que vous avez sauvegardé ceci (:wq), vous avez terminé. Regardez-le avec git log.

24
Martin Thoma

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

  1. Commander le golden repo

    git checkout golden_repo_name
    
  2. Créez une nouvelle branche à partir de celle-ci (golden repo) comme suit

    git checkout -b dev-branch
    
  3. Squash fusionner avec votre branche locale que vous avez déjà

    git merge --squash feature-branch
    
  4. Commit tes modifications (ce sera le seul commit qui ira dans dev-branch)

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

    git Push Origin dev-branch
    
23
Sandesh Kumar

C'est super-duper kludgy, mais d'une manière cool, alors 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: fournissez un nouvel "éditeur" pour git qui, si le nom du fichier à modifier est git-rebase-todo (l'invite de rebase interactive), modifie tout sauf le premier "choix" en "squash", et génère par ailleurs vim - invité à éditer le message de validation écrasé, vous obtenez vim. (Et évidemment, je réduisais les cinq derniers commits sur branch foo, mais vous pouvez changer cela à votre guise.)

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

15
Cascabel

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

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

Cela ouvrira l’éditeur de texte et vous devrez changer le "pick" devant chaque commit avec "squash" si vous souhaitez que ces commits soient fusionnés. Par exemple, si vous souhaitez fusionner tous les commits en un seul, le "choix" est le premier que vous avez effectué et tous les futurs (placés en dessous du premier) doivent être réglés sur "squash". Si vous utilisez vim, utilisez : x en mode insertion pour enregistrer et quitter l'éditeur.

Puis pour continuer la rebase:

git rebase --continue

Pour plus d'informations et pour savoir comment réécrire votre historique de validation, voir cet article utile

14
aabiro

Ce qui peut être vraiment pratique:
Trouvez le hachage de commit que vous voulez écraser, dites d43e15.

Maintenant utiliser

git reset d43e15
git commit -am 'new commit name'
14
Ariel Gabizon

Si vous voulez écraser tous les commit en un seul commit (par exemple, lors de la publication d'un projet pour la première fois), essayez

git checkout --Orphan <new-branch>
git commit
13
William Denniss

Je pense que le moyen le plus simple de procéder consiste à créer une nouvelle branche à partir de master et à effectuer une fusion 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.

10
user1376350

Pour écraser les 10 derniers commits en 1 seul:

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

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

git Push -f
7
Ayan
git rebase -i HEAD^^

où le nombre de ^ est X

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

4
Yoaz Menda

Je trouve une solution plus générique qui ne consiste pas à spécifier les "N" commits, mais plutôt la branche/l'ID de validation sur laquelle vous souhaitez écraser. Cela 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 commence 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 pousserai comme message "final" pour la fonctionnalité dans le référentiel public.) Je veux faire est git squash master retour 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 va vider l'historique écrasé avant de le faire - cela vous donne une chance de récupérer en récupérant un ancien ID de commit sur la console si vous voulez revenir. (Les utilisateurs de Solaris notent qu'ils utilisent l'option GNU sed -i. Les utilisateurs de Mac et de Linux devraient pouvoir s'en charger.)

4
Ethan

En question, la signification de "dernier" pourrait être ambiguë. 

par exemple, git log --graph génère les éléments suivants (simplifiés):

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

Les dernières validations par heure sont H0, fusion, B0. Pour les écraser, vous devrez rebaser votre branche fusionnée sur le commit H1.

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

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

rebase -i HEAD~2

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

pick B1
pick B0
pick H0

Mettez squash au lieu de pick sur H0:

pick B1
pick B0
s H0

Après l'enregistrement et la sortie, rebase appliquera les validations à tour de rôle après H1. Cela signifie qu'il vous demandera de résoudre à nouveau les conflits (où HEAD sera au début H1 puis accumule les commits au fur et à mesure qu'ils sont appliqués).

Une fois l’opération terminée, vous pouvez choisir un message pour H0 et B0 écrasés:

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

P.S. Si vous ne faites que réinitialiser BO: .__ (par exemple, en utilisant reset --mixed, expliqué 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'

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

4
littlewhywhat

En plus d’autres excellentes réponses, je voudrais ajouter comment git rebase -i m’embrouille toujours avec l’ordre de livraison - du plus ancien au plus récent ou inversement? Voici donc mon flux de travail:

  1. git rebase -i HEAD~[N], où N est le nombre de commits que je souhaite rejoindre, à partir du plus récent. Donc, git rebase -i HEAD~5 signifierait "écraser les 5 derniers commits dans un nouveau";
  2. l'éditeur apparaît et affiche la liste des commits que je souhaite fusionner. Maintenant, ils sont affichés dans l'ordre inverse: l'ancien commit est au dessus. Marquez comme "squash" ou "s" tous les commits qui s'y trouvent sauf le premier/plus 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: changez-le selon vos besoins, sauvegardez et fermez. Squash terminé!

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

3
Ignorant

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
3
Zsolt Z.

⚠️ AVERTISSEMENT: "Mon dernier X commet" pourrait ê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 https://github.com/fleetwood-mac/band-history repository, vous avez ouvert une demande d'extraction visant à fusionner le commit Bill Clinton dans le commit original (MASTER) Fleetwood Mac .

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

Quatre commits:

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

En pensant que personne ne se soucierait jamais de lire l'historique complet du référentiel. (Il existe actuellement un référentiel, cliquez sur le lien ci-dessus!) Vous décidez d'écraser ces commits. Donc, vous allez courir git reset --soft HEAD~4 && git commit. Ensuite, vous git Push --forcez-le sur GitHub pour nettoyer votre PR.

Et qu'est-ce qui se passe? Vous venez de faire un engagement unique 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 log ne correspond pas à ce que vous voyez sur GitHub.

???? MORALE DE L'HISTOIRE

  1. Trouvez les fichiers exacts que vous voulez et git checkout eux
  2. Recherchez le précédent engagement que vous souhaitez conserver dans l'historique et git reset --soft qui
  3. Créez un git commit qui se déforme directement du du au au
3
William Entriken

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

  1. de nombreux commits locaux, mélangés avec plusieurs fusions FROM maître,
  2. enfin un Push to remote,
  3. Relations publiques et fusion à maîtriser par le relecteur . (Oui, il serait plus facile pour le développeur de merge --squash après le PR, mais l'équipe a pensé que cela ralentirait le processus.)

Je n'ai pas vu un flux de travail comme celui-ci sur cette page. (C'est peut-être mon cas.) Si je comprends bien rebase, plusieurs fusions nécessiteraient plusieurs résolutions de conflit . 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 commettez beaucoup localement, fusionnez régulièrement le maître
  5. git checkout new-branch
  6. git merge --squash new-branch-temp // met toutes les modifications 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.
3
allenjom

Je découvre d’abord le nombre de commits entre ma branche principale et la branche principale actuelle en

git checkout master
git rev-list master.. --count

Ensuite, je crée une autre branche basée sur my-feature, conservez my-feature branch intacte.

Enfin, je cours

git checkout my-feature
git checkout -b my-rebased-feature
git checkout master
git checkout my-rebased-feature
git rebase master
git rebase head^x -i
// fixup/pick/rewrite
git Push Origin my-rebased-feature -f // force, if my-rebased-feature was ever pushed, otherwise no need for -f flag
// make a PR with clean history, delete both my-feature and my-rebased-feature after merge

J'espère que ça aide, merci.

2
Alan Dong

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.

 enter image description here

2
arush436

Une ligne simple qui fonctionne toujours, étant donné que vous êtes actuellement sur la branche que vous souhaitez écraser, maître est la branche dont elle provient, et le dernier commit contient le message de validation et l'auteur que vous souhaitez utiliser:

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

Si vous utilisez GitUp , sélectionnez le commit que vous souhaitez fusionner avec son parent et appuyez sur S. Vous devez le faire une fois pour chaque commit, mais c'est beaucoup plus simple que de proposer la bonne incantation en ligne de commande. Surtout si c'est quelque chose que vous ne faites que de temps en temps.

1
SSteve

si vous voulez par exemple réduire les 3 derniers commits en un seul commit dans une branche (référentiel distant), 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 de branche) --force
1
Albert Ruelan

Ajoutez simplement cette fonction bash à votre bash de fichier .zshrc.

# Squash last X commits with a Commit message.
# Usage: squash X 'COMMIT_MSG'
# where X= Number of last commits.
# where COMMIT_MSG= New commit msg.
function squash() {
    if [ -z "${1}" -o -z "${2}" ]; then
        echo "Usage: \`squash X COMMIT_MSG\`"
        echo "X= Number of last commits."
        echo "COMMIT_MSG= New commit msg."
        return 1
    fi

    git reset --soft HEAD~"$1"
    git add . && git ci -m "$2" # With 100 emoji
    git Push --force
}

Alors viens courir

squash X 'New Commit Message'

Et tu as fini.

0
Ahmad Awais