Que signifie squash commits dans git?

117

Que signifie Squashing commits dans git. Comment squash commits dans Github?

Je suis nouveau sur Git et j'ai demandé à être affecté à un nouveau bogue dans coala-analyzer. J'ai corrigé le bug, et maintenant on m'a demandé d'écraser mes commits. Comment fait-on ça?

Lakshmanan Meiyappan
la source
Hey @Lakshman, n'hésitez pas à accepter la réponse votée la plus élevée. Cela répond correctement à votre question.
Mostafiz Rahman

Réponses:

180

Vous pouvez considérer Git comme une base de données avancée d'instantanés de votre (vos) répertoire (s) de travail.

Une fonctionnalité très intéressante de Git est la possibilité de réécrire l'historique des commits.
La raison principale pour cela est qu'une grande partie de cette histoire n'est pertinente que pour le développeur qui l'a générée, elle doit donc être simplifiée, ou rendue plus agréable, avant de la soumettre à un référentiel partagé.

Écraser un commit signifie, d'un point de vue idiomatique, déplacer les changements introduits dans ledit commit dans son parent afin que vous vous retrouviez avec un commit au lieu de deux (ou plus).
Si vous répétez ce processus plusieurs fois, vous pouvez réduire n commit à un seul.

Visuellement, si vous avez commencé votre travail au commit marqué Start , vous voulez ceci

Git commet l'écrasement

Vous remarquerez peut-être que le nouveau commit a une nuance de bleu légèrement plus foncée. C'est intentionnel.

Dans Git, l'écrasement est réalisé avec une Rebase , d'une forme spéciale appelée Interactive Rebase .
En simplifiant lorsque vous rebasez un ensemble de commits dans une branche B , vous appliquez toutes les modifications introduites par ces commits comme elles ont été effectuées, en commençant par B au lieu de leur ancêtre d'origine.

Un indice visuel

entrez la description de l'image ici

Notez à nouveau les différentes nuances de bleu.

Un rebase interactif vous permet de choisir comment les commits doivent être rebasés. Si vous exécutez cette commande:

 git rebase -i branch

Vous vous retrouveriez avec un fichier qui répertorie les commits qui seront rebasés

 pick ae3...
 pick ef6...
 pick 1e0...
 pick 341...

Je n'ai pas nommé les commits, mais ces quatre sont destinés à être les commits du début à la tête

La bonne chose à propos de cette liste est qu'elle est modifiable .
Vous pouvez omettre les validations ou les écraser .
Tout ce que vous avez à faire est de changer le premier mot en squash .

 pick ae3...
 squash ef6...
 squash 1e0...
 squash 341...

Si vous fermez l'éditeur et qu'aucun conflit de fusion n'est trouvé, vous vous retrouvez avec cet historique:

entrez la description de l'image ici

Dans votre cas, vous ne voulez pas rebaser dans une autre branche, mais plutôt dans un commit précédent.
Afin de transformer l'historique comme indiqué dans le tout premier exemple, vous devez exécuter quelque chose comme

git rebase -i HEAD~4

changez les "commandes" en squash pour tous les commits sauf le premier, puis fermez votre éditeur.


Remarque sur la modification de l'historique

Dans Git, les commits ne sont jamais modifiés. Ils peuvent être élagués, rendus inaccessibles, clonés mais pas modifiés.
Lorsque vous rebase, vous créez en fait de nouveaux commits.
Les anciens ne sont plus joignables par aucune référence, ils ne sont donc pas affichés dans l'historique mais ils sont toujours là!

Voici ce que vous obtenez réellement pour un rebase:

entrez la description de l'image ici

Si vous les avez déjà poussés quelque part, réécrire l'historique fera en fait une branche!

Margaret Bloom
la source
Bien - qu'arrive-t-il aux commentaires de commit d'origine - sont-ils combinés en un seul gros commentaire de commit ou sont-ils perdus?
Paul R
From man git rebase: Le message de commit suggéré pour le commit plié est la concaténation des messages de commit du premier commit et de ceux avec la commande "squash"
Margaret Bloom
1
C'est la meilleure explication que j'ai vue du rebasage, merci.
Kerry Jones
À propos de l'écrasement dans votre première image: Pouvez-vous faire un exemple où HEAD a un répertoire de travail différent avant (bleu clair) et après (bleu foncé) l'écrasement? Dans tous les exemples, j'ai essayé le squash qui avait l'air de supprimer les trois commits entre START et HEAD.
actual_panda
@actual_panda Je ne suis pas sûr de suivre. HEAD est une référence à un commit. Valide les deltas de magasin. Le répertoire de travail est une propriété du référentiel dans son ensemble, cela dépend du commit que vous avez extrait. En général, deux refs (comme HEAD et START) donnent toujours deux workdirs différents lors de l'extraction. Si vous rebasez squash sur la même branche, l'effet est de "perdre" les commits intermédiaires mais en réalité, git en a fait un nouveau avec tous les deltas. git diffpeut vous aider à montrer ce qui s'est passé.
Margaret Bloom le
22

La commande rebase a des options impressionnantes disponibles dans son --interactive(ou -i) mode, et l'une des plus largement utilisées est la possibilité d'écraser les commits. Cela permet de prendre des commits plus petits et de les combiner en plus grands, ce qui pourrait être utile si vous terminez la journée de travail ou si vous souhaitez simplement regrouper vos modifications différemment. Nous allons voir comment vous pouvez le faire facilement.

Un mot d'avertissement: ne faites cela que sur les commits qui n'ont pas été poussés dans un référentiel externe. Si d'autres ont basé le travail sur les commits que vous allez supprimer, de nombreux conflits peuvent survenir. Ne réécrivez pas votre historique s'il a été partagé avec d'autres.

Disons que vous venez de faire quelques petits commits et que vous voulez en faire un plus grand. L'historique de notre référentiel ressemble actuellement à ceci:

entrez la description de l'image ici

Les 4 derniers commits seraient beaucoup plus heureux s'ils étaient regroupés, alors faisons simplement cela grâce au rebasage interactif:

$ git rebase -i HEAD~4

pick 01d1124 Adding license
pick 6340aaa Moving license into its own file
pick ebfd367 Jekyll has become self-aware.
pick 30e0ccb Changed the tagline in the binary, too.

# Rebase 60709da..30e0ccb onto 60709da
#
# Commands:
#  p, pick = use commit
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

Donc, quelques choses se sont produites ici. Tout d'abord, j'ai dit à Git que je voulais rebase en utilisant les quatre derniers commits d'où HEAD est avec HEAD ~ 4. Git m'a maintenant mis dans un éditeur avec le texte ci-dessus, et une petite explication de ce qui peut être fait. Vous avez beaucoup d'options à votre disposition à partir de cet écran, mais pour le moment, nous allons simplement tout écraser en un seul commit. Donc, changer les quatre premières lignes du fichier en ceci fera l'affaire:

pick 01d1124 Adding license
squash 6340aaa Moving license into its own file
squash ebfd367 Jekyll has become self-aware.
squash 30e0ccb Changed the tagline in the binary, too.

Fondamentalement, cela indique à Git de combiner les quatre commits dans le premier commit de la liste. Une fois que cela est fait et enregistré, un autre éditeur apparaît avec ce qui suit:

# This is a combination of 4 commits.
# The first commit's message is:
Adding license

# This is the 2nd commit message:

Moving license into its own file

# This is the 3rd commit message:

Jekyll has become self-aware.

# This is the 4th commit message:

Changed the tagline in the binary, too.

    # Please enter the commit message for your changes. Lines starting
    # with '#' will be ignored, and an empty message aborts the commit.
    # Explicit paths specified without -i nor -o; assuming --only paths...
    # Not currently on any branch.
    # Changes to be committed:
    #   (use "git reset HEAD <file>..." to unstage)
    #
    #   new file:   LICENSE
    #   modified:   README.textile
    #   modified:   Rakefile
    #   modified:   bin/jekyll
    #

Puisque nous combinons autant de commits, Git vous permet de modifier le message du nouveau commit en fonction du reste des commits impliqués dans le processus. Modifiez le message comme bon vous semble, puis enregistrez et quittez. Une fois que cela est fait, vos commits ont été écrasés avec succès!

Created commit 0fc4eea: Creating license file, and making jekyll self-aware.
 4 files changed, 27 insertions(+), 30 deletions(-)
  create mode 100644 LICENSE
    Successfully rebased and updated refs/heads/master.

Et si nous regardons à nouveau l'histoire… entrez la description de l'image ici

Donc, cela a été relativement indolore jusqu'à présent. Si vous rencontrez des conflits pendant le rebase, ils sont généralement assez faciles à résoudre et Git vous guide le plus possible. L'essentiel est de résoudre le conflit en question, git addle fichier, puis de git rebase --continuereprendre le processus. Bien sûr, faire un git rebase --abortvous ramènera à votre état antérieur si vous le souhaitez. Si pour une raison quelconque vous avez perdu un commit dans le rebase, vous pouvez utiliser le reflog pour le récupérer.

Les détails peuvent être trouvés sur ce lien .

0xAliHn
la source
3
Je vous remercie! Incase quiconque est aussi mal à l'aise avec VIM que moi ... lorsque vous arrivez au point où vous entrez des actions à modifier, appuyez sur «i» pour passer en mode édition. Lorsque vous avez terminé avec les modifications, appuyez sur échapper puis tapez ": wq" et cela vous fera avancer. (Mac)
Farasi78
+1 pour la prudence et le contexte du moment où faire cela D'après mon expérience personnelle, c'est bien si vous travaillez seul sur une branche de fonctionnalités et que vous avez une tonne de commits triviaux comme "readme mis à jour", "fait quelque chose dont personne ne se soucie" et que vous êtes prêt à fusionner la branche, peut-être Eh bien, écrasez tout cela en un seul commit Personne ne voudra revenir à votre "fait quelque chose dont personne ne se soucie" et cela pollue l'historique des commits
Adam Hughes