Question Comment supprimer / supprimer un fichier volumineux de l'historique des commit dans le dépôt Git?


De temps en temps, j'ai déposé un DVD-rip dans un projet de site Web, puis négligemment git commit -a -m ..., et, zap, le repo a été gonflé par 2,2 concerts. La prochaine fois que j'ai apporté des modifications, j'ai supprimé le fichier vidéo et tout ce que je voulais, mais le fichier compressé est toujours présent dans le référentiel, dans l'historique.

Je sais que je peux lancer des branches à partir de ces commits et rebaser une branche sur une autre. Mais que dois-je faire pour fusionner les 2 commits afin que le gros fichier ne s'affiche pas dans l’historique et ait été nettoyé lors de la récupération des ordures?


487
2018-01-20 11:18


origine


Réponses:


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

Suivez attentivement les instructions d'utilisation, la partie centrale est juste ceci:

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

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

$ git gc --prune=now --aggressive

Le BFG est généralement au moins 10-50x plus vite que courir git-filter-branch, et généralement plus facile à utiliser.

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


415
2017-07-26 20:15



Ce que vous voulez faire est très perturbant si vous avez publié l'histoire à d'autres développeurs. Voir "Récupération de Rebase Amont" dans le 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 rebase interactif, tous deux expliqués ci-dessous.

En utilisant git filter-branch

J'ai eu un problème similaire avec les données de test binaires volumineuses d'une importation Subversion et écrit à propos de supprimer des données d'un référentiel git.

Dites que votre histoire de 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 switch, nous pouvons voir les modifications d'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 prochain commit, cb14efd. En utilisant la technique décrite dans le 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 validations qui deviennent vides (c'est à dire., ne change 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 pour la génération de l'historique filtré. Si vous utilisez une distribution Linux moderne, spécifiez une arbre dans /dev/shm entraînera 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 où qu'il se trouve, 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 quand il est présent et ne manque pas autrement.
  • --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é complète.
  • --spécifie la fin des options à git filter-branch
  • --all Suivant -- est un raccourci pour toutes les références. Votre référentiel, comme l'exemple ci-dessus, peut avoir un seul ref (maître), mais j'ai inclus cette option pour une généralité complète.

Après un peu de barattage, 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 la validation "Remove DVD-rip" n'est plus sur la branche master. La branche étiquetée refs/original/refs/heads/master contient vos commits originaux au cas où vous avez fait une erreur. Pour le supprimer, suivez les étapes de "Liste de contrôle pour réduire 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 bits indésirables.

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

Utilisant un file:///... clone URL copie les objets plutôt que de créer des liens uniquement.

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'objet SHA1 pour les deux premiers commits ("Index" et "page d'administration") sont restés les mêmes car l'opération de filtrage n'a pas modifié ces validations. "Careless" perdu oops.iso et "Login page" a un nouveau parent, donc leurs SHA1 fait changement.

Rebase interactive

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

vous voulez supprimer oops.iso de "Careless" comme si vous ne l'avez jamais ajouté, puis "Remove DVD-rip" est inutile pour vous. Ainsi, notre plan allant dans un rebasage interactif est de conserver "Admin page", éditer "Careless", et rejeter "Remove DVD-rip".

Fonctionnement $ git rebase -i 5af4522 démarre un éditeur avec les contenus suivants.

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, nous supprimons la ligne avec "Remove DVD-rip" et changeons l'opération sur "Careless" pour être edit plutôt que pick.

Enregistrer-quitter Quitter l'éditeur nous envoie 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 le dit, nous sommes sur le commit "Careless" que nous voulons éditer, donc nous exécutons deux commandes.

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

Le premier supprime le fichier incriminé de l'index. Le second modifie ou amende "Careless" pour être l'index mis à jour et -C HEAD ordonne à git de réutiliser l'ancien message de validation. Finalement, git rebase --continue va de l'avant avec le reste de l'opération de rebasage.

Cela donne une histoire 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

c'est ce que tu veux


471
2018-01-28 21:55



Pourquoi ne pas utiliser cette commande simple mais puissante?

git filter-branch --tree-filter 'rm -f DVD-rip' HEAD

le --tree-filter L'option exécute la commande spécifiée après chaque extraction du projet, puis recommence les résultats. Dans ce cas, vous supprimez un fichier appelé DVD-rip de chaque instantané, qu'il existe ou non.

Voir ce lien.


110
2018-05-16 09:44



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 ci-dessus.

Pour ceux qui ont besoin de pousser ceci à github / bitbucket (j'ai seulement testé ceci avec bitbucket):

# WARNING!!!
# this will rewrite completely your bitbucket refs
# will delete all branches that you didn't have in your local

git push --all --prune --force

# Once you pushed, all your teammates need to clone repository again
# git pull will not work

26
2018-06-14 02:35



Après avoir essayé pratiquement toutes les réponses dans SO, j'ai finalement trouvé cette gemme qui a rapidement supprimé et supprimé les gros fichiers de mon dépôt et m'a permis de me synchroniser à nouveau: http://www.zyxware.com/articles/4027/how-to-delete-files-permanently-from-your-local-and-remote-git-repositories

CD dans votre dossier de travail local et exécutez la commande suivante:

git filter-branch -f --index-filter "git rm -rf --cached --ignore-unmatch FOLDERNAME" -- --all

remplacez FOLDERNAME par le fichier ou le dossier que vous souhaitez supprimer du référentiel git donné.

Une fois cela fait, exécutez les commandes suivantes pour nettoyer le dépôt local:

rm -rf .git/refs/original/
git reflog expire --expire=now --all
git gc --prune=now
git gc --aggressive --prune=now

Maintenant, poussez tous les changements dans le dépôt distant:

git push --all --force

Cela va nettoyer le référentiel distant.


17
2018-04-26 17:35



(La meilleure réponse que j'ai vue à ce problème est: https://stackoverflow.com/a/42544963/714112 , copié ici puisque ce fil apparaît haut dans les classements de recherche Google mais que l'autre ne le fait pas)

 Une coque extrêmement rapide

Ce script shell affiche tous les objets blob dans le référentiel, triés du plus petit au plus grand.

Pour mon exemple de repo, il a couru environ 100 fois plus rapide que les autres trouvés ici.
Sur mon fidèle système Athlon II X4, il gère le Dépôt de noyau Linux avec ses 5 622 155 objets dans un peu plus d'une minute.

Le script de base

git rev-list --objects --all \
| git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' \
| awk '/^blob/ {print substr($0,6)}' \
| sort --numeric-sort --key=2 \
| cut --complement --characters=13-40 \
| numfmt --field=2 --to=iec-i --suffix=B --padding=7 --round=nearest

Lorsque vous courez au-dessus du code, vous obtiendrez bien Sortie lisible par l'homme comme ça:

...
0d99bb931299  530KiB path/to/some-image.jpg
2ba44098e28f   12MiB path/to/hires-image.png
bd1741ddce0d   63MiB path/to/some-video-1080p.mp4

 Retrait rapide du fichier

Supposons que vous voulez ensuite supprimer les fichiers a et b de chaque engagement accessible depuis HEAD, vous pouvez utiliser cette commande:

git filter-branch --index-filter 'git rm --cached --ignore-unmatch a b' HEAD

15
2017-10-07 00:37



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

Le livre pro-git a un chapitre entier sur histoire de réécriture - regardez le filter-branch/ Suppression d'un fichier de chaque validation section.


9
2017-10-25 12:24



Notez simplement que ces commandes peuvent être très destructrices. Si plus de gens travaillent sur le repo, ils devront tous tirer le nouvel arbre. Les trois commandes intermédiaires ne sont pas nécessaires si votre objectif n'est PAS de réduire la taille. Parce que la branche de filtre crée une sauvegarde du fichier supprimé et il peut rester là pendant longtemps.

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

8
2018-06-14 11:53



Si vous savez que votre commit était récent au lieu de passer par l'arbre entier, faites ce qui suit: git filter-branch --tree-filter 'rm LARGE_FILE.zip' HEAD~10..HEAD


7
2018-01-01 06:21



J'ai couru dans ceci avec un compte de bitbucket, où j'avais accidentellement stocké des sauvegardes ginormous * .jpa de mon emplacement.

git filter-branch --prune-empty --index-filter 'git rm -rf --cached --ignore-unmatch MY-BIG-DIRECTORY-OR-FILE' --tag-name-filter cat -- --all

Relpace MY-BIG-DIRECTORY avec le dossier en question pour réécrire complètement votre historique (y compris les tags).

la source: http://naleid.com/blog/2012/01/17/finding-and-purging-big-files-from-git-history


5
2017-08-31 19:33



Vous pouvez le faire en utilisant le branch filter commander:

git filter-branch --tree-filter 'rm -rf path/to/your/file' HEAD


3
2018-04-05 23:28