Intro: vous avez 5 solutions disponibles
L'affiche originale indique:
J'ai accidentellement validé un fichier indésirable ... dans mon référentiel il y a plusieurs validations ... Je souhaite supprimer complètement le fichier de l'historique du référentiel.
Est-il possible de réécrire l'historique des modifications tel qu'il filename.orig
n'a jamais été ajouté au référentiel en premier lieu?
Il existe de nombreuses façons de supprimer complètement l'historique d'un fichier de git:
- Modifier les commits.
- Réinitialisations matérielles (éventuellement plus un rebase).
- Rebase non interactive.
- Rebases interactives.
- Filtrage des branches.
Dans le cas de l'affiche originale, la modification du commit n'est pas vraiment une option en soi, car il a fait plusieurs commits supplémentaires par la suite, mais par souci d'exhaustivité, j'expliquerai également comment le faire, pour toute autre personne qui veut de modifier leur engagement précédent.
Notez que toutes ces solutions impliquent de modifier / réécrire l' historique / les commits d'une manière une autre, donc toute personne ayant d'anciennes copies des commits devra faire un travail supplémentaire pour resynchroniser leur historique avec le nouvel historique.
Solution 1: modification des validations
Si vous avez accidentellement apporté une modification (comme l'ajout d'un fichier) dans votre validation précédente, et que vous ne souhaitez plus que l'historique de cette modification existe, vous pouvez simplement modifier la validation précédente pour en supprimer le fichier:
git rm <file>
git commit --amend --no-edit
Solution 2: réinitialisation matérielle (éventuellement plus une rebase)
Comme la solution n ° 1, si vous voulez simplement vous débarrasser de votre commit précédent, vous avez également la possibilité de simplement réinitialiser matériellement son parent:
git reset --hard HEAD^
Cette commande réinitialisera durablement votre branche au précédent 1 er commit parent.
Cependant , si, comme l'affiche originale, vous avez effectué plusieurs validations après la validation à laquelle vous souhaitez annuler la modification, vous pouvez toujours utiliser des réinitialisations matérielles pour la modifier, mais cela implique également l'utilisation d'une rebase. Voici les étapes que vous pouvez utiliser pour modifier un commit plus loin dans l'historique:
# Create a new branch at the commit you want to amend
git checkout -b temp <commit>
# Amend the commit
git rm <file>
git commit --amend --no-edit
# Rebase your previous branch onto this new commit, starting from the old-commit
git rebase --preserve-merges --onto temp <old-commit> master
# Verify your changes
git diff master@{1}
Solution 3: rebase non interactif
Cela fonctionnera si vous souhaitez simplement supprimer un commit de l'historique:
# Create a new branch at the parent-commit of the commit that you want to remove
git branch temp <parent-commit>
# Rebase onto the parent-commit, starting from the commit-to-remove
git rebase --preserve-merges --onto temp <commit-to-remove> master
# Or use `-p` insteda of the longer `--preserve-merges`
git rebase -p --onto temp <commit-to-remove> master
# Verify your changes
git diff master@{1}
Solution 4: rebases interactives
Cette solution vous permettra d'accomplir les mêmes choses que les solutions n ° 2 et n ° 3, c'est-à-dire de modifier ou de supprimer les validations plus loin dans l'historique que votre validation précédente, de sorte que la solution que vous choisissez d'utiliser vous appartient en quelque sorte. Les rebases interactifs ne sont pas bien adaptés pour rebaser des centaines de validations, pour des raisons de performances, donc j'utiliserais des rebases non interactifs ou la solution de branche de filtre (voir ci-dessous) dans ce genre de situations.
Pour commencer le rebase interactif, utilisez ce qui suit:
git rebase --interactive <commit-to-amend-or-remove>~
# Or `-i` instead of the longer `--interactive`
git rebase -i <commit-to-amend-or-remove>~
Cela amènera git à rembobiner l'historique des validations vers le parent de la validation que vous souhaitez modifier ou supprimer. Il vous présentera ensuite une liste des validations de rembobinage dans l'ordre inverse dans l'éditeur que git est configuré pour utiliser (c'est Vim par défaut):
pick 00ddaac Add symlinks for executables
pick 03fa071 Set `push.default` to `simple`
pick 7668f34 Modify Bash config to use Homebrew recommended PATH
pick 475593a Add global .gitignore file for OS X
pick 1b7f496 Add alias for Dr Java to Bash config (OS X)
Le commit que vous souhaitez modifier ou supprimer sera en haut de cette liste. Pour le supprimer, supprimez simplement sa ligne dans la liste. Sinon, remplacez "choisir" par "modifier" sur la 1ère ligne, comme ceci:
edit 00ddaac Add symlinks for executables
pick 03fa071 Set `push.default` to `simple`
Ensuite, entrez git rebase --continue
. Si vous avez choisi de supprimer entièrement le commit, alors c'est tout ce que vous devez faire (autre que la vérification, voir l'étape finale pour cette solution). Si, d'autre part, vous vouliez modifier le commit, alors git réappliquera le commit puis suspendra le rebase.
Stopped at 00ddaacab0a85d9989217dd9fe9e1b317ed069ac... Add symlinks
You can amend the commit now, with
git commit --amend
Once you are satisfied with your changes, run
git rebase --continue
À ce stade, vous pouvez supprimer le fichier et modifier la validation, puis poursuivre la rebase:
git rm <file>
git commit --amend --no-edit
git rebase --continue
C'est ça. Comme dernière étape, que vous ayez modifié ou supprimé complètement la validation, c'est toujours une bonne idée de vérifier qu'aucune autre modification inattendue n'a été apportée à votre branche en la différenciant avec son état avant le rebase:
git diff master@{1}
Solution 5: Filtrage des branches
Enfin, cette solution est préférable si vous souhaitez effacer complètement toutes les traces de l'existence d'un fichier de l'historique, et aucune des autres solutions n'est tout à fait à la hauteur.
git filter-branch --index-filter \
'git rm --cached --ignore-unmatch <file>'
Cela supprimera <file>
toutes les validations, à partir de la validation racine. Si, à la place, vous souhaitez simplement réécrire la plage de validation HEAD~5..HEAD
, vous pouvez passer cela comme argument supplémentaire à filter-branch
, comme indiqué dans
cette réponse :
git filter-branch --index-filter \
'git rm --cached --ignore-unmatch <file>' HEAD~5..HEAD
Encore une fois, une fois l'opération filter-branch
terminée, il est généralement judicieux de vérifier qu'il n'y a pas d'autres modifications inattendues en différenciant votre branche de son état précédent avant l'opération de filtrage:
git diff master@{1}
Alternative à la branche de filtre: BFG Repo Cleaner
J'ai entendu dire que l' outil BFG Repo Cleaner s'exécute plus rapidement que git filter-branch
, alors vous voudrez peut-être également vérifier cela en option. Il est même mentionné officiellement dans la documentation de la branche filtre comme une alternative viable:
git-filter-branch vous permet de faire des réécritures complexes par script shell de votre historique Git, mais vous n'avez probablement pas besoin de cette flexibilité si vous supprimez simplement les données indésirables comme les gros fichiers ou les mots de passe. Pour ces opérations, vous pouvez envisager le BFG Repo-Cleaner , une alternative basée sur JVM à git-filter-branch, généralement au moins 10 à 50 fois plus rapide pour ces cas d'utilisation, et avec des caractéristiques assez différentes:
Toute version particulière d'un fichier est nettoyée exactement une fois . Contrairement à git-filter-branch, le BFG ne vous donne pas la possibilité de gérer un fichier différemment en fonction de l'endroit ou du moment où il a été validé dans votre historique. Cette contrainte offre l'avantage de performance de base du BFG et est bien adaptée à la tâche de nettoyage des données incorrectes - vous ne vous souciez pas de savoir où se trouvent les données incorrectes, vous voulez juste qu'elles disparaissent .
Par défaut, le BFG tire pleinement parti des machines multicœurs, nettoyant les arbres de fichiers de validation en parallèle. git-filter-branch nettoie les validations séquentiellement (c'est-à-dire de manière monothread), bien qu'il soit
possible d'écrire des filtres qui incluent leur propre parallélisme, dans les scripts exécutés par rapport à chaque commit.
Les options de commande sont beaucoup plus restrictives que la branche filtre git, et exclusivement dédié aux tâches de suppression Data- par exemple indésirables: --strip-blobs-bigger-than 1M
.
Ressources additionnelles
- Pro Git § 6.4 Git Tools - Historique de réécriture .
- Page de manuel de git-filter-branch (1) .
- Page de manuel de git-commit (1) .
- Page de manuel de git-reset (1) .
- Page de manuel de git-rebase (1) .
- Le BFG Repo Cleaner (voir aussi cette réponse du créateur lui-même ).