web-dev-qa-db-fra.com

Comment supprimer / supprimer un fichier volumineux de l'historique de validation dans le référentiel Git?

Parfois, je laissais tomber un DVD dans un projet de site Web, puis négligemment git commit -a -m ..., et, zap, le repo était surchargé de 2,2 concerts. La prochaine fois que j'ai apporté quelques modifications, j'ai supprimé le fichier vidéo et tout validé, mais le fichier compressé est toujours présent dans le référentiel, 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 2 commits afin que le gros fichier ne soit pas affiché dans l'historique et qu'il soit nettoyé lors de la procédure de récupération de place?

619
culebrón

Utilisez le BFG Repo-Cleaner , une alternative plus simple et plus rapide à git-filter-branch, spécialement conçue pour supprimer les fichiers non souhaités de l’historique Git.

Suivez attentivement les instructions d'utilisation , la partie principale 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 font pas partie de votre dernier commit ) seront supprimés de l'historique de votre référentiel Git. Vous pouvez ensuite utiliser git gc pour effacer les données mortes:

$ git gc --Prune=now --aggressive

Le BFG est généralement au moins 10-50x plus rapide que l'exécution de git-filter-branch, et est généralement plus facile à utiliser.

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

529
Roberto Tyley

Ce que vous voulez faire est très perturbant si vous avez publié l'historique à d'autres développeurs. Voir "Récupération à partir de la base de données amont" dans la documentation _git rebase_ pour connaître les étapes nécessaires après la réparation de votre historique.

Vous avez au moins deux options: _git filter-branch_ et une base interactive, expliqués ci-dessous.

Utilisation de _git filter-branch_

J'ai eu un problème similaire avec des données de test binaires volumineuses provenant d'une importation Subversion et j'ai écrit à propos de suppression 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 commutateur _--name-status_, nous pouvons voir les modifications de l’arborescence 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é du prochain commit, cb14efd. En utilisant la technique décrite dans l'article de blog susmentionné, la commande à exécuter est la suivante:

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

Les options:

  • _--Prune-empty_ supprime les validations qui deviennent vides (, c'est-à-dire , ne changez pas l'arborescence) à 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 inexistant à utiliser pour créer l'historique filtré. Si vous utilisez une distribution Linux moderne, spécifier ne arborescence dans _/dev/shm_ entraînera une exécution plus rapide .
  • _--index-filter_ est l'événement principal et s'exécute sur l'index à chaque étape de l'historique. Vous voulez supprimer _oops.iso_ partout où 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 n'échoue pas autrement.
  • _--tag-name-filter_ explique 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 dans l'exemple ci-dessus, peut ne comporter qu'une seule référence (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" n’ajoute que _other.html_ et que le commit "Remove DVD-rip" n’apparaît plus dans la branche principale. La branche intitulée _refs/original/refs/heads/master_ contient vos commits d'origine en cas d'erreur. Pour le supprimer, suivez les étapes décrites 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 supprimer les bits indésirables.

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

L'utilisation d'une URL de clonage _file:///..._ permet de copier des objets plutôt que de créer des liens physiques.

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 validations ("Index" et "Page administrateur") sont restés inchangés car l'opération de filtrage n'a pas modifié ces validations. “Careless” lost _oops.iso_ et “Login page” ont un nouveau parent; leurs SHA1 ont donc changé .

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 "Insouciant" comme si vous ne l'aviez jamais ajouté, puis "Supprimer le DVD-rip" ne vous servirait à rien. Par conséquent, notre plan pour créer une base interactive est de conserver la "page d'administration", d'éditer "sans attention" et de supprimer "Supprimer le rip de DVD".

L'exécution de _$ git rebase -i 5af4522_ démarre 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 "Remove DVD-rip" et modifions l'opération sur "Careless" en edit plutôt que pick.

Enregistrer-quitter l'éditeur nous dépose à 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 souhaitons éditer. Nous exécutons donc 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 modifie "Careless" pour qu'il soit l'index mis à jour et _-C HEAD_ indique à git de réutiliser l'ancien message de validation. Enfin, _git rebase --continue_ continue avec le reste de l'opération de rebasement.

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.

521
Greg Bacon

Pourquoi ne pas utiliser cette commande simple mais puissante?

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

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

Voir ce lien .

158
Gary Gauh

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

???? Un one-liner Shell ultra-rapide ????

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

Pour mon exemple de dépôt, il fonctionnait environ 100 fois plus vite que les autres trouvés ici.
Sur mon fidèle système Athlon II X4, il gère le référentiel du noyau Linux avec ses 5 622 155 objets en à peine 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 exécutez le code ci-dessus, vous obtenez Nice une sortie lisible par l'homme comme ceci:

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

???? Suppression rapide de fichiers ????

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

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

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 (je l’ai seulement testé 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
36
Kostanos

Après avoir essayé pratiquement toutes les réponses dans SO, j’ai enfin trouvé ce petit bijou qui a rapidement supprimé et supprimé les gros fichiers de mon référentiel et m’a permis de synchroniser à nouveau: http://www.zyxware.com/articles/4027/how -to-supprimer-les-fichiers-en-permanence-de-votre-repertoire-local-et-distant-git

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 que cela est fait, exécutez les commandes suivantes pour nettoyer le référentiel local:

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

Maintenant, envoyez toutes les modifications au référentiel distant:

git Push --all --force

Cela nettoiera le référentiel distant.

35
Justin

Notez juste que ces commandes peuvent être très destructives. Si davantage de personnes travaillent sur le repo, elles devront toutes tirer le nouvel arbre. Les trois commandes du milieu 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 qu'il peut y rester 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
9
mkljun

git filter-branch --tree-filter 'rm -f path/to/file' HEAD a plutôt 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 contient un chapitre entier sur historique de la réécriture - consultez la section filter-branch/Suppression d’un fichier de chaque validation .

9
Thorsten Lorenz

Si vous savez que votre commit était récent au lieu de parcourir l'intégralité de l'arborescence, procédez comme suit: git filter-branch --tree-filter 'rm LARGE_FILE.Zip' HEAD~10..HEAD

8
Soheil

J'ai rencontré ce problème avec un compte bitbucket, où j'avais accidentellement stocké des sauvegardes ginormous * .jpa de mon site.

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

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

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

6
lfender6445

J'ai essentiellement fait ce qui était sur cette réponse: https://stackoverflow.com/a/11032521/128642

(pour l'histoire, je vais le copier-coller ici)

$ 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

Cela n'a pas fonctionné, car j'aime renommer et déplacer beaucoup de choses. Ainsi, certains gros fichiers se trouvaient dans des dossiers renommés, et je pense que le gc ne pourrait pas supprimer la référence à ces fichiers en raison de la référence dans les objets tree pointant vers ces fichiers. Ma solution ultime pour vraiment le tuer était de:

# First, apply what's in the answer linked in the front
# and before doing the gc --Prune --aggressive, do:

# Go back at the Origin of the repository
git checkout -b newinit <sha1 of first commit>
# Create a parallel initial commit
git commit --amend
# go back on the master branch that has big file
# still referenced in history, even though 
# we thought we removed them.
git checkout master
# rebase on the newinit created earlier. By reapply patches,
# it will really forget about the references to hidden big files.
git rebase newinit

# Do the previous part (checkout + rebase) for each branch
# still connected to the original initial commit, 
# so we remove all the references.

# Remove the .git/logs folder, also containing references
# to commits that could make git gc not remove them.
rm -rf .git/logs/

# Then you can do a garbage collection,
# and the hidden files really will get gc'ed
git gc --Prune --aggressive

Mon repo (le .git) est passé de 32 Mo à 388 Ko, même la branche de filtre ne peut pas nettoyer.

3
Dolanor

Vous pouvez le faire en utilisant la commande branch filter:

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

3
John Foley

git filter-branch est une commande puissante que vous pouvez utiliser pour supprimer un fichier volumineux de l'historique des commits. Le fichier restera pendant un moment et Git le supprimera dans la prochaine collecte de place. Vous trouverez ci-dessous le processus complet à partir de suppression de fichiers de l'historique de validation . Pour des raisons de sécurité, il exécute d'abord la commande sur une nouvelle branche:

# Do it in a new testing branch
$ git checkout -b test

# Remove file-name from every commit on the new branch
# --index-filter, rewrite index without checking out
# --cached, remove it from index but not include working tree
# --ignore-unmatch, ignore if files to be removed are absent in a commit
# HEAD, execute the specified command for each commit reached from HEAD by parent link

$ git filter-branch --index-filter 'git rm --cached --ignore-unmatch file-name' HEAD

# The output is OK, reset it to the prior branch master
$ git checkout master
$ git reset --soft test

# Remove test branch
$ git branch -rm test

# Push it with force
$ git Push --force Origin master
2
zhangyu12

Utilisez Git Extensions , c’est un outil d’interface utilisateur. Il possède un plugin nommé "Rechercher des fichiers volumineux" qui trouve les fichiers de stockage dans des référentiels et permet de les supprimer de manière permanente.

N'utilisez pas 'git filter-branch' avant d'utiliser cet outil, car il ne pourra pas trouver les fichiers supprimés par 'filter-branch' (Altough 'filter-branch' ne supprime pas complètement les fichiers des fichiers du pack de référentiel). .

1
Nir

Lorsque vous rencontrez ce problème, git rm ne suffira pas, car git se souvient que le fichier existait une fois dans notre historique et en conservera donc une référence.

Pour aggraver les choses, rebaser n'est pas facile non plus, car toute référence au blob empêchera git garbage collector de nettoyer l'espace. Ceci inclut les références distantes et les références de reflog.

J'ai assemblé git forget-blob, un petit script qui tente de supprimer toutes ces références, puis utilise git filter-branch pour réécrire chaque commit de la branche.

Une fois que votre blob n'est plus référencé, git gc s'en débarrassera

L'utilisation est assez simple git forget-blob file-to-forget. Vous pouvez obtenir plus d'informations ici

https://ownyourbits.com/2017/01/18/completely-remove-a-file-from-a-git-repository-with-git-forget-blob/

J'ai mis cela en place grâce aux réponses de Stack Overflow et à quelques entrées de blog. Crédits à eux!

1
nachoparker