2016-06-09 8 views
6

Sto lavorando a un corso di git e volevo menzionare che gli errori persi non sono davvero persi fino a quando è in esecuzione git gc. Ma verificando questo, ho scoperto che non è questo il caso. Anche dopo aver eseguito git gc --prune=all --aggressive gli errori persi sono ancora lì.Quando esegue esattamente gli oggetti git prune: perché "git gc" non rimuove i commit?

Chiaramente ho frainteso qualcosa. E prima di dire qualcosa di sbagliato nel corso, voglio chiarire i miei fatti! Ecco uno script di esempio illustra l'effetto:

#!/bin/bash 

git init 

# add 10 dummy commits 
for i in {1..10}; do 
    date > foo.txt 
    git add foo.txt 
    git commit -m "bump" foo.txt 
    sleep 1 
done; 

CURRENT=$(git rev-parse HEAD) 
echo HEAD before reset: ${CURRENT} 

# rewind 
git reset --hard HEAD~5 

# add another 10 commits 
for i in {1..10}; do 
    date > foo.txt 
    git add foo.txt 
    git commit -m "bump" foo.txt 
    sleep 1 
done; 

Questo script aggiungerà 10 fittizio impegna, ripristinare a 5 commit nel passato e aggiungere altri 10 commit. Appena prima del ripristino, stamperà l'hash del suo attuale HEAD.

Vorrei aspettare per perdere l'oggetto in CURRENT dopo aver eseguito git gc --prune=all. Tuttavia, posso ancora eseguire git show su quell'hash.

Capisco che dopo aver eseguito git reset e aggiungendo nuovi commit, ho essenzialmente creato un nuovo ramo. Ma il mio ramo originale non ha più alcun riferimento, quindi non viene visualizzato in git log --all. Inoltre, non sarebbe stato spinto su nessun telecomando, suppongo.

La mia comprensione di git gc era che rimuove quegli oggetti. Questo non sembra essere il caso.

Perché? E quando fa esattamente rimuovere git gc oggetti?

+2

Il reflog contiene ancora riferimenti ai commit "cancellati". Fino a quando non scadono o scadono esplicitamente, non verranno eliminati. – twalberg

+0

Interessante. Ho dato uno sguardo a https://git-scm.com/docs/git-reflog e ho lanciato 'git reflog --expire = all'. Dopo di che l'oggetto era * ancora * lì. Successivamente ho eseguito un altro 'gc' ed era ancora lì. Anche un altro 'git gc --aggressive --prune = all' non ha aiutato. – exhuma

+0

È necessario '--expire = all --all', o eseguirlo su' HEAD' (predefinito) e 'master'. Oppure puoi cancellare manualmente le voci specifiche (o vedere la risposta sotto). – torek

risposta

10

Per un oggetto da potare, è necessario soddisfare i criteri due. Uno è relativo alla data/ora: deve essere stato creato abbastanza da fare da raccogliere. La parte "abbastanza tempo fa" è ciò che stai impostando con --prune=all: stai ignorando la normale impostazione "almeno due settimane fa".

Il secondo criterio è dove il tuo esperimento sta andando male. Per potare, l'oggetto deve essere irraggiungibile. Come twalberg noted in a comment, ogni tuo commit apparentemente abbandonato (e quindi i loro alberi e blob corrispondenti) viene effettivamente referenziato, attraverso le voci di "reflog" di Git.

Ci sono due voci reflog per ogni tale commit: una per HEAD, e uno per il nome del ramo a cui HEAD stesso di cui al momento del commit è stato fatto (in questo caso, il reflog per refs/heads/master, cioè ramo master). Ogni voce di riferimento ha il proprio contrassegno orario e git gc scade anche le voci di prospetto per l'utente, sebbene con un insieme di regole più complesso rispetto al semplice valore predefinito "14 giorni" per la scadenza dell'oggetto.

Quindi, git gcpotrebbe prima eliminare tutte le voci reflog che stanno mantenendo il vecchio oggetto attorno, poi potano l'oggetto. Non sta succedendo qui.

Per visualizzare o persino eliminare le voci di prospetto manualmente, utilizzare git reflog.Si noti che git reflogvisualizza le voci eseguendo git log con l'opzione -g/--walk-reflogs (più alcune opzioni di formattazione di visualizzazione aggiuntive). È possibile eseguire git reflog --all --expire=all per cancellare tutto, anche se questo è un randello quando un bisturi può essere più appropriato. Utilizzare --expire-unreachable per un po 'più selettività. Per ulteriori informazioni, vedere the git log documentation e, naturalmente, the git reflog documentation.


Alcuni file system Unix-y no la creazione di file negozio di tempo ("nascita") a tutti: il campo di una struttura statst_ctime è il inode tempo di cambio, non l'ora di creazione. Se c'è un tempo di creazione, è in st_birthtime o st_birthtimespec. Tuttavia, ogni oggetto Git è di sola lettura, quindi il tempo di creazione del file è anche il suo tempo di modifica. Quindi st_mtime, che è sempre disponibile, fornisce il tempo di creazione per l'oggetto.

Le regole esatte sono descritti in the git gc documentation, ma penso Per impostazione predefinita, il 30 giorni per impegna irraggiungibili e 90 giorni per raggiungibile commette è una sintesi decente. La definizione di raggiungibile qui è inusuale, tuttavia: significa che è raggiungibile dal valore corrente del riferimento per il quale questo valore di riferimento trattiene i vecchi valori. Cioè, se stiamo a guardare il reflog per master, troviamo il commit che master identifica (ad esempio, 1234567), poi vedere se ogni voce reflog per master (ad esempio, [email protected]{27}) è raggiungibile da quel particolare impegnarsi (1234567 di nuovo).

Questo particolare nome di confusione è stato apportato dal personale di standardizzazione POSIX. :-) Il campo st_birthtimespec è un struct timespec, che registra sia i secondi che i nanosecondi.

+0

Si noti che anche le voci di reflog vengono raccolte automaticamente. Come [la documentazione 'git gc'] (https://www.kernel.org/pub/software/scm/git/docs/git-gc.html) dice, la variabile di configurazione opzionale' gc.reflogExpire' assume come valore predefinito 90 giorni e 'gc.reflogExpireUnreachable' ha un valore predefinito di 30 giorni. Le voci raggiungibili e non raggiungibili nel reflog verranno eliminate se sono più vecchie di quelle variabili quando viene eseguito 'git gc'. –

+0

@ RoryO'Kane: giusto; L'ho lasciato ai collegamenti della documentazione, ma forse dovrei menzionarlo direttamente nella risposta? – torek

+0

Sì, penso che potrebbe essere utile indirizzare il titolo della domanda più direttamente dicendo che 'git gc' a volte rimuoverà i commit. Ciò eviterebbe anche di suggerire che 'git reflog' è l'unico comando che cancella le voci di reflog. Tuttavia, scriverlo nella tua risposta non * è * importante, dato che i lettori possono ottenere le stesse informazioni da questi commenti. –