Comment diviser le dernier commit en deux dans Git

277

J'ai deux branches de travail, master et forum et je viens de faire quelques modifications dans la branche forum , que j'aimerais bien choisir en master . Mais malheureusement, le commit que je veux sélectionner contient également des modifications que je ne veux pas.

La solution serait probablement de supprimer en quelque sorte la mauvaise validation et de la remplacer par deux validations distinctes, une avec les modifications que je veux sélectionner dans master, et d'autres qui n'y appartiennent pas.

J'ai essayé de faire

git reset --hard HEAD^

qui a supprimé toutes les modifications, j'ai donc dû revenir avec

git reset ORIG_HEAD

Ma question est donc la suivante: quelle est la meilleure façon de diviser le dernier commit en deux commit séparés?

Jakub Arnold
la source

Réponses:

332

Vous devez utiliser l'index. Après avoir effectué une réinitialisation mixte (" git reset HEAD ^"), ajoutez le premier ensemble de modifications dans l'index, puis validez-les. Engagez ensuite le reste.

Vous pouvez utiliser " git add " pour mettre toutes les modifications apportées dans un fichier à l'index. Si vous ne voulez pas mettre en scène toutes les modifications apportées dans un fichier, seulement certaines d'entre elles, vous pouvez utiliser "git add -p".

Voyons un exemple. Supposons que j'avais un fichier appelé monfichier, qui contient le texte suivant:

something
something else
something again

Je l'ai modifié dans mon dernier commit pour que maintenant ça ressemble à ceci:

1
something
something else
something again
2

Maintenant, je décide que je veux le diviser en deux, et je veux que l'insertion de la première ligne soit dans le premier commit, et l'insertion de la dernière ligne dans le deuxième commit.

Je reviens d'abord au parent de HEAD, mais je veux conserver les modifications dans le système de fichiers, donc j'utilise "git reset" sans argument (ce qui fera une réinitialisation dite "mixte"):

$ git reset HEAD^
myfile: locally modified
$ cat myfile
1
something
something else
something again
2

Maintenant, j'utilise "git add -p" pour ajouter les changements que je veux valider à l'index (= je les mets en scène). "git add -p" est un outil interactif qui vous demande quelles modifications du fichier doit-il ajouter à l'index.

$ git add -p myfile
diff --git a/myfile b/myfile
index 93db4cb..2f113ce 100644
--- a/myfile
+++ b/myfile
@@ -1,3 +1,5 @@
+1
 something
 something else
 something again
+2
Stage this hunk [y,n,a,d,/,s,e,?]? s    # split this section into two!
Split into 2 hunks.
@@ -1,3 +1,4 @@
+1
 something
 something else
 something again
Stage this hunk [y,n,a,d,/,j,J,g,e,?]? y  # yes, I want to stage this
@@ -1,3 +2,4 @@
 something
 something else
 something again
+2
Stage this hunk [y,n,a,d,/,K,g,e,?]? n   # no, I don't want to stage this

Ensuite, je commets ce premier changement:

$ git commit -m "Added first line"
[master cef3d4e] Added first line
 1 files changed, 1 insertions(+), 0 deletions(-)

Maintenant, je peux valider toutes les autres modifications (à savoir le chiffre "2" mis à la dernière ligne):

$ git commit -am "Added last line"
[master 5e284e6] Added last line
 1 files changed, 1 insertions(+), 0 deletions(-)

Vérifions le journal pour voir quels engagements nous avons:

$ git log -p -n2 | cat
Commit 5e284e652f5e05a47ad8883d9f59ed9817be59d8
Author: ...
Date: ...

    Added last line

Diff --git a/myfile b/myfile
Index f9e1a67..2f113ce 100644
--- a/myfile
+++ b/myfile
@@ -2,3 +2,4 @@
 something
 something else
 something again
+2

Commit cef3d4e0298dd5d279a911440bb72d39410e7898
Author: ...
Date: ...

    Added first line

Diff --git a/myfile b/myfile
Index 93db4cb..f9e1a67 100644
--- a/myfile
+++ b/myfile
@@ -1,3 +1,4 @@
+1
 something
 something else
 something again
hcs42
la source
1
Je me suis lentement habitué à git depuis Mercurial au cours de la dernière semaine et demie, et il existe une commande de raccourci pratique git reset [--patch|-p] <commit>que vous pouvez utiliser pour vous éviter d'avoir à le faire git add -paprès la réinitialisation. Ai-je raison? Utiliser git 1.7.9.5.
trojjer
2
Voici un peu plus sur cette technique, y compris le rebasage s'il s'agissait d'un commit plus ancien, ou si vous devez changer N commits en M commits: emmanuelbernard.com/blog/2014/04/14/… .
Chris Westin
84

Buts:

  • Je veux diviser un commit ( splitme) passé en deux.
  • Je souhaite conserver le message de validation .

Plan:

  1. rebase interactif d'un précédent splitme.
  2. modifier splitme.
  3. Réinitialisez les fichiers à diviser en un deuxième commit.
  4. Modifier le commit, maintenir le message, modifier si nécessaire.
  5. Ajoutez à nouveau les fichiers séparés du premier commit.
  6. Validez avec un nouveau message.
  7. Continuez à rebaser.

Les étapes de rebase (1 et 7) peuvent être ignorées s'il splitmes'agit du commit le plus récent.

git rebase -i splitme^
# mark splitme commit with 'e'
git reset HEAD^ -- $files
git commit --amend
git add $files
git commit -m "commit with just some files"
git rebase --continue

Si je voulais que les fichiers fractionnés soient validés en premier, je rebase ensuite -i à nouveau et je change l'ordre

git rebase -i splitme^
# swap order of splitme and 'just some files'
spazm
la source
1
git reset HEAD^était la pièce manquante du puzzle. Fonctionne bien avec -paussi. Merci!
Marius Gedminas
10
Il est important de noter l' -- $filesargument git reset. Avec les chemins passés, git resetrestaure ces fichiers à l'état de la validation référencée mais ne modifie aucune validation. Si vous quittez les chemins, vous "perdez" le commit que vous souhaitez modifier à l'étape suivante.
marqueurs duelin
2
Cette méthode vous évite d'avoir à copier et coller à nouveau votre premier message de validation, par rapport à la réponse acceptée.
Calvin
Aussi: si vous souhaitez réinitialiser tous les fichiers, utilisez simplement git reset HEAD^ -- .. Il est extrêmement surprenant que ce ne soit pas exactement le comportement de git reset HEAD^.
allidoiswin
52

Pour changer le commit actuel en deux commits, vous pouvez faire quelque chose comme ceci.

Soit:

git reset --soft HEAD^

Cela annule le dernier commit mais laisse tout en scène. Vous pouvez ensuite décompresser certains fichiers:

git reset -- file.file

Restaurez éventuellement des parties de ces fichiers:

git add -p file.file

Faites un nouveau premier commit:

git commit

Étape et validez le reste des modifications dans un deuxième commit:

git commit -a

Ou:

Annulez et annulez toutes les modifications du dernier commit:

git reset HEAD^

Organisez de manière sélective le premier cycle de modifications:

git add -p

Commettre:

git commit

Validez le reste des modifications:

git commit -a

(Dans l'une ou l'autre étape, si vous annulez une validation qui a ajouté un nouveau fichier et que vous souhaitez l'ajouter à la deuxième validation, vous devrez l'ajouter manuellement, car commit -aseules les modifications des étapes dans les fichiers déjà suivis seront effectuées .)

CB Bailey
la source
22

Exécutez git gui, sélectionnez le bouton radio "Modifier le dernier commit" et annulez (Commit> Unstage From Commit ou Ctrl- U) les modifications que vous ne souhaitez pas entrer dans le premier commit. Je pense que c'est la façon la plus simple de procéder.

Une autre chose que vous pouvez faire est de sélectionner la modification sans commettre ( git cherry-pick -n), puis manuellement ou avec git guiles modifications souhaitées avant de valider.

Michael Krelin - pirate
la source
15
git reset HEAD^

le --hard est ce qui tue vos changements.

art sémantique
la source
13

Je suis surpris que personne ne l'ait suggéré git cherry-pick -n forum. Cela mettra en scène les modifications de la dernière forumvalidation, mais pas les valider - vous pouvez ensuite resetsupprimer les modifications dont vous n'avez pas besoin et valider ce que vous souhaitez conserver.

dahlbyk
la source
3

La méthode du double-retour-squash

  1. Effectuez un autre commit qui supprime les modifications indésirables. (Si c'est par fichier, c'est vraiment facile: git checkout HEAD~1 -- files with unwanted changeset git commit. Sinon, les fichiers avec des changements mixtes peuvent être partiellement organisés git reset fileet git add -p filecomme étape intermédiaire.) Appelez cela le retour .
  2. git revert HEAD- Faites encore un autre commit, qui ajoute les changements indésirables. Ceci est le double retour
  3. Sur les 2 commits que vous avez effectués, écrasez le premier sur le commit de split ( git rebase -i HEAD~3). Ce commit devient maintenant libre des changements indésirables, car ceux-ci sont dans le deuxième commit.

Avantages

  • Préserve le message de validation
  • Fonctionne même si la validation de la division n'est pas la dernière. Il suffit que les modifications indésirables n'entrent pas en conflit avec des validations ultérieures.
user2394284
la source
1

Puisque vous choisissez des cerises, vous pouvez:

  1. cherry-pickavec --no-commitoption ajoutée.
  2. resetet utiliser add --patch, add --editou tout simplement addpour mettre en scène ce que vous souhaitez conserver.
  3. commit les changements par étapes.
    • Pour réutiliser le message de validation d'origine, vous pouvez ajouter des options --reuse-message=<old-commit-ref>ou --reedit-message=<old-commit-ref>à la commitcommande.
  4. Soufflez les modifications non mises en scène avec reset --hard.

Une autre façon, en conservant ou en modifiant le message de validation d'origine:

  1. cherry-pick le commit d'origine comme d'habitude.
  2. Inversez les modifications que vous ne souhaitez pas et utilisez addpour mettre en scène l'inversion.
    • Cette étape serait facile si vous supprimez ce que vous avez ajouté, mais un peu délicate si vous ajoutez ce que vous avez supprimé ou annulez une modification.
  3. commit --amend pour effectuer l'inversion sur le commit choisi par les cerises.
    • Vous obtiendrez à nouveau le même message de validation, que vous pouvez conserver ou réviser si nécessaire.
ADTC
la source
0

Cela pourrait être une autre solution ciblée pour les cas où il y a une énorme validation et une petite quantité de fichiers doit être déplacée dans une nouvelle validation. Cela fonctionnera si un ensemble de <path>fichiers doit être extrait du dernier commit sur HEAD et tous déplacés vers un nouveau commit. Si plusieurs validations sont nécessaires, les autres solutions peuvent être utilisées.

Effectuez d'abord des correctifs dans les zones par étapes et non par étapes qui contiendraient les modifications pour rétablir le code respectivement avant et après modification:

git reset HEAD^ <path>

$ git status
On branch <your-branch>
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   <path>

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   <path>

Pour comprendre ce qui va se passer (la flèche et les commentaires ne font pas partie de la commande):

git diff --cached   -> show staged changes to revert <path> to before HEAD
git diff            -> show unstaged changes to add current <path> changes

Annuler les <path>modifications du dernier commit:

git commit --amend  -> reverts changes on HEAD by amending with staged changes

Créez un nouveau commit avec des <path>modifications:

git commit -a -m "New Commit" -> adds new commit with unstaged changes

Cela a pour effet de créer un nouveau commit contenant les modifications extraites du dernier commit.

Jose Cifuentes
la source