Supprimer le dossier et son contenu de l'historique de git / GitHub

318

Je travaillais sur un référentiel sur mon compte GitHub et c'est un problème sur lequel je suis tombé.

  • Projet Node.js avec un dossier avec quelques packages npm installés
  • Les packages étaient dans le node_modulesdossier
  • Ajout de ce dossier au dépôt git et transmission du code à github (ne pensait pas à la partie npm à l'époque)
  • Réalisé que vous n'avez pas vraiment besoin de ce dossier pour faire partie du code
  • Supprimé ce dossier, poussé

Dans ce cas, la taille du dépôt git total était d'environ 6 Mo, où le code réel (tous sauf ce dossier) n'était que d'environ 300 Ko .

Maintenant, ce que je recherche à la fin est un moyen de se débarrasser des détails de ce dossier de package de l'historique de git, donc si quelqu'un le clone, il n'a pas besoin de télécharger 6 Mo d'historique où les seuls fichiers réels qu'ils obtiendront au dernier commit serait de 300 Ko.

J'ai recherché des solutions possibles pour cela et j'ai essayé ces 2 méthodes

The Gist semblait avoir fonctionné où, après avoir exécuté le script, il a montré qu'il s'était débarrassé de ce dossier et après cela, il a montré que 50 validations différentes ont été modifiées. Mais cela ne m'a pas permis de pousser ce code. Quand j'ai essayé de le pousser, il a dit Branch up to datemais a montré que 50 commits ont été modifiés sur a git status. Les 2 autres méthodes n'ont pas aidé non plus.

Maintenant, même s'il a montré qu'il s'était débarrassé de l'historique de ce dossier, lorsque j'ai vérifié la taille de ce dépôt sur mon hôte local, il était toujours d'environ 6 Mo. (J'ai également supprimé le refs/originaldossier mais je n'ai pas vu le changement dans la taille du dépôt).

Ce que je cherche à clarifier, c'est s'il existe un moyen de se débarrasser non seulement de l'historique des validations (qui est la seule chose qui, selon moi, s'est produite), mais aussi de ces fichiers que git continue de supposer que l'on veut annuler.

Disons qu'une solution est présentée pour cela et est appliquée sur mon hôte local mais ne peut pas être reproduite sur ce repo GitHub, est-il possible de cloner ce repo, de revenir au premier commit d'effectuer l'astuce et de le pousser (ou cela signifie-t-il que git ont encore un historique de tous ces commits? - alias. 6 Mo).

Mon objectif final ici est de trouver fondamentalement la meilleure façon de se débarrasser du contenu du dossier de git afin qu'un utilisateur n'ait pas à télécharger 6 Mo de trucs et ait éventuellement les autres validations qui n'ont jamais touché le dossier des modules (c'est assez presque tous) dans l'histoire de git.

Comment puis-je faire ceci?

Kartik
la source
3
Si l'une des réponses ci-dessous a résolu votre problème, vous devriez peut-être envisager d'en accepter une comme réponse à votre question. meta.stackexchange.com/questions/5234/…
starbeamrainbowlabs
La meilleure réponse est: stackoverflow.com/a/32886427/5973334
Kuzeko

Réponses:

556

Si vous êtes ici pour copier-coller du code:

Ceci est un exemple qui supprime node_modulesde l'histoire

git filter-branch --tree-filter "rm -rf node_modules" --prune-empty HEAD
git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
echo node_modules/ >> .gitignore
git add .gitignore
git commit -m 'Removing node_modules from git history'
git gc
git push origin master --force

Ce que fait git:

La première ligne parcourt toutes les références de la même arborescence ( --tree-filter) que HEAD (votre branche actuelle), en exécutant la commande rm -rf node_modules. Cette commande supprime le dossier node_modules ( -r, sans -r, rmne supprimera pas les dossiers), sans invite à l'utilisateur ( -f). L'ajout --prune-emptysupprime inutile (ne change rien) commet récursivement.

La deuxième ligne supprime la référence à cette ancienne branche.

Les autres commandes sont relativement simples.

Mohsen
la source
3
Juste une remarque: j'ai l'habitude git count-objects -vde vérifier si les fichiers ont été réellement supprimés mais la taille du référentiel reste la même jusqu'à ce que je clone à nouveau le référentiel. Git conserve une copie de tous les fichiers originaux, je pense.
Davide Icardi
4
Avec un git non ancien, cela devrait probablement être lu --force-with-lease, non --force.
Griwes
4
Aucune de ces commandes ne fonctionne sous Windows. Ou du moins pas Windows 10, veuillez poster le système d'exploitation sur lequel le "copier-coller" fonctionne
David
3
Pour les utilisateurs de Windows 10, cela fonctionne bien sous Bash pour Windows (j'ai utilisé Ubuntu)
Andrej Kyselica
3
Je l'ai essayé avec le shell Windows et avec git bash, et cela n'a pas fonctionné. Première commande réussie, deuxième commande échouée!
Mohy Eldeen
240

Je trouve que l' --tree-filteroption utilisée dans d'autres réponses peut être très lente, en particulier sur les grands référentiels avec beaucoup de commits.

Voici la méthode que j'utilise pour supprimer complètement un répertoire de l'historique git en utilisant l' --index-filteroption, qui s'exécute beaucoup plus rapidement:

# Make a fresh clone of YOUR_REPO
git clone YOUR_REPO
cd YOUR_REPO

# Create tracking branches of all branches
for remote in `git branch -r | grep -v /HEAD`; do git checkout --track $remote ; done

# Remove DIRECTORY_NAME from all commits, then remove the refs to the old commits
# (repeat these two commands for as many directories that you want to remove)
git filter-branch --index-filter 'git rm -rf --cached --ignore-unmatch DIRECTORY_NAME/' --prune-empty --tag-name-filter cat -- --all
git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d

# Ensure all old refs are fully removed
rm -Rf .git/logs .git/refs/original

# Perform a garbage collection to remove commits with no refs
git gc --prune=all --aggressive

# Force push all branches to overwrite their history
# (use with caution!)
git push origin --all --force
git push origin --tags --force

Vous pouvez vérifier la taille du référentiel avant et après gcavec:

git count-objects -vH
Lee Netherton
la source
3
pourriez-vous expliquer pourquoi c'est beaucoup plus rapide?
knocte
7
@knocte: à partir des documents ( git-scm.com/docs/git-filter-branch ). "--index-filter: ... est similaire au filtre d'arbre mais ne vérifie pas l'arbre, ce qui le rend beaucoup plus rapide"
Lee Netherton
23
Pourquoi n'est-ce pas la réponse acceptée? C'est tellement complet.
Mad Physicist
2
Si vous faites cela dans Windows, vous avez besoin de guillemets doubles au lieu de guillemets simples.
Kris Morness
12
Passer --quietà ce qui git rmprécède a accéléré ma réécriture au moins par le facteur 4.
ctusch
46

En plus de la réponse populaire ci-dessus, je voudrais ajouter quelques notes pour les systèmes Windows . La commande

git filter-branch --tree-filter 'rm -rf node_modules' --prune-empty HEAD
  • fonctionne parfaitement sans aucune modification! Par conséquent, vous ne devez pas utiliser Remove-Item, delni rien d'autre à la place de rm -rf.

  • Si vous devez spécifier un chemin vers un fichier ou un répertoire, utilisez des barres obliques comme./path/to/node_modules

participant
la source
Cela ne fonctionnera pas sous Windows si le répertoire contient un. (point) dans le nom.
Corneliu Serediuc
4
Et j'ai trouvé la solution. Utilisez des virgules inversées doubles pour la commande rm comme ceci: "rm -rf node.modules".
Corneliu Serediuc
23

La méthode la meilleure et la plus précise que j'ai trouvée était de télécharger le fichier bfg.jar: https://rtyley.github.io/bfg-repo-cleaner/

Exécutez ensuite les commandes:

git clone --bare https://project/repository project-repository
cd project-repository
java -jar bfg.jar --delete-folders DIRECTORY_NAME  # i.e. 'node_modules' in other examples
git reflog expire --expire=now --all && git gc --prune=now --aggressive
git push --mirror https://project/new-repository

Si vous souhaitez supprimer des fichiers, utilisez plutôt l'option delete-files:

java -jar bfg.jar --delete-files *.pyc
Kim T
la source
1
très facile :) si vous voulez vous assurer que seul un dossier spécifique est supprimé, cela vous aidera: stackoverflow.com/questions/21142986/…
emjay
9

Il semble que la réponse à jour à cela soit de ne pas utiliser filter-branchdirectement (au moins git lui-même ne le recommande plus) et de reporter ce travail à un outil externe. En particulier, git-filter-repo est actuellement recommandé. L'auteur de cet outil fournit des arguments sur pourquoi l' utilisation filter-branchpeut directement conduire à des problèmes.

La plupart des scripts multi-lignes ci-dessus à supprimer dirde l'historique peuvent être réécrits comme suit:

git filter-repo --path dir --invert-paths

Apparemment, l'outil est plus puissant que cela. Vous pouvez appliquer des filtres par auteur, e-mail, nom de référence, etc. ( page de manuel complète ici ). De plus, c'est rapide . L'installation est facile - elle est distribuée dans une variété de formats .

André Anjos
la source
Bel outil! Fonctionne bien sur Ubuntu 20.04, vous pouvez le faire pip3 install git-filter-repocar il est uniquement compatible avec stdlib et n'installe aucune dépendance. Sur Ubuntu 18, il est incompatible avec la version git de la distribution Error: need a version of git whose diff-tree command has the --combined-all-paths option, mais il est assez facile de l'exécuter sur undocker run -ti ubuntu:20.04
kubanczyk
7

Complétez la recette copier-coller, en ajoutant simplement les commandes dans les commentaires (pour la solution copier-coller), après les avoir testées:

git filter-branch --tree-filter 'rm -rf node_modules' --prune-empty HEAD
echo node_modules/ >> .gitignore
git add .gitignore
git commit -m 'Removing node_modules from git history'
git gc
git push origin master --force

Après cela, vous pouvez supprimer la ligne "node_modules /" de .gitignore

jgbarah
la source
Pourquoi voudriez-vous alors retirer node_modulesde .gitignore? Pour qu'ils puissent à nouveau être accidentellement commis ??
Adamski
1
Il n'est pas supprimé de gitignore, il est ajouté à gitignore. Le message de validation dit "git history", pas "gitignore" :)
Danny Tuppeny
mais le commentaire dit que vous pouvez ensuite supprimer node_modulesde .gitignore.
zavr
7

Pour les utilisateurs de Windows, veuillez noter d'utiliser "au lieu de ' Aussi ajouté -fpour forcer la commande si une autre sauvegarde est déjà là.

git filter-branch -f --tree-filter "rm -rf FOLDERNAME" --prune-empty HEAD
git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
echo FOLDERNAME/ >> .gitignore
git add .gitignore
git commit -m "Removing FOLDERNAME from git history"
git gc
git push origin master --force
kcode
la source
3

J'ai supprimé les dossiers bin et obj des anciens projets C # en utilisant git sur windows. Attention à

git filter-branch --tree-filter "rm -rf bin" --prune-empty HEAD

Il détruit l'intégrité de l'installation de git en supprimant le dossier usr / bin dans le dossier d'installation de git.

LordObi
la source