909 votes

Comment puis-je supprimer un gros fichier de l'historique des commits dans le dépôt Git?

J'ai accidentellement déposé un DVD-rip dans un projet de site web, négligemment git commit -a -m ..., et, zut, le dépôt était gonflé de 2,2 Go. La prochaine fois, j'ai apporté quelques modifications, supprimé le fichier vidéo, et tout commité, mais le fichier compressé était toujours présent dans le dépôt, dans l'historique.

Je sais que je peux démarrer des branches à partir de ces commits et rebaser une branche sur une autre. Mais que dois-je faire pour fusionner les deux commits, afin que le gros fichier ne soit pas visible dans l'historique et soit nettoyé lors de la procédure de collecte des déchets?

12 votes

Cet article devrait vous aider help.github.com/removing-sensitive-data

1 votes

Notez que si votre grand fichier se trouve dans un sous-répertoire, vous devrez spécifier le chemin relatif complet.

736voto

Roberto Tyley Points 4352

Utilisez le BFG Repo-Cleaner, une alternative plus simple et plus rapide à git-filter-branch, spécifiquement conçue pour supprimer des fichiers indésirables de l'historique Git.

Suivez attentivement les instructions d'utilisation. La partie essentielle est simplement la suivante:

java -jar bfg.jar --strip-blobs-bigger-than 100M my-repo.git

Tous les fichiers de plus de 100 Mo (qui ne sont pas présents dans votre dernier commit) seront supprimés de l'historique de votre dépôt Git. Vous pouvez ensuite utiliser git gc pour nettoyer les données inutilisées:

git reflog expire --expire=now --all && git gc --prune=now --aggressive

Après la taille, nous pouvons pousser de force vers le dépôt distant*

git push --force

Remarque: impossible de forcer le push sur une branche protégée sur GitHub

Le BFG est généralement au moins 10-50 fois plus rapide que d'exécuter git-filter-branch, et généralement plus facile à utiliser.

Divulgation complète : je suis l'auteur du BFG Repo-Cleaner.

0 votes

@Roberto: J'ai suivi les instructions d'utilisation sur le site en faisant un clone --mirror. Quand est venu le moment de pousser le dépôt, cela a échoué en indiquant que je devais d'abord faire un pull. Je suis assez sûr qu'il n'y a pas eu de validations entre le moment du clonage et la tentative de pousser en arrière. Si je fais un pull, git se plaint qu'il a besoin d'un arbre de travail à l'intérieur de my-repo.git. Des suggestions ?

4 votes

@tony Il vaut la peine de répéter toute la procédure de clonage et de nettoyage pour voir si le message vous demandant de tirer se reproduit, mais c'est presque certainement parce que votre serveur distant est configuré pour rejeter les mises à jour non fast-forward (c'est-à-dire, il est configuré pour vous empêcher de perdre l'historique - ce que vous voulez faire exactement). Vous devez faire modifier ce paramètre sur le serveur distant, ou à défaut, pousser l'historique révisé du dépôt vers un tout nouveau dépôt vide.

1 votes

@RobertoTyley Merci. J'ai essayé cela 3 fois différentes et chaque fois j'ai reçu le même message. Donc je pense aussi que tu as raison à propos du serveur distant qui est configuré pour rejeter les mises à jour non fast-forward. Je vais envisager de pousser le dépôt mis à jour vers un tout nouveau dépôt. Merci!

649voto

Greg Bacon Points 50449

Ce que vous voulez faire est très perturbant si vous avez publié l'historique à d'autres développeurs. Voir "Récupération des rebasements en amont" dans le cadre du git rebase documentation pour les étapes nécessaires après la réparation de votre histoire.

Vous avez au moins deux options : git filter-branch et un rebasement interactif qui sont expliqués ci-dessous.

Utilisation de git filter-branch

J'ai rencontré un problème similaire avec des données de test binaires volumineuses provenant d'une importation Subversion et j'ai écrit sur le sujet suivant suppression des données d'un dépôt git .

Disons que votre histoire git est :

$ git lola --name-status
* f772d66 (HEAD, master) Login page
| A     login.html
* cb14efd Remove DVD-rip
| D     oops.iso
* ce36c98 Careless
| A     oops.iso
| A     other.html
* 5af4522 Admin page
| A     admin.html
* e738b63 Index
  A     index.html

Notez que git lola est un alias non standard mais très utile. Avec le --name-status nous pouvons voir les modifications de l'arbre associées à chaque commit.

Dans le commit "Careless" (dont le nom d'objet SHA1 est ce36c98) le fichier oops.iso est le DVD-rip ajouté par accident et supprimé dans le commit suivant, cb14efd. En utilisant la technique décrite dans l'article de blog mentionné ci-dessus, la commande à exécuter est :

git filter-branch --prune-empty -d /dev/shm/scratch \
  --index-filter "git rm --cached -f --ignore-unmatch oops.iso" \
  --tag-name-filter cat -- --all

Options :

  • --prune-empty supprime les commits qui deviennent vides ( c'est-à-dire ne modifie pas l'arbre) à la suite de l'opération de filtrage. Dans le cas typique, cette option produit un historique plus propre.
  • -d nomme un répertoire temporaire qui n'existe pas encore, à utiliser pour construire l'historique filtré. Si vous travaillez sur une distribution Linux moderne, le fait de spécifier un fichier arbre dans /dev/shm permettra une exécution plus rapide .
  • --index-filter est l'événement principal et s'exécute contre l'index à chaque étape de l'historique. Vous voulez supprimer oops.iso partout où il est trouvé, mais il n'est pas présent dans tous les commits. La commande git rm --cached -f --ignore-unmatch oops.iso supprime le DVD-rip lorsqu'il est présent et n'échoue pas dans le cas contraire.
  • --tag-name-filter décrit comment réécrire les noms de balises. Un filtre de cat est l'opération d'identité. Votre référentiel, comme l'exemple ci-dessus, peut ne pas avoir de balises, mais j'ai inclus cette option pour une généralité totale.
  • -- spécifie la fin des options pour git filter-branch
  • --all suivant -- est un raccourci pour toutes les références. Votre référentiel, comme l'exemple ci-dessus, peut n'avoir qu'une seule référence (master), mais j'ai inclus cette option pour une généralité complète.

Après un certain remue-ménage, l'histoire est maintenant :

$ git lola --name-status
* 8e0a11c (HEAD, master) Login page
| A     login.html
* e45ac59 Careless
| A     other.html
|
| * f772d66 (refs/original/refs/heads/master) Login page
| | A   login.html
| * cb14efd Remove DVD-rip
| | D   oops.iso
| * ce36c98 Careless
|/  A   oops.iso
|   A   other.html
|
* 5af4522 Admin page
| A     admin.html
* e738b63 Index
  A     index.html

Notez que le nouveau commit "Careless" ajoute seulement other.html et que le commit "Remove DVD-rip" n'est plus sur la branche master. La branche nommée refs/original/refs/heads/master contient vos commits originaux au cas où vous auriez fait une erreur. Pour le supprimer, suivez les étapes dans "Liste de contrôle pour la réduction d'un référentiel".

$ git update-ref -d refs/original/refs/heads/master
$ git reflog expire --expire=now --all
$ git gc --prune=now

Pour une alternative plus simple, clonez le référentiel pour éliminer les éléments indésirables.

$ cd ~/src
$ mv repo repo.old
$ git clone file:///home/user/src/repo.old repo

Utilisation d'un file:///... clone URL copie les objets plutôt que de créer uniquement des liens en dur.

Maintenant, votre histoire est :

$ git lola --name-status
* 8e0a11c (HEAD, master) Login page
| A     login.html
* e45ac59 Careless
| A     other.html
* 5af4522 Admin page
| A     admin.html
* e738b63 Index
  A     index.html

Les noms d'objets SHA1 pour les deux premiers commits ("Index" et "Admin page") sont restés les mêmes car l'opération de filtrage n'a pas modifié ces commits. "Careless" a perdu oops.iso et "Page de connexion" ont un nouveau parent, donc leurs SHA1s a fait changement.

Rebasement interactif

Avec une histoire de :

$ git lola --name-status
* f772d66 (HEAD, master) Login page
| A     login.html
* cb14efd Remove DVD-rip
| D     oops.iso
* ce36c98 Careless
| A     oops.iso
| A     other.html
* 5af4522 Admin page
| A     admin.html
* e738b63 Index
  A     index.html

que vous voulez supprimer oops.iso de "Careless" comme si vous ne l'aviez jamais ajouté, et ensuite "Remove DVD-rip" est inutile pour vous. Ainsi, notre plan pour une refonte interactive est de garder "Admin page", éditer "Careless", et jeter "Remove DVD-rip".

Running $ git rebase -i 5af4522 lance un éditeur avec le contenu suivant.

pick ce36c98 Careless
pick cb14efd Remove DVD-rip
pick f772d66 Login page

# Rebase 5af4522..f772d66 onto 5af4522
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

En exécutant notre plan, nous le modifions pour

edit ce36c98 Careless
pick f772d66 Login page

# Rebase 5af4522..f772d66 onto 5af4522
# ...

C'est-à-dire que nous supprimons la ligne avec "Retirer DVD-rip" et changeons l'opération sur "Careless" pour être edit plutôt que pick .

En quittant l'éditeur, nous nous retrouvons à une invite de commande avec le message suivant.

Stopped at ce36c98... Careless
You can amend the commit now, with

        git commit --amend

Once you are satisfied with your changes, run

        git rebase --continue

Comme le message nous l'indique, nous sommes sur le commit "Careless" que nous voulons modifier, donc nous lançons deux commandes.

$ git rm --cached oops.iso
$ git commit --amend -C HEAD
$ git rebase --continue

La première supprime le fichier incriminé de l'index. La deuxième modifie ou amende "Careless" pour qu'il devienne l'index mis à jour, et -C HEAD indique à git de réutiliser l'ancien message de livraison. Enfin, git rebase --continue va de l'avant avec le reste de l'opération de rebasement.

Cela donne un historique de :

$ git lola --name-status
* 93174be (HEAD, master) Login page
| A     login.html
* a570198 Careless
| A     other.html
* 5af4522 Admin page
| A     admin.html
* e738b63 Index
  A     index.html

ce qui est ce que vous voulez.

5 votes

Pourquoi je ne peux pas pousser lorsque j'utilise git filter-branch, impossible de pousser certaines références vers 'git@bitbucket.org:product/myproject.git' Pour éviter que vous ne perdiez l'historique, les mises à jour non fast-forward ont été refusées. Fusionnez les changements distants avant de pousser à nouveau.

11 votes

Ajoutez l'option -f (ou --force) à votre commande git push : "En général, la commande refuse de mettre à jour une référence distante qui n'est pas un ancêtre de la référence locale utilisée pour l'écraser. Ce drapeau désactive la vérification. Cela peut entraîner la perte de commits dans le dépôt distant ; utilisez-le avec précaution."

6 votes

C'est une réponse merveilleusement complète expliquant l'utilisation de git-filter-branch pour supprimer des fichiers indésirables volumineux de l'historique, mais il convient de noter que depuis la rédaction de sa réponse, The BFG Repo-Cleaner a été publié, ce qui est souvent plus rapide et plus facile à utiliser - voir ma réponse pour plus de détails.

42voto

Kostanos Points 1126

Ces commandes ont fonctionné dans mon cas :

git filter-branch --force --index-filter 'git rm --cached -r --ignore-unmatch oops.iso' --prune-empty --tag-name-filter cat -- --all
rm -rf .git/refs/original/
git reflog expire --expire=now --all
git gc --prune=now
git gc --aggressive --prune=now

C'est un peu différent des versions précédentes.

Pour ceux qui ont besoin de pousser ceci vers GitHub/Bitbucket (j'ai seulement testé cela avec Bitbucket) :

# ATTENTION!!!
# Cela va réécrire complètement vos références Bitbucket
# va supprimer toutes les branches que vous n'aviez pas en local

git push --all --prune --force

# Une fois que vous avez poussé, tous vos coéquipiers doivent cloner le dépôt à nouveau
# git pull ne fonctionnera pas

4 votes

Comment est-il différent de ce qui précède, pourquoi est-il meilleur ?

1 votes

Pour une raison quelconque, la version mkljun n'a pas réduit l'espace git dans mon cas, j'avais déjà supprimé les fichiers de l'index en utilisant git rm --cached fichiers. La proposition de Greg Bacon est plus complète, et assez similaire à la mienne, mais il a omis l'index --force pour les cas où vous utilisez filter-branch plusieurs fois, et il a écrit tellement d'informations que ma version est comme un résumé de celle-ci.

1 votes

Cela a vraiment aidé mais j'ai eu besoin d'utiliser l'option -f pas juste -rf ici git rm --cached -rf --ignore-unmatch oops.iso au lieu de git rm --cached -r --ignore-unmatch oops.iso comme l'a suggéré @lfender6445 ci-dessous

10voto

mkljun Points 55

Juste notez que ces commandes peuvent être très destructrices. Si plusieurs personnes travaillent sur le référentiel, elles devront toutes tirer le nouvel arbre. Les trois commandes intermédiaires ne sont pas nécessaires si votre objectif est de ne pas réduire la taille. Car la branche de filtrage crée une sauvegarde du fichier supprimé et peut y rester pendant longtemps.

git filter-branch --index-filter "git rm -rf --cached --ignore-unmatch VOTRENOMDEFICHIER" HEAD
rm -rf .git/refs/original/
git reflog expire --all
git gc --aggressive --prune
git push origin master --force

13 votes

Ne lancez PAS ces commandes à moins que vous ne vouliez vous causer d'immenses souffrances. Cela a supprimé beaucoup de mes fichiers de code source d'origine. J'ai supposé que cela purgerait certains gros fichiers de mon historique de commit GIT (comme indiqué dans la question originale), cependant, je pense que cette commande est conçue pour purger définitivement les fichiers de votre arborescence de code source d'origine (grosse différence !). Mon système : Windows, VS2012, Git Source Control Provider.

2 votes

J'ai utilisé cette commande : git filter-branch --force --index-filter 'git rm --cached -r --ignore-unmatch oops.iso' --prune-empty --tag-name-filter cat -- --all au lieu de la première de votre code.

0 votes

@mkljun, veuillez au moins supprimer "git push origin master --force" ! Tout d'abord, cela n'a aucun rapport avec la question initiale - l'auteur n'a pas demandé comment modifier les commits et pousser les changements vers un référentiel. Deuxièmement, ceci est dangereux, vous pouvez vraiment supprimer beaucoup de fichiers et pousser des changements vers un référentiel distant sans d'abord vérifier ce qui a été supprimé n'est pas une bonne idée.

9voto

Thorsten Lorenz Points 4419

git filter-branch --tree-filter 'rm -f path/to/file' HEAD a très bien fonctionné pour moi, bien que j'aie rencontré le même problème que celui décrit ici, que j'ai résolu en suivant cette suggestion.

Le livre pro-git a un chapitre entier sur la réécriture de l'historique - jetez un œil à la section filter-branch/Removing a File from Every Commit.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X