Comment écraser deux commits non consécutifs?

196

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

A -> B -> C -> D

Ensuite, je me rends compte qu'il Dcontient un correctif qui dépend d'un nouveau code ajouté A, et que ces commits vont ensemble. Comment écraser A& Densemble et partir B& Cseul?

Nik Reiman
la source

Réponses:

279

Vous pouvez exécuter git rebase --interactiveet réorganiser D avant B et écraser D en A.

Git ouvrira un éditeur et vous verrez un fichier comme celui-ci, ex: git rebase --interactive HEAD~4

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 modifiez 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 ensuite. Lorsque vous ne souhaitez pas conserver le message de validation de D, au lieu de squash, vous utilisez le fixupmot - clé. Pour en savoir plus fixup, vous pouvez consulter la git rebasedocumentation ou consulter cette question qui a de bonnes réponses.

Rudi
la source
5
Au départ, je l'ai lu comme "rebase D sur A, écrasement D en A, puis rebase B sur DA". Il n'est pas clair d'après la réponse que cela peut être fait en réorganisant les lignes dans un éditeur de texte.
Victor Sergienko
3
Si votre branche est locale, vous obtiendrez une There is no tracking information for the current brancherreur lors du rebasage. Dans ce cas , vous devez spécifier le nombre de commits que vous voulez travailler avec, comme ceci: git rebase -i HEAD~4. Voyez cette réponse .
johndodo
1
J'utilise le mode interactif (git rebase -i) depuis des années, je viens de réaliser qu'il peut être réorganisé . Merci 🤟🏻
CalvinChe
44

Remarque: vous ne devez en aucun cas modifier les commits qui ont été transférés vers un autre dépôt, sauf si vous en 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

Type i(Mettre VIM en mode insertion)

Modifiez la liste pour qu'elle ressemble à ceci (vous n'avez pas à supprimer ou à inclure le message de validation). Ne vous trompez pas squash! :

pick C commit_message_for_C
pick B commit_message_for_B
pick A commit_message_for_A
squash D

Tapez Escensuite 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

Type 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 Aet D:

new_commit_message_for_A_and_D

Tapez EscalorsZZ

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. S'engage Aet Dne font plus partie de votre histoire mais ne sont pas partis. Vous pouvez toujours les récupérer à ce stade et pendant un certain temps git rebase --hard D( git rebase --harddétruira toutes les modifications locales! ).

Nate
la source
3

Pour ceux qui utilisent SourceTree :

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

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

Le rebase interactif fonctionne bien jusqu'à ce que vous ayez une grande branche de fonctionnalités avec 20-30 commits et / ou quelques fusions du maître ou / et corrigeant les conflits pendant que vous vous engagiez dans votre branche. Même avec trouver mes engagements à travers l'histoire et remplacer pickpar squashne fonctionne pas ici. Alors je cherchais un autre moyen et j'ai trouvé cet article . J'ai fait mes modifications pour travailler ceci sur une branche séparée:

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 pull request avec environ 30 commits avec 2-3 fusions de master + résolution des conflits. Et après cela, j'ai obtenu des relations publiques claires avec un seul commit.

PS voici un script bash pour effectuer ces étapes en mode automatique.

Oleksiy Guzenko
la source
La première solution de cet article est vraiment sympa, merci pour le lien
Sweat à
-1

$ git checkout master

$ git log --oneline

D
C
B
A

$ git rebase --onto HEAD ^^^ HEAD ^

$ git log --oneline

D
A
Coton
la source
1
Je pense que tu veux dire --oneline? Et il semble que vous ayez abandonné Cet B, ce n'est pas ce que le PO avait l'intention.
bstpierre
Cela n'a pas fonctionné pour moi. Il a déplacé à la fois ma tête et mon maître vers A, mais n'a pas fusionné D dans A ( git show A) et D, C et B ont été perdus dans mon ref-log. J'ai dû git rebase Drentrer.
Nate