Décomposer une validation précédente en plusieurs validations


1225

Sans créer une branche et faire un tas de travaux géniaux sur une nouvelle branche, est-il possible de diviser un seul commit en quelques commits différents après qu'il ait été validé dans le référentiel local?


36
Une bonne source pour apprendre à le faire est Pro Git §6.4 Git Tools - Rewriting History , dans la section "Splitting a Commit".

2
Les documents liés au commentaire ci-dessus sont excellents, mieux expliqués que les réponses ci-dessous.
Blaisorblade

2
Je suggère l'utilisation de cet alias stackoverflow.com/a/19267103/301717 . Il permet de fractionner un commit à l'aide degit autorebase split COMMIT_ID
Jérôme Pouiller

La chose la plus simple à faire sans rebase interactif est (probablement) de créer une nouvelle branche à partir de la validation avant celle que vous souhaitez fractionner, de sélectionner -n le commit, de réinitialiser, de masquer, de valider le déplacement du fichier, de réappliquer la cachette et valider les modifications, puis fusionner avec l'ancienne branche ou sélectionner les validations qui ont suivi. (Ensuite, changez l'ancien nom de la branche en tête actuelle.) (Il est probablement préférable de suivre les conseils des MBO et de faire un rebase interactif.) (Copié de la réponse de 2010 ci-dessous)
William Pursell

1
J'ai rencontré ce problème après avoir accidentellement écrasé deux commits lors d'un rebase dans un commit précédent. Ma façon de le réparer était à la caisse du Squashed commit, git reset HEAD~, git stashpuis git cherry-pickla première commettras dans le squash, puis git stash pop. Mon étui à cerise est assez spécifique ici, mais git stashil git stash popest assez pratique pour les autres.
SOFe

Réponses:


1802

git rebase -i le fera.

Tout d'abord, commencez par un répertoire de travail propre: git statusne devrait afficher aucune modification, suppression ou ajout en attente.

Maintenant, vous devez décider quel (s) commit (s) vous souhaitez fractionner.

A) Fractionnement du commit le plus récent

Pour séparer votre dernier commit, commencez par:

$ git reset HEAD~

Maintenant, validez les morceaux individuellement de la manière habituelle, en produisant autant de commits que vous le souhaitez.

B) Fractionner un commit plus loin

Cela nécessite un rebasage , c'est-à-dire une réécriture de l'historique. Pour trouver le bon commit, vous avez plusieurs choix:

  • S'il s'agissait de trois commits, alors

    $ git rebase -i HEAD~3
    

    3est le nombre de validations en retour.

  • S'il était plus loin dans l'arbre que vous ne le souhaitez, alors

    $ git rebase -i 123abcd~
    

    123abcdest le SHA1 du commit que vous souhaitez fractionner.

  • Si vous êtes sur une branche différente (par exemple, une branche d'entité) que vous prévoyez de fusionner en maître:

    $ git rebase -i master
    

Lorsque vous obtenez l'écran d'édition de rebase, recherchez le commit que vous souhaitez séparer. Au début de cette ligne, remplacez pickpar edit( epour faire court). Enregistrez le tampon et quittez. Rebase s'arrête maintenant juste après la validation que vous souhaitez modifier. Alors:

$ git reset HEAD~

Validez les pièces individuellement de la manière habituelle, en produisant autant de validations que nécessaire, puis

$ git rebase --continue

2
Merci pour cette réponse. Je voulais avoir des fichiers précédemment validés dans la zone de transfert, donc les instructions pour moi étaient un peu différentes. Avant que je pouvais git rebase --continue, je devais réellement git add (files to be added), git commitalors git stash(pour les fichiers restants). Après git rebase --continue, j'avais l'habitude git checkout stash .d'obtenir les fichiers restants
Eric Hu

18
La réponse de manojlds a en fait ce lien avec la documentation sur git-scm , qui explique également très clairement le processus de fractionnement des commits.

56
Vous voudrez également profiter de git add -ppour n'ajouter que des sections partielles de fichiers, éventuellement avec la epossibilité de modifier les différences pour ne valider qu'une partie du morceau. git stashest également utile si vous souhaitez poursuivre le travail mais le supprimer du commit actuel.
Craig Ringer

2
Si vous souhaitez fractionner et réorganiser les validations, ce que j'aime faire est de diviser d' abord , puis de réorganiser séparément à l'aide d'une autre git rebase -i HEAD^3commande. De cette façon, si la division se passe mal, vous n'avez pas à annuler autant de travail.
David M. Lloyd

4
@kralyk Les fichiers nouvellement validés dans HEAD seront conservés sur le disque après git reset HEAD~. Ils ne sont pas perdus.
Wayne Conrad

312

Depuis le manuel de git-rebase (section SPLITTING COMMITS)

En mode interactif, vous pouvez marquer les validations avec l'action "modifier". Cependant, cela ne signifie pas nécessairement que git rebase s'attend à ce que le résultat de cette modification soit exactement un commit. En effet, vous pouvez annuler la validation ou ajouter d'autres validations. Cela peut être utilisé pour diviser un commit en deux:

  • Démarrez un rebase interactif avec git rebase -i <commit>^, où <commit>est le commit que vous souhaitez fractionner. En fait, n'importe quelle plage de validation fera l'affaire, tant qu'elle contient cette validation.

  • Marquez le commit que vous souhaitez fractionner avec l'action "modifier".

  • Quand il s'agit de modifier ce commit, exécutez git reset HEAD^. L'effet est que la TETE est rembobinée d'une unité, et l'indice suit. Cependant, l'arbre de travail reste le même.

  • Ajoutez maintenant les modifications à l'index que vous souhaitez avoir dans le premier commit. Vous pouvez utiliser git add(éventuellement de manière interactive) ou git gui(ou les deux) pour ce faire.

  • Validez l'index actuel avec le message de validation approprié.

  • Répétez les deux dernières étapes jusqu'à ce que votre arbre de travail soit propre.

  • Continuez le rebasage avec git rebase --continue.


12
Sous Windows, vous devez utiliser ~au lieu de ^.
Kevin Kuszyk

13
Attention: avec cette approche, j'ai perdu le message de validation.
user420667

11
@ user420667 Oui, bien sûr. resetAprès tout, nous validons le commit - message inclus. La chose prudente à faire, si vous savez que vous allez fractionner un commit mais que vous souhaitez conserver tout ou partie de son message, est de prendre une copie de ce message. Donc, git showle commit avant rebaseing, ou si vous l'oubliez ou le préférez: revenez-y plus tard via le reflog. Rien de tout cela ne sera réellement "perdu" jusqu'à ce qu'il soit récupéré dans 2 semaines ou autre.
underscore_d

4
~et ce ^sont des choses différentes, même sur Windows. Vous voulez toujours le signe d'insertion ^, vous aurez donc juste besoin de l'échapper en fonction de votre coquille. Dans PowerShell, c'est HEAD`^. Avec cmd.exe, vous pouvez le doubler pour vous échapper comme HEAD^^. Dans la plupart des coquilles (toutes?), Vous pouvez entourer de guillemets comme "HEAD^".
AndrewF

7
Vous pouvez aussi le faire git commit --reuse-message=abcd123. L'option courte pour cela est -C.
j0057

41

Utilisez-le git rebase --interactivepour modifier ce commit, exécuter git reset HEAD~, puis git add -pen ajouter, puis faire un commit, puis en ajouter et faire un autre commit, autant de fois que vous le souhaitez. Lorsque vous avez terminé, exécutez git rebase --continueet vous aurez toutes les validations fractionnées plus tôt dans votre pile.

Important : Notez que vous pouvez jouer et apporter toutes les modifications que vous souhaitez, sans avoir à vous soucier de perdre les anciennes modifications, car vous pouvez toujours exécuter git reflogpour trouver le point dans votre projet qui contient les modifications que vous souhaitez, (appelons-le a8c4ab) , puis git reset a8c4ab.

Voici une série de commandes pour montrer comment cela fonctionne:

mkdir git-test; cd git-test; git init

ajoutez maintenant un fichier A

vi A

ajoutez cette ligne:

one

git commit -am one

puis ajoutez cette ligne à A:

two

git commit -am two

puis ajoutez cette ligne à A:

three

git commit -am three

maintenant le fichier A ressemble à ceci:

one
two
three

et notre git logapparence est la suivante (enfin, j'utilisegit log --pretty=oneline --pretty="%h %cn %cr ---- %s"

bfb8e46 Rose Perrone 4 seconds ago ---- three
2b613bc Rose Perrone 14 seconds ago ---- two
9aac58f Rose Perrone 24 seconds ago ---- one

Disons que nous voulons diviser le deuxième commit, two.

git rebase --interactive HEAD~2

Cela fait apparaître un message qui ressemble à ceci:

pick 2b613bc two
pick bfb8e46 three

Remplacez le premier pickpar un epour modifier ce commit.

git reset HEAD~

git diff nous montre que nous venons de décompresser le commit que nous avons fait pour le deuxième commit:

diff --git a/A b/A
index 5626abf..814f4a4 100644
--- a/A
+++ b/A
@@ -1 +1,2 @@
 one
+two

Étalons ce changement et ajoutons "et un troisième" à cette ligne dans le fichier A.

git add .

C'est généralement le point au cours d'une rebase interactive où nous exécuterions git rebase --continue, car nous voulons généralement simplement revenir dans notre pile de validations pour modifier une validation antérieure. Mais cette fois, nous voulons créer un nouveau commit. Nous allons donc courir git commit -am 'two and a third'. Maintenant, nous éditons le fichier Aet ajoutons la ligne two and two thirds.

git add . git commit -am 'two and two thirds' git rebase --continue

Nous avons un conflit avec notre commit three, alors résolvons-le:

Nous allons changer

one
<<<<<<< HEAD
two and a third
two and two thirds
=======
two
three
>>>>>>> bfb8e46... three

à

one
two and a third
two and two thirds
three

git add .; git rebase --continue

Maintenant, notre git log -plook ressemble à ceci:

commit e59ca35bae8360439823d66d459238779e5b4892
Author: Rose Perrone <roseperrone@fake.com>
Date:   Sun Jul 7 13:57:00 2013 -0700

    three

diff --git a/A b/A
index 5aef867..dd8fb63 100644
--- a/A
+++ b/A
@@ -1,3 +1,4 @@
 one
 two and a third
 two and two thirds
+three

commit 4a283ba9bf83ef664541b467acdd0bb4d770ab8e
Author: Rose Perrone <roseperrone@fake.com>
Date:   Sun Jul 7 14:07:07 2013 -0700

    two and two thirds

diff --git a/A b/A
index 575010a..5aef867 100644
--- a/A
+++ b/A
@@ -1,2 +1,3 @@
 one
 two and a third
+two and two thirds

commit 704d323ca1bc7c45ed8b1714d924adcdc83dfa44
Author: Rose Perrone <roseperrone@fake.com>
Date:   Sun Jul 7 14:06:40 2013 -0700

    two and a third

diff --git a/A b/A
index 5626abf..575010a 100644
--- a/A
+++ b/A
@@ -1 +1,2 @@
 one
+two and a third

commit 9aac58f3893488ec643fecab3c85f5a2f481586f
Author: Rose Perrone <roseperrone@fake.com>
Date:   Sun Jul 7 13:56:40 2013 -0700

    one

diff --git a/A b/A
new file mode 100644
index 0000000..5626abf
--- /dev/null
+++ b/A
@@ -0,0 +1 @@
+one

39

Les réponses précédentes ont couvert l'utilisation de git rebase -ipour modifier le commit que vous souhaitez fractionner et le valider en plusieurs parties.

Cela fonctionne bien lorsque vous divisez les fichiers en différentes validations, mais si vous souhaitez séparer les modifications apportées aux fichiers individuels, vous devez en savoir plus.

Après avoir obtenu le commit que vous souhaitez fractionner, en l'utilisant rebase -iet en le marquant edit, vous avez deux options.

  1. Après utilisation git reset HEAD~, parcourez les correctifs individuellement en utilisant git add -ppour sélectionner ceux que vous voulez dans chaque commit

  2. Modifiez la copie de travail pour supprimer les modifications que vous ne souhaitez pas; engager cet État provisoire; puis retirez le commit complet pour le prochain tour.

L'option 2 est utile si vous fractionnez un gros commit, car elle vous permet de vérifier que les versions intermédiaires se construisent et s'exécutent correctement dans le cadre de la fusion. Cela se déroule comme suit.

Après avoir utilisé rebase -iet editvalidé le commit, utilisez

git reset --soft HEAD~

pour annuler la validation, mais laissez les fichiers validés dans l'index. Vous pouvez également effectuer une réinitialisation mixte en omettant --soft, selon la proximité du résultat final de votre commit initial. La seule différence est de savoir si vous commencez par toutes les modifications par étapes ou par toutes sans modifications.

Maintenant, entrez et modifiez le code. Vous pouvez supprimer les modifications, supprimer les fichiers ajoutés et faire tout ce que vous voulez pour créer le premier commit de la série que vous recherchez. Vous pouvez également le créer, l'exécuter et confirmer que vous disposez d'un ensemble cohérent de sources.

Une fois que vous êtes satisfait, mettez en scène / décompressez les fichiers selon vos besoins (j'aime l'utiliser git guipour cela) et validez les modifications via l'interface utilisateur ou la ligne de commande

git commit

C'est le premier commit fait. Vous voulez maintenant restaurer votre copie de travail à l'état qu'elle avait après la validation que vous fractionnez, afin que vous puissiez prendre plus de modifications pour votre prochaine validation. Pour trouver le sha1 du commit que vous modifiez, utilisez git status. Dans les premières lignes de l'état, vous verrez la commande rebase en cours d'exécution, dans laquelle vous pouvez trouver le sha1 de votre commit d'origine:

$ git status
interactive rebase in progress; onto be83b41
Last commands done (3 commands done):
   pick 4847406 US135756: add debugging to the file download code
   e 65dfb6a US135756: write data and download from remote
  (see more in file .git/rebase-merge/done)
...

Dans ce cas, le commit que je modifie a sha1 65dfb6a. Sachant cela, je peux vérifier le contenu de ce commit sur mon répertoire de travail en utilisant la forme git checkoutqui prend à la fois un commit et un emplacement de fichier. Ici, j'utilise .comme emplacement de fichier pour remplacer la copie de travail entière:

git checkout 65dfb6a .

Ne manquez pas le point à la fin!

Cela extraira et mettra en scène les fichiers tels qu'ils étaient après la validation que vous modifiez, mais par rapport à la validation précédente que vous avez effectuée, donc toutes les modifications que vous avez déjà validées ne feront pas partie de la validation.

Vous pouvez soit continuer maintenant et le valider tel quel pour terminer le fractionnement, soit recommencer, en supprimant certaines parties du commit avant de faire un autre commit provisoire.

Si vous souhaitez réutiliser le message de validation d'origine pour une ou plusieurs validations, vous pouvez l'utiliser directement à partir des fichiers de travail de la rebase:

git commit --file .git/rebase-merge/message

Enfin, une fois que vous avez validé tous les changements,

git rebase --continue

poursuivra et terminera l'opération de rebase.


3
Je vous remercie!!! Ce devrait être la réponse acceptée. Cela m'aurait fait gagner beaucoup de temps et de douleur aujourd'hui. C'est la seule réponse où le résultat de la validation finale vous amène au même état que la validation en cours d'édition.
Doug Coburn

1
J'aime la façon dont vous utilisez le message de validation d'origine.
Salamandar

En utilisant l'option 2, quand je le fais, git checkout *Sha I'm Editing* .il dit toujours Updated 0 paths from *Some Sha That's Not In Git Log*et ne donne aucun changement.
Noumenon

18

git rebase --interactivepeut être utilisé pour diviser un commit en plus petits commits. Les documents Git sur rebase ont une procédure pas à pas concise - Splitting Commits :

En mode interactif, vous pouvez marquer les validations avec l'action "modifier". Cependant, cela ne signifie pas nécessairement que git rebasele résultat de cette modification soit exactement un commit. En effet, vous pouvez annuler la validation ou ajouter d'autres validations. Cela peut être utilisé pour diviser un commit en deux:

  • Démarrez un rebase interactif avec git rebase -i <commit>^, où <commit>est le commit que vous souhaitez fractionner. En fait, n'importe quelle plage de validation fera l'affaire, tant qu'elle contient cette validation.

  • Marquez le commit que vous souhaitez fractionner avec l'action "modifier".

  • Quand il s'agit de modifier ce commit, exécutez git reset HEAD^. L'effet est que la TETE est rembobinée d'une unité, et l'indice suit. Cependant, l'arbre de travail reste le même.

  • Ajoutez maintenant les modifications à l'index que vous souhaitez avoir dans le premier commit. Vous pouvez utiliser git add(éventuellement de manière interactive) ou git gui (ou les deux) pour ce faire.

  • Validez l'index actuel avec le message de validation approprié.

  • Répétez les deux dernières étapes jusqu'à ce que votre arbre de travail soit propre.

  • Continuez le rebasage avec git rebase --continue.

Si vous n'êtes pas absolument sûr que les révisions intermédiaires sont cohérentes (elles compilent, passent la suite de tests, etc.), vous devez utiliser git stashpour cacher les modifications non encore validées après chaque validation, tester et modifier la validation si des correctifs sont nécessaires .


Sous Windows, n'oubliez pas qu'il ^s'agit d'un caractère d'échappement pour la ligne de commande: il doit être doublé. Par exemple, émettez git reset HEAD^^au lieu de git reset HEAD^.
Frédéric

@ Frédéric: s je n'ai jamais rencontré ça. Au moins dans PowerShell, ce n'est pas le cas. Ensuite, en utilisant ^deux fois réinitialise deux commits au-dessus de la tête actuelle.
Farway

@Farway, essayez-le dans une ligne de commande classique. PowerShell est une autre bête, son caractère d'échappement est le backtilt.
Frédéric

Pour résumer: "HEAD^"dans cmd.exe ou PowerShell, HEAD^^dans cmd.exe, HEAD`^dans PowerShell. Il est utile d'en apprendre davantage sur le fonctionnement des shells - et de votre shell particulier (c'est-à-dire, comment une commande se transforme en parties individuelles qui sont transmises au programme) afin que vous puissiez adapter les commandes en ligne en caractères appropriés pour votre shell particulier. (Non spécifique à Windows.)
AndrewF

11

Maintenant, dans le dernier TortoiseGit sur Windows, vous pouvez le faire très facilement.

Ouvrez la boîte de dialogue de rebase, configurez -la et procédez comme suit.

  • Cliquez avec le bouton droit sur le commit que vous souhaitez fractionner et sélectionnez " Edit" (parmi le choix, le squash, la suppression ...).
  • Cliquez sur " Start" pour commencer le rebasage.
  • Une fois qu'il arrive au commit de se diviser, cochez le Edit/Splitbouton "" et cliquez Amenddirectement sur " ". La boîte de dialogue de validation s'ouvre.
    Edition / Split commit
  • Désélectionnez les fichiers que vous souhaitez placer dans une validation distincte.
  • Modifiez le message de validation, puis cliquez sur " commit".
  • Jusqu'à ce qu'il y ait des fichiers à valider, la boîte de dialogue de validation s'ouvrira encore et encore. Lorsqu'il n'y a plus de fichier à valider, il vous demandera toujours si vous souhaitez ajouter un autre commit.

Très utile, merci TortoiseGit!



8

Veuillez noter qu'il y en a aussi git reset --soft HEAD^. Il est similaire à git reset(par défaut --mixed) mais il conserve le contenu de l'index. Ainsi, si vous avez ajouté / supprimé des fichiers, vous les avez déjà dans l'index.

Se révèle très utile en cas de commits géants.


3

Voici comment diviser un commit dans IntelliJ IDEA , PyCharm , PhpStorm, etc.

  1. Dans la fenêtre du journal de Version Control, sélectionnez le commit que vous souhaitez fractionner, cliquez avec le bouton droit et sélectionnez le Interactively Rebase from Here

  2. marquez celui que vous souhaitez diviser edit, cliquez surStart Rebasing

  3. Vous devriez voir une balise jaune placée, ce qui signifie que le HEAD est défini sur ce commit. Faites un clic droit sur ce commit, sélectionnezUndo Commit

  4. Maintenant, ces validations sont de retour dans la zone de transfert, vous pouvez ensuite les valider séparément. Une fois que tous les changements ont été validés, l'ancien commit devient inactif.


2

La chose la plus simple à faire sans rebase interactif est (probablement) de créer une nouvelle branche à partir de la validation avant celle que vous souhaitez fractionner, de sélectionner -n le commit, de réinitialiser, de masquer, de valider le déplacement du fichier, de réappliquer la cachette et valider les modifications, puis fusionner avec l'ancienne branche ou sélectionner les validations qui ont suivi. (Ensuite, remplacez l'ancien nom de la branche par la tête actuelle.) (Il est probablement préférable de suivre les conseils des MBO et de rebaser de manière interactive.)


selon les normes SO de nos jours, cela devrait être qualifié de non-réponse; mais cela peut toujours être utile pour les autres, donc si cela ne vous dérange pas, veuillez déplacer cela vers les commentaires du message d'origine
YakovL

@YakovL semble raisonnable. Sur le principe de l'action minimale, je ne supprimerai pas la réponse, mais je ne m'y opposerais pas si quelqu'un d'autre le fait.
William Pursell

ce serait beaucoup plus facile que toutes les rebase -isuggestions. Je pense que cela n'a pas attiré beaucoup d'attention en raison du manque de formatage. Peut-être pourriez-vous l'examiner, maintenant que vous avez 126 000 points et savez probablement comment le faire. ;)
erikbwork


1

Si vous avez ceci:

A - B <- mybranch

Où vous avez validé du contenu dans la validation B:

/modules/a/file1
/modules/a/file2
/modules/b/file3
/modules/b/file4

Mais vous voulez diviser B en C - D, et obtenir ce résultat:

A - C - D <-mybranch

Vous pouvez diviser le contenu comme ceci par exemple (contenu de différents répertoires dans différents commits) ...

Réinitialisez la branche sur le commit avant celle à diviser:

git checkout mybranch
git reset --hard A

Créez le premier commit (C):

git checkout B /modules/a
git add -u
git commit -m "content of /modules/a"

Créez un deuxième commit (D):

git checkout B /modules/b
git add -u
git commit -m "content of /modules/b"

Et s'il y a des commits au-dessus de B?
CoolMind

1

Cela fait plus de 8 ans, mais peut-être que quelqu'un le trouvera utile de toute façon. J'ai pu faire le tour sans rebase -i. L'idée est de conduire git au même état qu'il était avant vous git commit:

# first rewind back (mind the dot,
# though it can be any valid path,
# for instance if you want to apply only a subset of the commit)
git reset --hard <previous-commit> .

# apply the changes
git checkout <commit-you-want-to-split>

# we're almost there, but the changes are in the index at the moment,
# hence one more step (exactly as git gently suggests):
# (use "git reset HEAD <file>..." to unstage)
git reset

Après cela, vous verrez ce brillant Unstaged changes after reset:et votre dépôt est dans un état tel que vous êtes sur le point de valider tous ces fichiers. À partir de maintenant, vous pouvez facilement le recommencer comme vous le faites habituellement. J'espère que cela aide.


0

Une référence rapide des commandes nécessaires, parce que je sais quoi faire mais oublie toujours la bonne syntaxe:

git rebase -i <sha1_before_split>
# mark the targeted commit with 'edit'
git reset HEAD^
git add ...
git commit -m "First part"
git add ...
git commit -m "Second part"
git rebase --continue

Crédits au billet de blog d'Emmanuel Bernard .

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.