Comment supprimer l'ancien historique d'un référentiel git?

209

J'ai bien peur de ne rien trouver de tel que ce scénario particulier.

J'ai un référentiel git avec beaucoup d'histoire: 500+ branches, 500+ balises, remontant à la mi-2007. Il contient environ 19 500 commits. Nous aimerions supprimer tout l'historique avant le 1er janvier 2010, pour le rendre plus petit et plus facile à gérer (nous conserverions une copie complète de l'historique dans un référentiel d'archives).

Je connais le commit que je veux être devenu la racine du nouveau dépôt. Je ne peux cependant pas trouver le bon git mojo pour tronquer le dépôt pour commencer avec ce commit. Je devine une variante de

git filter-branch

impliquant des greffes serait nécessaire; il pourrait également être nécessaire de traiter chacune des branches 200+ que nous voulons garder séparément puis patcher de nouveau le repo ensemble (quelque chose que je ne sais comment faire).

Quelqu'un a-t-il déjà fait quelque chose comme ça? J'ai git 1.7.2.3 si cela compte.

Ebneter
la source

Réponses:

118

Créez simplement une greffe du parent de votre nouveau commit racine sur aucun parent (ou sur un commit vide, par exemple le vrai commit root de votre dépôt). Par exempleecho "<NEW-ROOT-SHA1>" > .git/info/grafts

Après avoir créé la greffe, elle prend effet immédiatement; vous devriez pouvoir regarder git loget voir que les anciens commits indésirables ont disparu:

$ echo 4a46bc886318679d8b15e05aea40b83ff6c3bd47 > .git/info/grafts
$ git log --decorate | tail --lines=11
commit cb3da2d4d8c3378919844b29e815bfd5fdc0210c
Author: Your Name <[email protected]>
Date:   Fri May 24 14:04:10 2013 +0200

    Another message

commit 4a46bc886318679d8b15e05aea40b83ff6c3bd47 (grafted)
Author: Your Name <[email protected]>
Date:   Thu May 23 22:27:48 2013 +0200

    Some message

Si tout semble comme prévu, vous pouvez simplement faire un simple git filter-branch -- --allpour le rendre permanent.

ATTENTION: après avoir effectué l' étape de branchement du filtre , tous les ID de validation auront changé, donc toute personne utilisant l'ancien référentiel ne doit jamais fusionner avec quiconque utilisant le nouveau référentiel.

apenwarr
la source
6
Je devais faire git filter-branch --tag-name-filter cat -- --allpour mettre à jour les balises. Mais j'ai également des balises plus anciennes pointant vers l'ancienne histoire que je veux supprimer. Comment puis-je me débarrasser de toutes ces anciennes balises? Si je ne les supprime pas, l'historique plus ancien ne disparaît pas et je peux toujours le voir avec gitk --all.
Craig McQueen
9
"Il suffit de créer une greffe du parent de votre nouveau commit racine sur aucun parent" nécessite quelques précisions. J'ai essayé cela et je n'ai pas réussi à comprendre la syntaxe de "pas de parent". La page de manuel prétend qu'un ID de validation parent est requis; utiliser tous les zéros me donne juste une erreur.
Marius Gedminas
6
Au cas où quelqu'un d'autre se demanderait comment cela fonctionne exactement, c'est assez simple:echo "<NEW-ROOT-HASH>" > .git/info/grafts
friederbluemle
3
Je suis d'accord, expliquer ce qu'est une greffe serait plus qu'utile
Charles Martin
4
Cité sur la page wiki liée sur les greffes. "Depuis Git 1.6.5, le remplacement git plus flexible a été ajouté, ce qui vous permet de remplacer n'importe quel objet par n'importe quel autre objet, et de suivre les associations via des références qui peuvent être poussées et tirées entre les dépôts." Cette réponse peut donc être obsolète pour les versions actuelles de git.
ThorSummoner
130

Il est peut-être trop tard pour publier une réponse, mais comme cette page est le premier résultat de Google, elle peut toujours être utile.

Si vous voulez libérer de l'espace dans votre dépôt git, mais ne voulez pas reconstruire tous vos commits (rebase ou greffe), tout en étant capable de pousser / tirer / fusionner à partir de personnes qui ont le dépôt complet, vous pouvez utiliser le git cloner un clone peu profond ( paramètre --depth ).

; Clone the original repo into limitedRepo
git clone file:///path_to/originalRepo limitedRepo --depth=10

; Remove the original repo, to free up some space
rm -rf originalRepo
cd limitedRepo
git remote rm origin

Vous pouvez peut-être réduire votre référentiel existant en suivant ces étapes:

; Shallow to last 5 commits
git rev-parse HEAD~5 > .git/shallow

; Manually remove all other branches, tags and remotes that refers to old commits

; Prune unreachable objects
git fsck --unreachable ; Will show you the list of what will be deleted
git gc --prune=now     ; Will actually delete your data

Comment supprimer toutes les balises locales git?

Ps: les anciennes versions de git ne prenaient pas en charge le clonage / push / pull de / vers des dépôts superficiels.

Alexandre T.
la source
9
+1 C'est la bonne réponse pour les nouvelles versions de Git. (Oh, et s'il vous plaît revenez à PPCG !)
wizzwizz4
6
Comment cdaccéder à un dossier qui vient d'être supprimé? J'ai l'impression qu'il manque des informations ici. Existe-t-il également un moyen d'appliquer ces modifications au référentiel distant?
Trogdor
4
@Jez Ce serait l'autre réponse la mieux notée. Cette réponse n'est pas pour vous si vous voulez vous débarrasser définitivement de l'histoire. C'est pour travailler avec des histoires énormes.
Personne
4
Pour répondre à ma propre question: git clone file:///Users/me/Projects/myProject myClonedProject --shallow-since=2016-09-02Fonctionne comme un charme!
Micros
5
@Jez vous pouvez convertir votre dépôt superficiel en un dépôt normal en exécutant git filter-branch -- --all. Cela va changer tous les hachages mais après cela, vous pourrez le pousser vers un nouveau
dépôt
61

Cette méthode est facile à comprendre et fonctionne bien. L'argument du script ( $1) est une référence (tag, hachage, ...) au commit à partir duquel vous souhaitez conserver votre historique.

#!/bin/bash
git checkout --orphan temp $1 # create a new branch without parent history
git commit -m "Truncated history" # create a first commit on this branch
git rebase --onto temp $1 master # now rebase the part of master branch that we want to keep onto this branch
git branch -D temp # delete the temp branch

# The following 2 commands are optional - they keep your git repo in good shape.
git prune --progress # delete all the objects w/o references
git gc --aggressive # aggressively collect garbage; may take a lot of time on large repos

NOTEZ que les anciennes balises resteront toujours présentes; vous devrez peut-être les supprimer manuellement

remarque: je sais que c'est presque la même réponse que @yoyodin, mais il y a quelques commandes et informations supplémentaires importantes ici. J'ai essayé de modifier la réponse, mais comme il s'agit d'un changement substantiel de la réponse de @ yoyodin, ma modification a été rejetée, alors voici les informations!

Chris Maes
la source
J'apprécie les explications données pour les commandes git pruneet git gc. Y a-t-il une explication pour le reste des commandes dans le script? En l'état, il n'est pas clair quels arguments lui sont transmis et ce que fait chaque commande. Merci.
user5359531
2
@ user5359531 merci pour votre remarque, j'ai ajouté quelques commentaires supplémentaires pour chaque commande. J'espère que cela t'aides.
Chris Maes
4
Fusionner les conflits partout ... pas très utile
Warpzit
3
@Warpzit Je me suis débarrassé des conflits de fusion en ajoutant -pà la rebasecommande, comme suggéré dans une autre réponse
leonbloy
1
J'ai suivi cela exactement, et tout ce que j'ai obtenu était la même histoire qu'auparavant avec une nouvelle branche commençant au commit que je voulais tailler avec la même histoire qu'auparavant. Aucun historique n'a été supprimé.
DrStrangepork
51

Essayez cette méthode Comment tronquer l'historique git :

#!/bin/bash
git checkout --orphan temp $1
git commit -m "Truncated history"
git rebase --onto temp $1 master
git branch -D temp

Voici $1SHA-1 de la validation que vous souhaitez conserver et le script créera une nouvelle branche qui contient toutes les validations entre $1et masterque tout l'historique plus ancien est supprimé. Notez que ce script simple suppose que vous n'avez pas de branche existante appelée temp. Notez également que ce script n'efface pas les données git de l'ancien historique. Exécutez git gc --prune=all && git repack -a -f -F -daprès avoir vérifié que vous voulez vraiment perdre tout l'historique. Vous pouvez également avoir besoin rebase --preserve-mergesmais soyez averti que l'implémentation git de cette fonctionnalité n'est pas parfaite. Inspectez les résultats manuellement si vous utilisez cela.

yoyodyn
la source
22
J'ai essayé cela, mais j'ai obtenu des conflits de fusion à l' rebaseétape. Étrange - je ne m'attendais pas à ce que des conflits de fusion soient possibles dans ces circonstances.
Craig McQueen
2
À utiliser git commit --allow-empty -m "Truncate history"si la validation que vous avez extraite ne contient aucun fichier.
friederbluemle
2
Comment puis-je repousser cela vers le maître distant? Quand je fais cela, je me retrouve avec une histoire ancienne et nouvelle.
rustyx
1
Quel est le «temp» censé être? Qu'est-ce que tu es censé faire comme argument pour cela? Existe-t-il un exemple de ce à quoi ces commandes sont censées ressembler lorsque vous les exécutez? Merci.
user5359531
1
Je crois que 1 $ est le hachage de validation. (Il y a plus de détails fournis dans l'article lié).
Chris Nolet
34

Comme alternative à la réécriture de l'histoire, envisagez d'utiliser git replacecomme dans cet article du livre Pro Git . L'exemple décrit implique le remplacement d'une validation parent pour simuler le début d'un arbre, tout en conservant l'historique complet en tant que branche distincte pour la conservation.

Jeff Bowman
la source
Oui, je pense que vous pourriez probablement faire ce que nous voulions avec cela, si vous supprimiez également la branche distincte de l'histoire complète. (Nous essayions de réduire le référentiel.)
Ebneter
1
J'ai été découragé par le fait que la réponse était hors site; mais il fait un lien vers le site GitScm et le tutoriel auquel il renvoie est très bien écrit et semble directement au point de la question de l'OP.
ThorSummoner
@ThorSummoner Désolé à ce sujet! Je développerai la réponse un peu plus complètement sur place
Jeff Bowman
Malheureusement, ce n'est pas une alternative à la réécriture de l'histoire. Il y a une phrase déroutante au début de l'article qui a probablement donné cette impression. Cela pourrait-il être supprimé de cette réponse? Vous verrez dans l'article que l'auteur réécrit l'historique de la branche tronquée, mais propose un moyen de rattacher l'ancienne branche "historique" en utilisantgit replace . Je crois que cela a été corrigé sur une autre question où vous avez posté cette réponse.
Mitch
1
Une discussion sur git replaceversus git graftest faite à stackoverflow.com/q/6800692/873282
koppor
25

Si vous souhaitez conserver le référentiel en amont avec un historique complet , mais des extractions locales plus petites, effectuez un clonage superficiel avecgit clone --depth=1 [repo] .

Après avoir poussé un commit, vous pouvez faire

  1. git fetch --depth=1pour tailler les vieux commits. Cela rend les anciens commits et leurs objets inaccessibles.
  2. git reflog expire --expire-unreachable=now --all. Pour expirer tous les anciens commits et leurs objets
  3. git gc --aggressive --prune=all enlever les anciens objets

Voir aussi Comment supprimer l'historique git local après un commit?.

Notez que vous ne pouvez pas pousser ce dépôt "superficiel" ailleurs: "mise à jour superficielle non autorisée". Voir Rejeté à distance (mise à jour superficielle non autorisée) après avoir changé l'URL distante de Git . Si vous le souhaitez, vous devez vous en tenir à la greffe.

koppor
la source
1
Le point numéro 1. a fait la différence pour moi. Acclamations
clapas
21

J'avais besoin de lire plusieurs réponses et quelques autres informations pour comprendre ce que je faisais.

1. Ignorez tout ce qui est plus ancien qu'un certain commit

Le fichier .git/info/graftspeut définir de faux parents pour un commit. Une ligne avec juste un ID de validation indique que la validation n'a pas de parent. Si nous voulions dire que nous ne nous soucions que des 2000 derniers commits, nous pouvons taper:

git rev-parse HEAD~2000 > .git/info/grafts

git rev-parse nous donne l'id de commit du 2000ème parent du commit actuel. La commande ci-dessus écrasera le fichier de greffons s'il est présent. Vérifiez s'il est là en premier.

2. Réécrivez l'historique Git (facultatif)

Si vous voulez faire de ce faux parent greffé un vrai, exécutez:

git filter-branch -- --all

Cela changera tous les ID de validation. Chaque copie de ce référentiel doit être mise à jour avec force.

3. Nettoyer l'espace disque

Je n'ai pas fait l'étape 2, car je voulais que ma copie reste compatible avec l'amont. Je voulais juste économiser de l'espace disque. Pour oublier tous les anciens commits:

git prune
git gc

Alternative: copies superficielles

Si vous disposez d'une copie superficielle d'un autre référentiel et que vous souhaitez simplement économiser de l'espace disque, vous pouvez mettre à jour .git/shallow. Mais attention, rien ne pointe vers un commit d'avant. Vous pouvez donc exécuter quelque chose comme ceci:

git fetch --prune
git rev-parse HEAD~2000 > .git/shallow
git prune
git gc

L'entrée en superficiel fonctionne comme une greffe. Mais attention à ne pas utiliser de greffes et peu profondes en même temps. Au moins, ne contient pas les mêmes entrées, cela échouera.

Si vous avez encore d'anciennes références (balises, branches, têtes distantes) qui pointent vers des validations plus anciennes, elles ne seront pas nettoyées et vous n'économiserez pas plus d'espace disque.

Maikel
la source
La prise en charge de <GIT_DIR> / info / greffons est obsolète et sera supprimée dans une future version de Git.
danny
Veuillez envisager d'utiliser à la git replaceplace. Voir stackoverflow.com/questions/6800692/…
Joel AZEMAR
3

Lorsque rebase ou push to head / master cette erreur peut se produire

remote: GitLab: You are not allowed to access some of the refs!
To git@giturl:main/xyz.git
 ! [remote rejected] master -> master (pre-receive hook declined)
error: failed to push some refs to 'git@giturl:main/xyz.git'

Pour résoudre ce problème dans le tableau de bord git, supprimez la branche principale des "branches protégées"

entrez la description de l'image ici

alors vous pouvez exécuter cette commande

git push -f origin master

ou

git rebase --onto temp $1 master
HMagdy
la source
0

Il y a trop de réponses ici qui ne sont pas actuelles et certaines n'expliquent pas complètement les conséquences. Voici ce qui a fonctionné pour moi pour réduire l'historique en utilisant le dernier git 2.26:

Créez d'abord un commit factice. Ce commit apparaîtra comme le premier commit de votre dépôt tronqué. Vous en avez besoin car ce commit contiendra tous les fichiers de base de l'historique que vous conservez. Le SHA est l'ID de la validation précédente de la validation que vous souhaitez conserver (dans cet exemple, 8365366). La chaîne 'Initial' apparaîtra comme message de validation du premier commit. Si vous utilisez Windows, tapez la commande ci-dessous à partir de l'invite de commande Git Bash.

# 8365366 is id of parent commit after which you want to preserve history
echo 'Initial' | git commit-tree 8365366^{tree}

Commande ci - dessus imprimera SHA, par exemple, d10f7503bc1ec9d367da15b540887730db862023.

Tapez maintenant:

# d10f750 is commit ID from previous command
git rebase --onto d10f750 8365366

Cela placera d'abord tous les fichiers à partir de la validation 8365366dans la validation fictive d10f750. Ensuite, il rejouera toutes les validations après 8365366 par dessus d10f750. Enfin master, le pointeur de branche sera mis à jour pour que le dernier commit soit lu.

Maintenant, si vous voulez pousser ces repo tronqués, faites simplement git push -f .

Peu de choses à garder à l'esprit (cela s'applique à d'autres méthodes ainsi qu'à celle-ci): les balises ne sont pas transférées. Alors que les ID de validation et les horodatages sont préservés, vous verrez GitHub afficher ces validations en en-tête comme Commits on XY date.

Heureusement, il est possible de conserver l'historique tronqué en tant qu '"archive" et, plus tard, vous pouvez joindre le référentiel réduit avec le référentiel d'archives. Pour ce faire, consultez ce guide .

Shital Shah
la source
-3

vous pouvez supprimer le répertoire, les fichiers et aussi tout l'historique lié au répertoire ou au fichier en utilisant le pot mentionné ci-dessous [le télécharger] et les commandes

Fichier bfg.jar: https://rtyley.github.io/bfg-repo-cleaner/

git clone --bare repo-url cd repo_dir java -jar bfg.jar --delete-folder dossier_name git reflog expire --expire = now --all && git gc --prune = now --aggressive git push --mirror repo_url

RahulMohan Kolakandy
la source
-10
  1. supprimer les données git, rm .git
  2. git init
  3. ajouter une télécommande git
  4. poussée forcée
Brad Reid
la source
6
cela fonctionnera pour supprimer TOUTE l'histoire, mais pas pour ce qu'il a demandé: garder l'histoire depuis janvier 2010
Chris Maes
1
Je voulais juste dire merci car cela m'a aidé dans mon scénario même si ce n'est peut-être pas la bonne réponse à la question
apnerve