Comment diviser un commit Git enfoui dans l'histoire?

292

J'ai fouillé mon histoire et je veux y apporter quelques changements. Le problème est que j'ai un commit avec deux changements non liés, et ce commit est entouré de quelques autres changements dans mon historique local (non poussé).

Je veux fractionner ce commit avant de le pousser, mais la plupart des guides que je vois ont à voir avec le fractionnement de votre commit le plus récent ou des modifications locales non validées. Est-il possible de faire cela à un commit qui est un peu enfoui dans l'histoire, sans avoir à "refaire" mes commits depuis?

Ben
la source

Réponses:

450

Il existe un guide pour fractionner les validations dans la page de manuel de rebase . Le résumé rapide est:

  • Effectuez un rebase interactif incluant le commit cible (par exemple git rebase -i <commit-to-split>^ branch) et marquez-le comme étant à éditer.

  • Lorsque le rebase atteint ce commit, utilisez git reset HEAD^pour réinitialiser avant le commit, mais gardez votre arbre de travail intact.

  • Ajoutez et modifiez les modifications de manière incrémentielle, en effectuant autant de validations que vous le souhaitez. add -ppeut être utile pour n'ajouter que certaines des modifications dans un fichier donné. Utilisationcommit -c ORIG_HEAD si vous souhaitez réutiliser le message de validation d'origine pour une certaine validation.

  • Si vous voulez tester ce que vous commettez (bonne idée!) Utilisez git stashpour cacher la partie que vous n'avez pas commise (ou stash --keep-indexavant même de la commettre), testez, puisgit stash pop remettez le reste dans l'arbre de travail. Continuez à faire des commits jusqu'à ce que toutes les modifications soient validées, c'est-à-dire que vous ayez un arbre de travail propre.

  • Exécutez git rebase --continuepour continuer à appliquer les validations après la validation maintenant partagée.

Cascabel
la source
17
... mais rien de tout cela si vous avez déjà poussé l'historique depuis la validation de la séparation.
wilhelmtell
29
@wilhelmtell: J'ai omis mon passe-partout habituel "potentiellement dangereux; voir 'récupération après rebase en amont'" parce que le PO a explicitement dit qu'il n'avait pas poussé cette histoire.
Cascabel
2
et vous avez fait une lecture parfaite. J'essayais d'éviter le «passe-partout» quand j'ai spécifié qu'il ne s'agissait pas encore d'un historique partagé :) En tout cas, j'ai réussi votre suggestion. C'est une grande douleur de faire ce genre de choses après coup. J'ai appris une leçon ici, et c'est pour m'assurer que les commits sont correctement insérés pour commencer!
Ben
2
La première étape peut être mieux énoncée comme git rebase -i <sha1_of_the_commit_to_split>^ branch. Et git guiest un bel outil pour la tâche de fractionnement, qui peut être utilisé pour ajouter différentes parties d'un fichier dans différentes validations.
Qiang Xu
3
@QiangXu: La première est une suggestion raisonnable. La seconde est exactement la raison pour laquelle j'ai suggéré git add -p, qui peut faire plus que ce qui est git guipossible dans ce département (notamment éditer des mecs, tout mettre en scène à partir du morceau actuel et rechercher des mecs par expression régulière).
Cascabel
3

Voici comment le faire avec Magit .

Dites que commit ed417ae est celui que vous voulez changer; il contient deux modifications indépendantes et est enfoui sous un ou plusieurs commits. Appuyez sur llpour afficher le journal et accédez à ed417ae:

journal initial

rAppuyez ensuite pour ouvrir la fenêtre de rebase

rebaser la fenêtre contextuelle

et mpour modifier le commit au point.

Remarquez comment @il y a maintenant le commit que vous souhaitez diviser - cela signifie que HEAD est maintenant à ce commit:

modification d'un commit

Nous voulons déplacer HEAD vers le parent, alors accédez au parent (47e18b3) et appuyez sur x( magit-reset-quickly, lié à osi vous utilisez evil-magit) et entrez pour dire "oui je voulais dire valider au point". Votre journal devrait maintenant ressembler à:

journal après réinitialisation

Maintenant, appuyez sur qpour passer à l'état normal de Magit, puis utilisez la ucommande régulière unstage pour annuler ce qui ne va pas dans le premier commit, valider cle reste comme d'habitude, puis stage et commit ce qui se passe dans le deuxième commit, et une fois terminé: appuyez rpour ouvrir la fenêtre de rebase

rebaser la fenêtre contextuelle

et un autre rpour continuer, et vous avez terminé! llmontre maintenant:

journal tout fait

déconcerter
la source
1

Pour diviser un commit <commit>et ajouter le nouveau commit avant celui-ci , et enregistrer la date de l'auteur de <commit>, - les étapes sont les suivantes:

  1. Modifiez le commit avant <commit>

    git rebase -i <commit>^^
    

    NB: il faudra peut-être aussi le modifier <commit>aussi.

  2. Cerise <commit>dans l'indice

    git cherry-pick -n <commit>
    
  3. Réinitialiser interactivement les modifications inutiles de l'index et réinitialiser l'arborescence de travail

    git reset -p && git checkout-index -f -a
    

    Comme alternative, cachez simplement les modifications inutiles de manière interactive: git stash push -p -m "tmp other changes"

  4. Apportez d'autres modifications (le cas échéant) et créez le nouveau commit

    git commit -m "upd something" .
    

    Facultativement, répétez les éléments 2 à 4 pour ajouter d'autres validations intermédiaires.

  5. Continuer le rebasage

    git rebase --continue
    
ruvim
la source
0

Il existe une version plus rapide si vous souhaitez uniquement extraire le contenu d'un seul fichier. C'est plus rapide parce que le rebase interactif n'est plus réellement interactif (et c'est bien sûr encore plus rapide si vous voulez extraire du dernier commit, alors pas besoin du tout de rebaser)

  1. Utilisez votre éditeur et supprimez les lignes dont vous souhaitez extraire the_file. Fermez the_file. C'est la seule édition dont vous avez besoin, tout le reste n'est que des commandes git.
  2. Étape de cette suppression dans l'index:

    git  add  the_file
    
  3. Restaurez les lignes que vous venez de supprimer dans le fichier sans affecter l'index !

    git show HEAD:./the_file > the_file
    
  4. "SHA1" est le commit dont vous voulez extraire les lignes:

    git commit -m 'fixup! SHA1' 
    
  5. Créez le deuxième commit flambant neuf avec le contenu à extraire restauré à l'étape 3:

    git commit -m 'second and new commit' the_file 
    
  6. Ne modifiez pas, n'arrêtez / continuez pas - acceptez tout simplement:

    git rebase --autosquash -i SHA1~1
    

Bien sûr, encore plus rapide lorsque le commit à extraire est le dernier commit:

4. git commit -C HEAD --amend
5. git commit -m 'second and new commit' thefile
6. no rebase, nothing

Si vous utilisez magitalors les étapes 4, 5 et 6 sont une seule action: Valider, Fixup instantané

Mars
la source
-2

Si vous n'avez pas encore poussé, utilisez simplement git rebase. Encore mieux, utilisez git rebase -ipour déplacer les validations de manière interactive. Vous pouvez déplacer le commit incriminé vers l'avant, puis le diviser à votre guise et reculer les patchs (si nécessaire).

Gintautas Miliauskas
la source
14
Il n'est pas nécessaire de le déplacer n'importe où. Divisez-le où il est.
Cascabel
1
Malheureusement, cela ne fonctionne pas pour moi car une partie de l'historique après la validation en dépend, donc je suis un peu restreint. Cependant, cela aurait été mon premier choix.
Ben
@Ben: ce n'est pas grave - les validations ultérieures n'auront pas du tout besoin de changer (en supposant que vous gardiez toutes les modifications, plutôt que d'en jeter certaines). Plus d'informations ici - stackoverflow.com/questions/1440050/…
Ether