Come rimuovo la vecchia cronologia da un repository git?


209

Temo di non riuscire a trovare qualcosa di simile a questo particolare scenario.

Ho un repository git con molta storia: 500+ rami, 500+ tag, che risale alla metà del 2007. Contiene ~ 19.500 commit. Vorremmo rimuovere tutta la cronologia prima del 1 ° gennaio 2010, per renderla più piccola e più semplice da gestire (conserveremmo una copia completa della cronologia in un repository di archivi).

Conosco il commit che voglio essere diventato la radice del nuovo repository. Non riesco, tuttavia, a capire il git mojo corretto per troncare il repository per iniziare con quel commit. Sto indovinando una variante di

git filter-branch

coinvolgere innesti sarebbe necessario; potrebbe anche essere necessario per il trattamento di ciascuna delle oltre 200 filiali che vogliamo tenere separatamente e poi ricucire il repo di nuovo insieme (cosa che non so come fare).

Qualcuno ha mai fatto qualcosa del genere? Ho git 1.7.2.3 se questo è importante.

Risposte:


118

Basta creare un innesto del genitore del nuovo commit root su nessun genitore (o su un commit vuoto, ad esempio il commit root reale del proprio repository). Per esempioecho "<NEW-ROOT-SHA1>" > .git/info/grafts

Dopo aver creato l'innesto, ha subito effetto; dovresti essere in grado di guardare git loge vedere che i vecchi commit indesiderati sono andati via:

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

    Another message

commit 4a46bc886318679d8b15e05aea40b83ff6c3bd47 (grafted)
Author: Your Name <your.email@example.com>
Date:   Thu May 23 22:27:48 2013 +0200

    Some message

Se tutto sembra come previsto, puoi semplicemente fare un semplice git filter-branch -- --allper renderlo permanente.

ATTENZIONE: dopo aver eseguito il passaggio della diramazione del filtro , tutti gli ID di commit saranno cambiati, quindi chiunque utilizzi il vecchio repository non deve mai fondersi con nessuno che utilizza il nuovo repository.


6
Ho dovuto fare git filter-branch --tag-name-filter cat -- --allper aggiornare i tag. Ma ho anche tag più vecchi che puntano alla vecchia storia che voglio eliminare. Come posso eliminare tutti quei vecchi tag? Se non li elimino, la cronologia precedente non scompare e posso ancora vederla con gitk --all.
Craig McQueen,

9
"Basta creare un innesto del genitore della tua nuova radice senza nessun genitore" richiede qualche elaborazione. Ci ho provato e non sono riuscito a capire la sintassi per "nessun genitore". La pagina del manuale afferma che è richiesto un ID di commit principale; l'uso di tutti gli zeri mi dà solo un errore.
Marius Gedminas,

6
Nel caso in cui qualcun altro si chiedesse come funzioni esattamente, è abbastanza facile:echo "<NEW-ROOT-HASH>" > .git/info/grafts
friederbluemle

3
Sono d'accordo, spiegare cos'è un innesto sarebbe più che utile
Charles Martin,

4
Citato dalla pagina wiki collegata sugli innesti. "A partire da Git 1.6.5, è stata aggiunta la sostituzione più flessibile di git, che consente di sostituire qualsiasi oggetto con qualsiasi altro oggetto e tiene traccia delle associazioni tramite ref che possono essere spinti e tirati tra i repository." Quindi questa risposta potrebbe non essere aggiornata per le versioni correnti di git.
ThorSummoner,

130

Forse è troppo tardi per pubblicare una risposta, ma poiché questa pagina è il primo risultato di Google, potrebbe essere comunque utile.

Se vuoi liberare spazio nel tuo repository git, ma non vuoi ricostruire tutti i tuoi commit (rebase o innesto), ed essere ancora in grado di spingere / tirare / unire da persone che hanno il repository completo, puoi usare git clone shallow clone ( parametro --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

Potresti riuscire a rendere superficiale il tuo repository esistente seguendo questi passaggi:

; 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

Come rimuovere tutti i tag git local?

Ps: le versioni precedenti di git non supportavano clone / push / pull da / a repository superficiali.


9
+1 Questa è la risposta corretta per le versioni più recenti di Git. (Oh, e per favore, torna a PPCG !)
wizzwizz4,

6
Come si può cdin una cartella che è stata appena eliminata? Sento che ci sono alcune informazioni mancanti qui. Inoltre, esiste un modo per applicare queste modifiche al repository remoto?
Trogdor,

4
@Jez Questa sarebbe la risposta più votata. Questa risposta non fa per te se vuoi liberarti definitivamente della storia. È per lavorare con storie enormi.
Nessuno il

4
Per rispondere alla mia domanda: git clone file:///Users/me/Projects/myProject myClonedProject --shallow-since=2016-09-02funziona come un fascino!
Micros,

5
@Jez puoi convertire il tuo repository superficiale in uno normale eseguendo git filter-branch -- --all. Questo cambierà tutti gli hash in esso contenuti, ma dopo potrai spingerlo in un nuovo repository
Ed'ka,

61

Questo metodo è facile da capire e funziona bene. L'argomento per lo script ( $1) è un riferimento (tag, hash, ...) al commit a partire dal quale si desidera conservare la cronologia.

#!/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

NOTA che i vecchi tag rimarranno comunque presenti; quindi potrebbe essere necessario rimuoverli manualmente

osservazione: so che è quasi la stessa risposta di @yoyodin, ma qui ci sono alcuni comandi e informazioni importanti importanti. Ho provato a modificare la risposta, ma poiché si tratta di una modifica sostanziale alla risposta di @ yoyodin, la mia modifica è stata respinta, quindi ecco le informazioni!


Apprezzo le spiegazioni fornite per i comandi git prunee git gc. C'è una spiegazione per il resto dei comandi nello script? Allo stato attuale, non è chiaro quali argomenti gli vengano passati e cosa stia facendo ciascun comando. Grazie.
user5359531

2
@utente5359531 grazie per la tua osservazione, ho aggiunto altri commenti per ciascun comando. Spero che questo ti aiuti.
Chris Maes,

4
Unisci conflitti dappertutto ... non molto utile
Warpzit,

3
@Warpzit Mi sono sbarazzato dei conflitti di unione aggiungendo -pal rebasecomando, come suggerito in altre risposte
leonbloy

1
Ho seguito esattamente questo, e tutto quello che ho avuto è stata la stessa storia di prima con un nuovo ramo a partire dal commit che volevo potare con tutta la stessa storia di prima. Nessuna cronologia è stata rimossa.
DrStrangepork,

51

Prova questo metodo Come troncare la cronologia di git :

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

Ecco $1SHA-1 del commit che si desidera mantenere e lo script creerà un nuovo ramo che contiene tutti i commit tra $1e masterviene eliminata tutta la cronologia precedente. Si noti che questo semplice script presuppone che non si abbia un ramo esistente chiamato temp. Si noti inoltre che questo script non cancella i dati git per la cronologia precedente. Corri git gc --prune=all && git repack -a -f -F -ddopo aver verificato che vuoi davvero perdere tutta la storia. Potresti anche aver bisogno, rebase --preserve-mergesma ti avverto che l'implementazione git di quella funzione non è perfetta. Ispeziona i risultati manualmente se lo usi.


22
Ho provato questo, ma ho ottenuto conflitti di unione nel rebasepassaggio. Strano - non mi aspettavo che in queste circostanze fossero possibili conflitti di unione.
Craig McQueen,

2
Utilizzare git commit --allow-empty -m "Truncate history"se il commit che è stato estratto non contiene alcun file.
friederbluemle,

2
Come posso rispedirlo al master remoto? Quando lo faccio, finisco con la storia vecchia e nuova.
Rustyx,

1
Cosa dovrebbe essere "temp"? Cosa dovresti passare come argomento per questo? C'è un esempio di come dovrebbero apparire questi comandi quando li esegui? Grazie.
user5359531

1
Credo che $ 1 sia l'hash di commit. (Ci sono maggiori dettagli forniti nell'articolo collegato).
Chris Nolet,

34

In alternativa alla riscrittura della storia, considera l'utilizzo git replacecome in questo articolo del libro Pro Git . L'esempio discusso riguarda la sostituzione di un commit parent per simulare l'inizio di un albero, mantenendo comunque la cronologia completa come ramo separato per la custodia.


Sì, penso che probabilmente potresti fare ciò che volevamo con questo, se anche tu avessi rovinato il ramo della storia completa separato. (Stavamo cercando di ridurre il repository.)
ebneter,

1
Ero scoraggiato dal fatto che la risposta fosse fuori sede; ma si collega al sito GitScm e il tutorial a cui si collega è molto ben scritto e sembra direttamente al punto della domanda del PO.
ThorSummoner,

@ThorSummoner Mi dispiace per quello! Svilupperò la risposta un po 'più pienamente sul posto
Jeff Bowman,

Sfortunatamente questa non è un'alternativa alla riscrittura della storia. C'è una frase confusa all'inizio dell'articolo che probabilmente ha dato questa impressione. Potrebbe essere rimosso da questa risposta? Vedrai nell'articolo che l'autore riscrive la storia del ramo troncato, ma propone un modo per ricollegare il ramo "storia" legacy usando git replace. Credo che sia stato corretto su un'altra domanda in cui hai pubblicato questa risposta.
Mitch

1
Una discussione di git replacecontro git graftviene effettuato presso stackoverflow.com/q/6800692/873282
koppor

25

Se si desidera mantenere il repository upstream con cronologia completa , ma checkout locali più piccoli, eseguire un clone superficiale con git clone --depth=1 [repo].

Dopo aver inviato un commit, puoi farlo

  1. git fetch --depth=1per potare i vecchi impegni. Questo rende irraggiungibili i vecchi commit e i loro oggetti.
  2. git reflog expire --expire-unreachable=now --all. Per far scadere tutti i vecchi commit e i loro oggetti
  3. git gc --aggressive --prune=all per rimuovere i vecchi oggetti

Vedi anche Come rimuovere la cronologia git locale dopo un commit? .

Si noti che non è possibile trasferire questo repository "shallow" in un'altra posizione: "aggiornamento shallow non consentito". Vedi Rifiuto remoto (aggiornamento superficiale non consentito) dopo aver modificato l'URL remoto Git . Se vuoi farlo, devi rimanere con l'innesto.


1
Il punto numero 1. ha fatto la differenza per me. Saluti
Clapas

21

Avevo bisogno di leggere diverse risposte e alcune altre informazioni per capire cosa stavo facendo.

1. Ignora tutto ciò che è più vecchio di un certo commit

Il file .git/info/graftspuò definire genitori falsi per un commit. Una riga con solo un ID commit indica che il commit non ha un genitore. Se volessimo dire che ci preoccupiamo solo degli ultimi 2000 commit, possiamo digitare:

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

git rev-parse ci fornisce l'id di commit del 2000 ° genitore del commit corrente. Il comando sopra sovrascriverà il file innesto, se presente. Controlla se c'è prima.

2. Riscrivi la cronologia di Git (opzionale)

Se vuoi rendere reale questo genitore falso innestato, esegui:

git filter-branch -- --all

Cambierà tutti gli ID di commit. Ogni copia di questo repository deve essere aggiornata forzatamente.

3. Pulire lo spazio su disco

Non ho fatto il passaggio 2, perché volevo che la mia copia fosse compatibile con l'upstream. Volevo solo risparmiare un po 'di spazio su disco. Per dimenticare tutti i vecchi impegni:

git prune
git gc

Alternativa: copie superficiali

Se si dispone di una copia superficiale di un altro repository e si desidera solo risparmiare spazio su disco, è possibile eseguire l'aggiornamento .git/shallow. Ma fai attenzione che nulla punta a un commit di prima. Quindi potresti eseguire qualcosa del genere:

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

L'entrata in shallow funziona come un innesto. Ma fai attenzione a non usare innesti e shallow allo stesso tempo. Almeno, non ci sono le stesse voci, fallirà.

Se hai ancora dei vecchi riferimenti (tag, rami, testine remote) che indicano commit precedenti, non verranno ripuliti e non risparmierai più spazio sul disco.


Il supporto per <GIT_DIR> / info / innesti è obsoleto e verrà rimosso in una versione futura di Git.
Danny,

Si prega di considerare l'utilizzo git replaceinvece. Vedere stackoverflow.com/questions/6800692/...
Joel AZEMAR

3

Quando rebase o spingi verso head / master questo errore può verificarsi

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'

Per risolvere questo problema nella dashboard di Git è necessario rimuovere il ramo principale da "Rami protetti"

inserisci qui la descrizione dell'immagine

allora puoi eseguire questo comando

git push -f origin master

o

git rebase --onto temp $1 master

0

Ci sono troppe risposte che non sono attuali e alcune non spiegano completamente le conseguenze. Ecco cosa ha funzionato per me nel tagliare la storia usando l'ultimo git 2.26:

Per prima cosa crea un commit fittizio. Questo commit verrà visualizzato come primo commit nel repository troncato. Ne hai bisogno perché questo commit conterrà tutti i file di base per la cronologia che stai conservando. SHA è l'ID del commit precedente del commit che si desidera conservare (in questo esempio 8365366). La stringa "Iniziale" verrà visualizzata come messaggio di commit del primo commit. Se si utilizza Windows, digitare il comando seguente dal prompt dei comandi di Git Bash.

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

Sopra il comando stamperà SHA, ad esempio d10f7503bc1ec9d367da15b540887730db862023.

Ora basta digitare:

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

Questo prima metterà tutti i file al momento del commit 8365366nel commit fittizio d10f750. Quindi riprodurrà tutti i commit dopo 8365366 sopra d10f750. Infine master, il puntatore della diramazione verrà aggiornato per l'ultimo commit riprodotto.

Ora se vuoi spingere questi repository troncati, fallo e basta git push -f.

Poche cose da tenere a mente (si applicano ad altri metodi oltre a questo): i tag non vengono trasferiti. Mentre gli ID di commit e i timestamp vengono conservati, vedrai GitHub mostrare questi commit nell'intestazione di grumi come Commits on XY date.

Fortunatamente è possibile mantenere la cronologia troncata come "archivio" e successivamente è possibile unire nuovamente i repository ritagliati con i repository di archivio. Per fare ciò, consulta questa guida .


-3

è possibile eliminare la directory, i file e anche l'intera cronologia relativa alla directory o al file utilizzando il jar di seguito [download] e i comandi

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

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


-10
  1. rimuovere i dati git, rm .git
  2. git init
  3. aggiungi un telecomando git
  4. forza di spinta

6
che funzionerà per rimuovere TUTTA la storia, ma non per quello che ha chiesto: mantenere la storia da gennaio 2010
Chris Maes,

1
Volevo solo dire grazie perché mi ha aiutato nel mio scenario anche se questa potrebbe non essere la risposta giusta alla domanda
apnerve il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.