web-dev-qa-db-fra.com

Comment écraser deux commits non consécutifs?

Je suis un peu nouveau pour toute la fonctionnalité de rebasage dans git. Disons que j'ai fait les commits suivants:

A -> B -> C -> D

Ensuite, je me rends compte que D contient un correctif qui dépend du nouveau code ajouté dans A, et que ces commits appartiennent ensemble. Comment puis-je écraser A & D ensemble et laisser B & C tranquille?

153
Nik Reiman

Tu peux courir git rebase --interactive et réorganiser D avant B et écraser D en A.

Git ouvrira un éditeur et vous verrez un fichier comme celui-ci:

pick aaaaaaa Commit A
pick bbbbbbb Commit B
pick ccccccc Commit C
pick ddddddd Commit D

# Rebase aaaaaaa..ddddddd onto 1234567 (4 command(s))
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using Shell
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

Maintenant, vous changez le fichier qui ressemble à ceci:

pick aaaaaaa Commit A
squash ddddddd Commit D
pick bbbbbbb Commit B
pick ccccccc Commit C

Et git va maintenant fusionner les changements de A et D en un seul commit, et mettre B et C après. Lorsque vous ne souhaitez pas conserver le message de validation de D, vous pouvez également utiliser le mot clé "fix".

204
Rudi

Remarque: vous ne devez pas modifier les commits qui ont été poussés vers un autre référentiel de quelque manière que ce soit sauf si vous connaissez les conséquences =.

git log --oneline -4

D commit_message_for_D
C commit_message_for_C
B commit_message_for_B
A commit_message_for_A

git rebase --interactive

pick D commit_message_for_D
pick C commit_message_for_C
pick B commit_message_for_B
pick A commit_message_for_A

Tapez i (Mettez VIM en mode insertion)

Modifiez la liste pour ressembler à ceci (vous n'avez pas besoin de supprimer ou d'inclure le message de validation). Ne pas mal orthographier squash !:

pick C commit_message_for_C
pick B commit_message_for_B
pick A commit_message_for_A
squash D

Type Esc puis ZZ (Enregistrer et quitter VIM)

# This is a combination of 2 commits.
# The first commit's message is:

commit_message_for_D

# This is the 2nd commit message:

commit_message_for_A

Tapez i

Remplacez le texte par ce à quoi vous voulez que le nouveau message de validation ressemble. Je recommande que ce soit une description des changements dans commit A et D:

new_commit_message_for_A_and_D

Type Esc alors ZZ

git log --oneline -4

E new_commit_message_for_A_and_D
C commit_message_for_C
B commit_message_for_B

git show E

(You should see a diff showing a combination of changes from A and D)

Vous avez maintenant créé un nouveau commit E. Les validations A et D ne sont plus dans votre historique mais n'ont pas disparu. Vous pouvez toujours les récupérer à ce stade et pendant un certain temps par git rebase --hard D (git rebase --hard détruira tous les changements locaux !).

41
Nate

Pour ceux qui utilisent SourceTree :

Assurez-vous que vous n'avez pas déjà poussé les commits.

  1. Référentiel> Rebase interactive ...
  2. Faites glisser D (le commit le plus récent) pour être directement au-dessus de A (le commit le plus ancien)
  3. Assurez-vous que la validation D est en surbrillance
  4. Cliquez sur Squash with previous
3
Adam Johns

Le rebase interactif fonctionne bien jusqu'à ce que vous ayez une grosse branche de fonctionnalités avec 20-30 commits et/ou quelques fusions de master ou/et de résolution des conflits pendant que vous vous engagiez dans votre branche. Même avec la recherche de mes validations dans l'historique et le remplacement de pick par squash ne fonctionne pas ici. Je cherchais donc un autre moyen et j'ai trouvé cela article . J'ai fait mes changements pour travailler cela sur une branche distincte:

git checkout master
git fetch
git pull
git merge branch-name
git reset Origin/master
git branch -D branch-name
git checkout -b branch-name
git add --all
#Do some commit
git Push -f --set-upstream Origin branch-name

Avant cela, j'ai reçu ma demande de tirage avec environ 30 commits avec 2-3 fusions de conflits maître + correction. Et après cela, j'ai obtenu un PR clair avec un seul commit.

P.S. voici bash script pour faire ces étapes en mode automatique.

1
Oleksiy Guzenko