Faire de la validation actuelle la seule validation (initiale) dans un référentiel Git?

666

J'ai actuellement un référentiel Git local, que je pousse vers un référentiel Github.

Le référentiel local a environ 10 validations, et le référentiel Github en est un double synchronisé.

Ce que je voudrais faire, c'est supprimer TOUT l'historique des versions du référentiel Git local, de sorte que le contenu actuel du référentiel apparaisse comme le seul commit (et donc les anciennes versions de fichiers dans le référentiel ne sont pas stockées).

J'aimerais ensuite transmettre ces modifications à Github.

J'ai étudié le rebase de Git, mais cela semble être plus adapté à la suppression de versions spécifiques. Une autre solution potentielle est de supprimer le référentiel local et d'en créer un nouveau - bien que cela créerait probablement beaucoup de travail!

ETA: Il existe des répertoires / fichiers spécifiques qui ne sont pas suivis - si possible, je voudrais maintenir le non-suivi de ces fichiers.

kaese
la source
6
Voir aussi stackoverflow.com/questions/435646/… ("Comment combiner les deux premiers commits d'un dépôt Git?")
Anonymoose

Réponses:

983

Voici l'approche par force brute. Il supprime également la configuration du référentiel.

Remarque : cela ne fonctionne PAS si le référentiel contient des sous-modules! Si vous utilisez des sous-modules, vous devez utiliser par exemple un rebase interactif

Étape 1: supprimer tout l'historique ( assurez-vous d'avoir une sauvegarde, cela ne peut pas être annulé )

cat .git/config  # note <github-uri>
rm -rf .git

Étape 2: reconstruire le référentiel Git avec uniquement le contenu actuel

git init
git add .
git commit -m "Initial commit"

Étape 3: appuyez sur GitHub.

git remote add origin <github-uri>
git push -u --force origin master
Fred Foo
la source
3
Merci larsmans - j'ai choisi d'utiliser ceci comme ma solution. Bien que l'initialisation du dépôt Git perd l'enregistrement de fichiers non suivis dans l'ancien dépôt, c'est probablement une solution plus simple à mon problème.
kaese
5
@kaese: Je pense que vous .gitignoredevriez les gérer, non?
Fred Foo
48
Enregistrez votre .git / config avant et restaurez-le après.
lalebarde
@lalebarde Si vous restaurez .git / config après, git commit -m "Initial commit"vous pouvez probablement ignorer la git remote add ...partie, en supposant qu'elle était déjà dans votre configuration, et passer directement à la poussée. Ça a marché pour moi.
Buttle Butkus
24
Soyez prudent avec cela si vous essayez de supprimer des données sensibles: la présence d'un seul commit dans la branche principale nouvellement poussée est trompeuse - l'historique existera toujours, il ne sera tout simplement pas accessible depuis cette branche. Si vous avez des balises, par exemple, qui pointent vers des validations plus anciennes, ces validations seront accessibles. En fait, pour tous ceux qui ont un peu de git foo, je suis sûr qu'après cette poussée de git, ils seront toujours en mesure de récupérer tout l'historique du référentiel GitHub - et si vous avez d'autres branches ou balises, alors ils ne le font pas même besoin de beaucoup de git foo.
Robert Muil
621

La seule solution qui fonctionne pour moi (et fait fonctionner les sous-modules) est

git checkout --orphan newBranch
git add -A  # Add all files and commit them
git commit
git branch -D master  # Deletes the master branch
git branch -m master  # Rename the current branch to master
git push -f origin master  # Force push master branch to github
git gc --aggressive --prune=all     # remove the old files

La suppression .git/cause toujours d'énormes problèmes lorsque j'ai des sous-modules. L'utilisation git rebase --rootme causerait en quelque sorte des conflits (et prendrait longtemps car j'avais beaucoup d'histoire).

Zeelot
la source
55
ce devrait être la bonne réponse! ajoutez juste un git push -f origin mastercomme le dernier op et le soleil brillera encore sur votre repo frais! :)
gru
2
Cela ne conserve-t-il pas les anciens commits?
Brad
4
@JonePolvora git fetch; git reset --hard origin / master stackoverflow.com/questions/4785107/…
echo
5
après cela, l'espace de stockage sera-t-il libre?
Inuart
8
Je pense que vous devriez ajouter la suggestion de @JasonGoemaat comme dernière ligne à votre réponse. Sans git gc --aggressive --prune alltout le point de perdre l'histoire serait raté.
Tuncay Göncüoğlu
93

Voici mon approche privilégiée:

git branch new_branch_name $(echo "commit message" | git commit-tree HEAD^{tree})

Cela va créer une nouvelle branche avec un commit qui ajoute tout dans HEAD. Cela ne change rien d'autre, donc c'est complètement sûr.

dan_waterworth
la source
3
Meilleure approche! Effacez et faites le travail. De plus, je renomme la branche avec beaucoup de changements de "master" en "local-work" et "new_branch_name" en "master". Dans master, procédez comme suit: git -m modifications locales git branch -m modifications locales git checkout new_branch_name git branch -m master <
Valtoni Boaventura
Cela a l'air vraiment court et élégant, la seule chose que je ne comprends pas ou que je n'ai pas encore vue est HEAD ^ {tree}, quelqu'un pourrait-il expliquer? En dehors de cela, je lirais ceci comme "créer une nouvelle branche à partir d'un commit donné, créé en créant un nouvel objet commit avec un message de commit donné de ___"
TomKeegasi
3
L'endroit ultime pour chercher des réponses aux questions sur la syntaxe de référence git est dans les git-rev-parsedocuments. Ce qui se passe ici git-commit-treenécessite une référence à un arbre (un instantané du dépôt), mais HEADest une révision. Pour trouver l'arbre associé à un commit, nous utilisons le <rev>^{<type>}formulaire.
dan_waterworth
Bonne réponse. Fonctionne bien. Enfin diregit push --force <remote> new_branch_name:<remote-branch>
Felipe Alvarez
31

L'autre option, qui pourrait s'avérer être beaucoup de travail si vous avez beaucoup de commits, est une rebase interactive (en supposant que votre version git soit> = 1.7.12):git rebase --root -i

Lorsqu'une liste de validations s'affiche dans votre éditeur:

  • Remplacez «choisir» par «reformuler» pour le premier commit
  • Changer "choisir" en "corriger" tous les autres commit

Sauver et fermer. Git va commencer le rebasage.

À la fin, vous auriez un nouveau commit racine qui est une combinaison de tous ceux qui l'ont suivi.

L'avantage est que vous n'avez pas à supprimer votre référentiel et si vous avez des doutes, vous avez toujours un repli.

Si vous voulez vraiment nuancer votre historique, réinitialisez master à ce commit et supprimez toutes les autres branches.

Carl
la source
Une fois le rebase terminé, je ne peux pas pousser:error: failed to push some refs to
Begueradj
@Begueradj si vous avez déjà poussé la branche que vous avez rebasée, vous devrez forcer la poussée git push --force-with-lease. force-with-lease est utilisé car il est moins destructeur que --force.
Carl
19

Variante de la méthode proposée par Larsmans :

Enregistrez votre liste de non-traces:

git ls-files --others --exclude-standard > /tmp/my_untracked_files

Enregistrez votre configuration git:

mv .git/config /tmp/

Effectuez ensuite les premières étapes de larsmans:

rm -rf .git
git init
git add .

Restaurez votre configuration:

mv /tmp/config .git/

Détachez vos fichiers non suivis:

cat /tmp/my_untracked_files | xargs -0 git rm --cached

Engagez ensuite:

git commit -m "Initial commit"

Et enfin, poussez vers votre référentiel:

git push -u --force origin master
lalebarde
la source
6

Vous trouverez ci-dessous un script adapté de la réponse de @Zeelot. Il devrait supprimer l'historique de toutes les branches, pas seulement de la branche principale:

for BR in $(git branch); do   
  git checkout $BR
  git checkout --orphan ${BR}_temp
  git commit -m "Initial commit"
  git branch -D $BR
  git branch -m $BR
done;
git gc --aggressive --prune=all

Cela a fonctionné pour mes besoins (je n'utilise pas de sous-modules).

Shafique Jamal
la source
4
Je pense que vous avez oublié de forcer push master à terminer la procédure.
not2qubit
2
J'ai dû faire une légère modification. git branchinclura un astérisque à côté de votre branche extraite, qui sera ensuite globglé, ce qui entraînera sa résolution dans tous les fichiers ou dossiers comme s'il s'agissait également de noms de branche. Au lieu de cela, j'ai utilisé git branch --format="%(refname:lstrip=2)"ce qui m'a donné uniquement les noms des succursales.
Ben Richards
@ not2qubit: Merci pour cela. Quelle serait la commande exacte? git push --force origin master, ou git push --force-with-lease? Apparemment, ce dernier est plus sûr (voir stackoverflow.com/questions/5509543/… )
Shafique Jamal
@BenRichards. Intéressant. Je vais réessayer à un moment donné avec un dossier qui correspond à un nom de branche pour le tester, puis mettre à jour la réponse. Merci.
Shafique Jamal
5

Vous pouvez utiliser des clones peu profonds (git> 1.9):

git clone --depth depth remote-url

Pour en savoir plus: http://blogs.atlassian.com/2014/05/handle-big-repositories-git/

Matthias M
la source
4
Un tel clone ne peut pas être poussé vers un nouveau référentiel.
Seweryn Niemiec
1
Il serait utile de savoir comment contourner cette limitation. Quelqu'un peut-il expliquer pourquoi cela ne peut pas être forcé?
not2qubit
La réponse à votre question: stackoverflow.com/questions/6900103/…
Matthias M
4

git filter-branch est l'outil de chirurgie majeure.

git filter-branch --parent-filter true -- @^!

--parent-filterobtient les parents sur stdin et devrait imprimer les parents réécrits sur stdout; unix truesort avec succès et n'imprime rien, donc: pas de parents. @^!est l' abréviation de Git pour "le responsable principal mais pas l'un de ses parents". Supprimez ensuite toutes les autres références et appuyez à loisir.

jthill
la source
3

Supprimez simplement le dépôt Github et créez-en un nouveau. De loin l'approche la plus rapide, la plus simple et la plus sûre. Après tout, qu'avez-vous à gagner à exécuter toutes ces commandes dans la solution acceptée lorsque tout ce que vous voulez, c'est la branche principale avec un seul commit?

AndroidDev
la source
1
L'un des principaux points est de pouvoir voir d'où il provient.
not2qubit
Je viens de le faire et ça va
thanos.a
2

La méthode ci-dessous est exactement reproductible, il n'est donc pas nécessaire de réexécuter le clone si les deux côtés étaient cohérents, il suffit d'exécuter le script de l'autre côté également.

git log -n1 --format=%H >.git/info/grafts
git filter-branch -f
rm .git/info/grafts

Si vous souhaitez ensuite le nettoyer, essayez ce script:

http://sam.nipl.net/b/git-gc-all-ferocious

J'ai écrit un script qui "tue l'historique" pour chaque branche du référentiel:

http://sam.nipl.net/b/git-kill-history

voir aussi: http://sam.nipl.net/b/confirm

Sam Watkins
la source
1
Merci pour cela. Juste pour info: votre script pour tuer l'historique de chaque branche pourrait utiliser une mise à jour - il donne les erreurs suivantes: git-hash: not foundetSupport for <GIT_DIR>/info/grafts is deprecated
Shafique Jamal
1
@ShafiqueJamal, merci, le petit script "git-hash" est git log HEAD~${1:-0} -n1 --format=%H, ici, sam.aiki.info/b/git-hash Il serait préférable de tout mettre dans un script pour la consommation publique. Si je l'utilise à nouveau, je pourrais trouver comment le faire avec la nouvelle fonctionnalité qui remplace les "greffons".
Sam Watkins
2

Ce que je voudrais faire, c'est supprimer TOUT l'historique des versions du référentiel Git local, de sorte que le contenu actuel du référentiel apparaisse comme le seul commit (et donc les anciennes versions de fichiers dans le référentiel ne sont pas stockées).

Une réponse plus conceptuelle:

git récupère automatiquement les anciens commits si aucune balise / branche / référence ne pointe vers eux. Il vous suffit donc de supprimer toutes les balises / branches et de créer un nouveau commit orphelin, associé à n'importe quelle branche - par convention, vous laisseriez la branchemaster pointer vers ce commit.

Les anciens commits inaccessibles ne seront alors plus jamais vus par quiconque à moins qu'ils ne creusent avec des commandes git de bas niveau. Si cela vous suffit, je m'arrête là et je laisse le GC automatique faire son travail quand il le souhaite. Si vous voulez vous en débarrasser tout de suite, vous pouvez utiliser git gc(éventuellement avec --aggressive --prune=all). Pour le référentiel git distant, il n'y a aucun moyen pour vous de forcer cela, sauf si vous avez un accès shell à leur système de fichiers.

AnoE
la source
Un bel ajout, vu dans le contexte de la réponse de @Zeelot.
Mogens TrasherDK
Ouaip, Zeelot a les commandes qui font essentiellement cela (juste différemment, en recommençant complètement, ce qui pourrait être bien pour OP). @MogensTrasherDK
AnoE
0

Voici:

#!/bin/bash
#
# By Zibri (2019)
#
# Usage: gitclean username password giturl
#
gitclean () 
{ 
    odir=$PWD;
    if [ "$#" -ne 3 ]; then
        echo "Usage: gitclean username password giturl";
        return 1;
    fi;
    temp=$(mktemp -d 2>/dev/null /dev/shm/git.XXX || mktemp -d 2>/dev/null /tmp/git.XXX);
    cd "$temp";
    url=$(echo "$3" |sed -e "s/[^/]*\/\/\([^@]*@\)\?\.*/\1/");
    git clone "https://$1:$2@$url" && { 
        cd *;
        for BR in "$(git branch|tr " " "\n"|grep -v '*')";
        do
            echo working on branch $BR;
            git checkout $BR;
            git checkout --orphan $(basename "$temp"|tr -d .);
            git add -A;
            git commit -m "Initial Commit" && { 
                git branch -D $BR;
                git branch -m $BR;
                git push -f origin $BR;
                git gc --aggressive --prune=all
            };
        done
    };
    cd $odir;
    rm -rf "$temp"
}

Également hébergé ici: https://gist.github.com/Zibri/76614988478a076bbe105545a16ee743

Zibri
la source
Gah! Ne me faites pas fournir mon mot de passe non caché et non protégé sur la ligne de commande! De plus, la sortie de git branch est généralement mal adaptée aux scripts. Vous voudrez peut-être regarder les outils de plomberie.
D. Ben Knoble
-1

J'ai résolu un problème similaire en supprimant simplement le .gitdossier de mon projet et en réintégrant le contrôle de version via IntelliJ. Remarque: le .gitdossier est masqué. Vous pouvez l'afficher dans le terminal avec ls -a, puis le supprimer à l'aide de rm -rf .git.

JB Lovell
la source
c'est ce qu'il fait à l'étape 1: rm -rf .git?
nuits
-1

Pour cela, utilisez la commande Shallow Clone git clone --depth 1 URL - Il clone uniquement le HEAD actuel du référentiel

kkarki
la source
-2

Pour supprimer le dernier commit de git, vous pouvez simplement exécuter

git reset --hard HEAD^ 

Si vous supprimez plusieurs validations par le haut, vous pouvez exécuter

git reset --hard HEAD~2 

pour supprimer les deux derniers commits. Vous pouvez augmenter le nombre pour supprimer encore plus de validations.

Plus d'infos ici.

Git tutoturial fournit ici de l' aide sur la façon de purger le référentiel:

vous souhaitez supprimer le fichier de l'historique et l'ajouter au .gitignore pour vous assurer qu'il n'est pas réaffecté accidentellement. Pour nos exemples, nous allons supprimer Rakefile du référentiel GitHub gem.

git clone https://github.com/defunkt/github-gem.git

cd github-gem

git filter-branch --force --index-filter \
  'git rm --cached --ignore-unmatch Rakefile' \
  --prune-empty --tag-name-filter cat -- --all

Maintenant que nous avons supprimé le fichier de l'historique, assurons-nous de ne pas le commettre à nouveau accidentellement.

echo "Rakefile" >> .gitignore

git add .gitignore

git commit -m "Add Rakefile to .gitignore"

Si vous êtes satisfait de l'état du référentiel, vous devez forcer les modifications pour écraser le référentiel distant.

git push origin master --force
kiriloff
la source
6
Supprimer des fichiers ou des validations du référentiel n'a absolument aucun rapport avec la question (qui demande de supprimer l'historique, une chose complètement différente). L'OP souhaite un historique clair mais souhaite conserver l'état actuel du référentiel.
Victor Schröder
cela ne produit pas le résultat demandé dans la question. vous supprimez toutes les modifications après la validation que vous conservez en dernier et perdez toutes les modifications depuis, mais la question demande de conserver les fichiers actuels et de supprimer l'historique.
Tuncay Göncüoğlu