web-dev-qa-db-fra.com

Annuler le changement dans git (ne pas réécrire l'historique)

J'ai fait un changement dans un script et je l'ai commis. Ensuite, j'ai apporté quelques autres modifications et les ai poussées vers un référentiel distant et autres.

Puis j'ai réalisé que le premier changement que j'avais mentionné était stupide, et je veux l'annuler. Puis-je "désappliquer" ce commit, sans copier/coller manuellement le diff?

Par exemple: j'ai deux fichiers, a.py et b.py:

Commit 1:
I delete a function in a.py

Commit 2:
I change a few lines in b.py

Commit 3:
I change the docstring in a.py

Puis-je annuler cette suppression de fonction et la faire apparaître comme "commit 4" (plutôt que de supprimer commit 1)

50
dbr

Oui, vous pouvez utiliser git revert pour cela. Voir la section du manuel git à ce sujet pour plus d'informations.

L'essentiel est que vous pouvez dire:

git revert 4f4k2a

Où 4f4k2a est l'ID du commit que vous souhaitez annuler, et il essaiera de le annuler.

61
Jesse Rusak

Juste un commentaire:

git revert aCommit

retourne la validation tous (comme dans " tous les fichiers la partie de la validation"):
il calcule un patch inverse, l'applique sur HEAD et commit.

Donc deux problèmes ici (le premier est facilement résolu):

  • il commet toujours, donc vous voudrez peut-être ajouter -no-commit option: "git revert --no-commit aCommit ": ceci est utile lorsque vous rétablissez plus d'un effet de commit sur votre index d'affilée.
  • il ne s'applique pas à un fichier spécifique (que faire si vous a.py faisait partie d'un commit avec 1000 autres modifications que vous ne souhaitez peut-être pas annuler)?
    Pour cela, si vous voulez extraire des fichiers spécifiques tels qu'ils étaient dans un autre commit, vous devriez voir git-checkout , en particulier le git checkout <commit> <filename> syntaxe (ce n'est cependant pas exactement ce dont vous avez besoin dans ce cas)

Easy Git (Elijah Newren) a essayé d'apporter un retour "plus complet" à la liste de diffusion Git ; mais sans grand succès:

Les gens veulent parfois "annuler les changements".

Maintenant, cela peut être:

  • les changements entre 32 et 29 révisions il y a,
  • il peut s'agir de tous les changements depuis le dernier commit,
  • ce pourrait être les changements depuis 3 commits il y a, ou
  • il peut s'agir d'un seul commit spécifique.
  • L'utilisateur peut vouloir sous-définir de telles inversions à des fichiers spécifiques ,

(eg revert est documenté ici , mais je ne suis pas sûr que cela fasse partie de la distribution actuelle, par exemple)

mais tout se résume à "annuler les changements" à la fin.

eg revert --since HEAD~3  # Undo all changes since HEAD~3
eg revert --in HEAD~8     # much like git revert HEAD~8, but nocommit by default
eg revert --since HEAD foo.py  # Undo changes to foo.py since last commit
eg revert foo.py               # Same as above
eg revert --in trial~7 bar.c baz.  # Undo changes made in trial~7 to bar.[ch]

Ces types de "restauration de données" sont-ils vraiment si différents qu'il devrait y avoir des commandes différentes, ou que certaines de ces opérations ne devraient pas être prises en charge par la simple commande de restauration?
Bien sûr, la plupart des utilisateurs utiliseront probablement le "eg revert FILE1 FILE2... ", mais je n'ai pas vu le mal à supporter les capacités supplémentaires.

Aussi ... y a-t-il quelque chose de fondamental qui empêcherait le noyau dur d'adopter un tel comportement?

Elijah

Remarque: les validations par défaut n'ont aucun sens pour la commande généralisée revert et "git revert REVISION "entraînerait une erreur avec les instructions (indiquant à l'utilisateur d'ajouter l'indicateur --in).


Disons que vous avez, sur 50 validés, 20 fichiers, vous vous rendez compte que l'ancien commit X a introduit des changements qui n'auraient pas dû avoir lieu.
Un peu de plomberie s'impose.
Ce dont vous avez besoin est un moyen de lister tous les fichiers spécifiques dont vous avez besoin pour revenir
(comme dans "pour annuler les modifications apportées dans la validation X tout en conservant toutes les modifications ultérieures" ),
puis, pour chacun d'eux:

git-merge-file -p a.py X X^

Le problème ici est de récupérer la fonction perdue sans effacer toutes les modifications ultérieures dans a.py que vous voudrez peut-être conserver.
Cette technique est parfois appelée "fusion négative".

Depuis git merge-file <current-file> <base-file> <other-file>signifie :
incorpore toutes les modifications qui découlent de <base-file> à <other-file> dans <current-file>, vous pouvez restaurer la fonction supprimée en disant que vous souhaitez intégrer toutes les modifications.)

  • de: X (où la fonction a été supprimée)
  • à: X ^ (le commit précédent avant X, où la fonction était toujours là)

Remarque: le '-p ' argument qui vous permet de revoir d'abord les modifications sans rien faire sur le fichier courant. Lorsque vous êtes sûr, supprimez cette option.

Remarque : le git merge-file est pas si simple : vous ne pouvez pas référencer les versions précédentes du fichier comme ça.
(vous auriez sans cesse le message frustrant: error: Could not stat X)
Vous devez:

git cat-file blob a.py > tmp/ori # current file before any modification
git cat-file blob HEAD~2:a.py > tmp/X # file with the function deleted
git cat-file blob HEAD~3:a.py > tmp/F # file with the function which was still there

git merge-file a.py tmp/X tmp/F # basically a RCS-style merge
                                 # note the inversed commit order: X as based, then F
                                 # that is why is is a "negative merge"
diff -u a.py tmp/ori # eyeball the merge result
git add a.py 
git commit -m "function restored" # and any other changes made from X are preserved!

Si cela doit être fait pour un grand nombre de fichiers dans un commit précédent ... certains scripts sont en ordre;)

33
VonC

Pour annuler les modifications apportées à un seul fichier dans un commit, comme VonC l'a souligné, je voudrais checkout la branche (master ou trunk ou autre) puis checkout the version du fichier que je voulais rétablir et le traiter comme un nouveau commit:

$ git checkout trunk
$ git checkout 4f4k2a^ a.py
$ git add a.py
$ git diff              #verify I'm only changing what I want; edit as needed
$ git commit -m 'recover function deleted from a.py in 4f4k2a'

Il y a probablement une commande de plomberie qui ferait cela directement, mais je ne l'utiliserais pas si je le savais. Ce n'est pas que je ne fais pas confiance à Git, mais je ne me fais pas confiance - je ne ferais pas confiance à ce que je savais sans regarder ce qui a été changé dans ce fichier dans ce commit et depuis lors. Et une fois que je regarde, il est plus facile de simplement construire un nouveau commit en éditant le diff. C'est peut-être juste un style de travail personnel.

4
Paul

Jetez un oeil à cette git revert question . Il semble y avoir un problème lors de la restauration des validations plus anciennes, si ce n'est dans une séquence consécutive, y compris la validation la plus récente.

1
Mohan Nayaka