Come aggiornare un clone git shallow?

sfondo

(per tl; dr, vedi #questioni di seguito)

Ho più cloni superficiali di repository git. Sto usando dei cloni poco profondi perché è molto più piccolo rispetto a un clone sporco. Ciascuno è clonato facendo su git clone --single-branch --depth 1 .

Funziona bene, tranne che non vedo come aggiornarlo.

Quando sto clonando da un tag, l’aggiornamento non è significativo, poiché un tag è congelato nel tempo (come ho capito). In questo caso, se voglio aggiornare, questo significa che voglio clonare con un altro tag, quindi ho solo rm -rf e clonato di nuovo.

Le cose diventano più complicate quando ho clonato l’HEAD di un ramo master, poi voglio aggiornarlo.

Ho provato git pull --depth 1 ma sebbene non debba git pull --depth 1 nulla al repository remoto, si lamenta di non sapere chi sono.

Ho provato git fetch --depth 1 , ma anche se sembra che aggiorni qualcosa, ho verificato che non sia aggiornato (alcuni file sul repository remoto hanno un contenuto diverso da quelli sul mio clone).

Dopo https://stackoverflow.com/a/20508591/279335 , ho provato git fetch --depth 1; git reset --hard origin/master git fetch --depth 1; git reset --hard origin/master , ma due cose: prima non capisco perché git reset è necessario, in secondo luogo, anche se i file sembrano essere aggiornati, alcuni vecchi file rimangono, e git clean -df non cancella questi file.

Domande

Lascia che un clone git clone --single-branch --depth 1 creato con git clone --single-branch --depth 1 . Come aggiornarlo per ottenere lo stesso risultato di rm -rf ; git clone --single-branch --depth 1 rm -rf ; git clone --single-branch --depth 1 ? O è rm -rf e clona di nuovo l’unico modo?

Nota

Questo non è un duplicato di Come aggiornare un sottomodulo clonato poco profondo senza aumentare le dimensioni del repository principale , in quanto la risposta non soddisfa le mie aspettative e sto usando repository semplici, non sotto-moduli (che non conosco).

[leggermente riformulato e formattato] Dato un clone creato con git clone --single-branch --depth 1 url directory , come posso aggiornarlo per ottenere lo stesso risultato della rm -rf directory ; git clone --single-branch --depth 1 url directory rm -rf directory ; git clone --single-branch --depth 1 url directory ?

Si noti che --single-branch è il valore predefinito quando si utilizza --depth 1 . Il (singolo) ramo è quello che dai con -b . C’è una lunga parentesi che riguarda l’uso di -b con i tag, ma lo lascerò per dopo. Se non usi -b , il tuo Git chiede al “upstream” Git-the Git all’URL -che il ramo ha fatto il check-out, e finge di aver usato -b thatbranch . Ciò significa che è importante prestare attenzione quando si utilizza --single-branch senza -b per assicurarsi che il ramo corrente di questo repository upstream sia sensibile, e, naturalmente, quando si usa -b , per assicurarsi che l’argomento branch sia dare veramente un nome a un ramo, non un tag.

La semplice risposta è fondamentalmente questa, con due piccole modifiche:

Dopo https://stackoverflow.com/a/20508591/279335 , ho provato git fetch --depth 1; git reset --hard origin/master git fetch --depth 1; git reset --hard origin/master , ma due cose: prima non capisco perché git reset è necessario, in secondo luogo, anche se i file sembrano essere aggiornati, alcuni vecchi file rimangono, e git clean -df non cancella questi file.

Le due piccole modifiche sono: assicurati di usare origin/ branchname e aggiungi -x ( git clean -d -f -x o git clean -dfx ) al passo git clean . Per quanto riguarda il motivo , ciò diventa un po ‘più complicato.

Cosa sta succedendo

Senza --depth 1 , il passo git fetch richiama l’altro Git e ottiene da esso un elenco di nomi di rami e corrispondenti hash ID di commit. Cioè, trova una lista di tutti i rami dell’upstream e dei loro commit correnti. Quindi, dato che hai un repository --single-branch , il tuo Git butta fuori tutto tranne il singolo ramo, e porta su tutto ciò che Git ha bisogno per connettere il commit corrente ai commit che hai già nel tuo repository.

Con --depth 1 , il tuo Git non si preoccupa di connettere il nuovo commit a vecchi commit storici. Invece, ottiene solo il commit e gli altri oggetti Git necessari per completare quell’unico commit. Quindi scrive una voce aggiuntiva “shallow graft” per contrassegnare quella commit come un nuovo commit pseudo-root.

Clone (non superficiale) regolare e recupero

Questi sono tutti legati al comportamento di Git quando si utilizza un clone normale (non superficiale, non a un ramo): git fetch richiama il Git upstream, ottiene un elenco di tutto e quindi rimanda a tutto ciò che non si fa. Ho già . Questo è il motivo per cui un clone iniziale è così lento, e un fetch-to-update è solitamente così veloce: una volta ottenuto un clone completo, gli aggiornamenti raramente hanno molto da portare: forse alcuni commit, forse qualche centinaio, e la maggior parte di questi commit non ha bisogno di molto altro.

La cronologia di un repository è formata dai commit. Ogni commit nomina il suo genitore commit (o per merges, parent commit, plurale), in una catena che va all’indietro da “l’ultimo commit”, al commit precedente, a un commit più ancestrale e così via. La catena alla fine si ferma quando raggiunge un commit che non ha un genitore, come il primo commit mai fatto nel repository. Questo tipo di commit è un commit di root .

Cioè, possiamo disegnare un grafico di commit. In un repository davvero semplice il grafico è solo una linea retta, con tutte le frecce puntate all’indietro:

 o <- o <- o <- o <-- master 

Il nome master indica il quarto e ultimo commit, che punta al terzo, che punta al secondo, che punta al primo.

Ogni commit porta con sé un'istantanea completa di tutti i file che vanno in quel commit. I file che non sono affatto cambiati sono condivisi tra questi commit: il quarto commit solo "prende in prestito" la versione invariata dal terzo commit, che "prende in prestito" dal secondo, e così via. Quindi, ogni commit nomina tutti gli "oggetti Git" di cui ha bisogno, e Git trova gli oggetti localmente - perché li ha già - o usa il protocollo fetch per portarli dall'altra, Git upstream. C'è un formato di compressione chiamato "packing" e una variante speciale per il trasferimento di rete chiamata "thin packs", che consente a Git di farlo ancora meglio / amatore, ma il principio è semplice: Git ha bisogno di tutti, e solo, di quegli oggetti che vanno con i nuovi commit sta prendendo piede. Il tuo Git decide se ha quegli oggetti, e in caso contrario li ottiene dal loro Git.

Un grafico più complicato e più completo generalmente ha diversi punti in cui si dirama, alcuni in cui si fonde e più nomi di rami che puntano a diversi consigli di ramo:

  o--o <-- feature/tall / o--o--o---o <-- master \ / o--o <-- bug/short 

Qui branch bug/short viene riunito in master , mentre branch feature/tall è ancora in fase di sviluppo. Il nome bug/short può (probabilmente) essere cancellato completamente: non ne abbiamo più bisogno se abbiamo finito di fare commit su di esso. Il commit sulla punta dei nomi master due commit precedenti, incluso il commit sulla punta di bug/short , quindi recuperando il master recupereremo il bug/short commit bug/short .

Nota che sia il grafico semplice che quello leggermente più complicato hanno ciascuno un solo commit di root. Questo è piuttosto tipico: tutti i repository che hanno commit hanno almeno un commit di root, dal momento che il primo commit è sempre un commit di root; ma la maggior parte dei repository ha anche un solo commit di root. Tuttavia, è ansible avere commit radice diversi, come con questo grafico:

  o--o \ o--o--o <-- master 

o questo:

  o--o <-- orphan o--o <-- master 

Infatti, quello con un solo master stato probabilmente creato unendo orphan in master , quindi cancellando il nome orphan .

Innesti e sostituzioni

Git ha da tempo un supporto (probabilmente tremolante) per gli innesti , che è stato sostituito con un supporto (molto migliore, in realtà solido) per le sostituzioni generiche. Per afferrarli concretamente dobbiamo aggiungere, a quanto sopra, la nozione che ogni commit ha il suo ID univoco. Questi ID sono i brutti hash SHA-1 di 40 caratteri, face0ff... e così via. In effetti, ogni object Git ha un ID univoco, sebbene per scopi grafici, tutto ciò che ci interessa è il commit.

Per disegnare i grafici, questi grandi hash ID sono troppo dolorosi da usare, quindi possiamo usare i nomi di A sola lettera dalla A alla Z Usiamo di nuovo questo grafico ma inseriamo nomi di una sola lettera:

  E--H <-- feature/tall / A--B--D---G <-- master \ / C--F <-- bug/short 

Commit H fa riferimento a commit E ( E è il genitore di H ). Commit G , che è un commit di merge - nel senso che ha almeno due genitori - fa riferimento a D e F , e così via.

Si noti che i nomi dei rami, feature/tall , master e bug/short , ogni punto a un singolo commit . Il nome bug/short punti bug/short per commettere F Questo è il motivo per cui commit F è su bug/short ramo bug/short ... ma così è commit C Commit C è su bug/short perché è raggiungibile dal nome. Il nome ci porta a F , e F ci porta a C , quindi C è su bug/short ramo bug/short .

Nota, comunque, che commit G , la punta del master , ci fa commettere F Ciò significa che commit F è anche su master ramo. Questo è un concetto chiave in Git: i commit possono essere su uno , molti o addirittura nessun ramo. Un nome di ramo è semplicemente un modo per iniziare all'interno di un grafico di commit. Ci sono altri modi, come i nomi dei tag, refs/stash (che ti porta allo stash corrente: ogni stash è in realtà un paio di commit), e i reflog (che normalmente sono nascosti alla vista perché normalmente sono solo disordine).

Questo, tuttavia, ci porta a innesti e sostituzioni. Un trapianto è solo un tipo limitato di sostituzione e i depositi superficiali utilizzano una forma limitata di innesto. 1 Non descriverò completamente le sostituzioni in quanto sono un po 'più complicate, ma in generale, ciò che Git fa per tutti questi è usare l'innesto o la sostituzione come "al posto di". Per il caso specifico di commit , quello che vogliamo qui è essere in grado di cambiare - o almeno, far finta di cambiare - l'ID genitore o gli ID di ogni commit ... e per i repository poco profondi , vogliamo essere in grado di fingere che l'impegno in questione non ha genitori.


1 Il modo in cui i repository superficiali utilizzano il codice dell'innesto non è traballante. Per il caso più generale, ho consigliato invece di usare git replace , in quanto anch'esso era e non è traballante. L'unico uso raccomandato per gli innesti è - o almeno lo era, anni fa - per metterli sul posto giusto il tempo necessario per eseguire git filter-branch per copiare una storia innestata alterata, dopodiché dovresti semplicemente scartare completamente la storia innestata. Puoi usare git replace per questo scopo, ma a differenza degli innesti, puoi usare git replace permanente o semi-permanente, senza bisogno di git filter-branch .


Fare un clone superficiale

Per creare un clone shallow-depth dello stato attuale del repository upstream, sceglieremo uno dei tre nomi feature/tall branch- feature/tall , master o bug/short -and lo traduciamo in un commit ID. Quindi scriveremo una voce di trapianto speciale che dice: "Quando vedi che commetti, fai finta che non abbia commit da parte del genitore, cioè, è un commit di root."

Diciamo che selezioniamo il master . Il nome master punta al commit di G , quindi per creare un clone poco profondo di commit G , otteniamo come sempre il commit G del git upstream, ma poi scriviamo una voce di innesto speciale che afferma che il commit G non ha genitori. Lo abbiamo inserito nel nostro repository e ora il nostro grafico ha il seguente aspetto:

 G <-- master, origin/master 

Questi ID genitore sono ancora effettivamente all'interno di G ; è solo che ogni volta che Git ci usa o ci mostra la cronologia, immediatamente "innesta" nulla su tutto, in modo che G sembri un commit di root, per scopi di cronologia.

Aggiornare un clone superficiale che abbiamo creato in precedenza

Ma cosa succede se abbiamo già un clone (di profondità 1 superficiale) e vogliamo aggiornarlo ? Beh, non è davvero un problema. Diciamo che abbiamo creato un clone superficiale del controcorrente quando il master indicava di eseguire il commit di B , prima dei nuovi rami e della correzione del bug. Ciò significa che attualmente abbiamo questo:

 B <-- master, origin/master 

Mentre il vero genitore di B è A , abbiamo una voce d'innesto di clone superficiale che dice "fingere B è un commit di radice". Ora git fetch --depth 1 , che cerca il master dell'upstream - la cosa che chiamiamo origin/master - e vede commit G Impugniamo G da monte, insieme ai suoi oggetti, ma deliberatamente non afferriamo i commit D e F Aggiorniamo quindi le voci di graft shallow-clone per dire "fingere che G sia un commit di root":

 B <-- master G <-- origin/master 

Il nostro repository ora ha due commit di root: il nome master (ancora) punta a commit B , i cui genitori (ancora) fingiamo inesistenti, e il nome origin/master punta a G , i cui genitori fingiamo inesistenti.

Questo è il motivo per cui è necessario git reset

In un normale repository, potresti usare git pull , che in realtà è git fetch seguito da git merge . Ma la git merge richiede la cronologia, e non ne abbiamo alcuna: abbiamo falsificato Git con i commit di finta radice e non hanno una storia alle spalle. Quindi dobbiamo usare git reset invece.

Quello che git reset fa è un po 'complicato, perché può influenzare fino a tre cose diverse: un nome di ramo , l' indice e l' albero di lavoro . Abbiamo già visto quali sono i nomi delle filiali: puntano semplicemente a un commit (uno specifico), che chiamiamo la punta del ramo. Questo lascia l'indice e l'albero del lavoro.

L' albero del lavoro è facile da spiegare: è dove sono tutti i tuoi file. Questo è tutto: niente di più e niente di meno. È lì che puoi effettivamente usare Git: Git significa archiviare ogni commit mai realizzato, per sempre, in modo che possano essere tutti recuperati. Ma sono in un formato inutile ai comuni mortali. Per essere usato , un file - o più tipicamente, il valore di un intero commit di file - deve essere estratto nel suo formato normale. L'albero del lavoro è dove ciò accade, quindi puoi lavorarci sopra e fare nuovi commit usando anche questo.

L' indice è un po 'più difficile da spiegare. È qualcosa di peculiare a Git: gli altri sistemi di controllo delle versioni non ne hanno uno, o se hanno qualcosa del genere, non lo espongono. Git fa. L'indice di Git è essenzialmente il punto in cui si mantiene il prossimo commit da fare, ma ciò significa che inizia tenendo premuto il commit corrente che è stato estratto nell'albero di lavoro, e Git lo usa per rendere Git veloce. Ne parleremo di più tra un po '.

Quello che git reset --hard fa è quello di influenzare tutti e tre : nome del ramo, indice e albero di lavoro. Sposta il nome del ramo in modo che punti a un commit (probabilmente diverso). Quindi aggiorna l'indice per farlo corrispondere e aggiorna l'albero del lavoro in modo che corrisponda al nuovo indice.

Quindi:

 git reset --hard origin/master 

dice a Git di cercare origin/master . Dato che abbiamo eseguito il nostro git fetch , ora punta a commettere G Git quindi fa sì che il nostro master - il nostro attuale (e solo) ramo - punti anche a commit G , e quindi aggiorni il nostro indice e la nostra struttura. Il nostro grafico ora si presenta così:

 B [abandoned - but see below] G <-- master, origin/master 

Ora il master e l' origin/master entrambi denominano commit G e commit G è quello estratto nell'albero di lavoro.

Perché hai bisogno di git clean -dfx

La risposta qui è un po 'complicata, ma di solito è "tu non lo fai" (è necessario git clean ).

Quando hai bisogno di git clean , è perché tu, o qualcosa che hai eseguito, hai aggiunto dei file al tuo albero di lavoro di cui non hai parlato a Git. Questi sono file non tracciati e / o ignorati . Usando git clean -df verranno rimossi i file non git clean -df (e le directory vuote); aggiungendo -x rimuoverà anche i file ignorati.

Per ulteriori informazioni sulla differenza tra "non tracciato" e "ignorato", vedere questa risposta .

Perché non hai bisogno di git clean : l'indice

Ho già detto che di solito non è necessario eseguire git clean . Questo è a causa dell'indice. Come ho detto prima, l'indice di Git è principalmente "il prossimo impegno a fare". Se non aggiungi mai i tuoi file, se stai semplicemente utilizzando git checkout per controllare vari commit esistenti che hai già avuto, o che hai aggiunto con git fetch ; o se stai usando git reset --hard per spostare il nome di un ramo e anche passare l'indice e l'albero di lavoro a un altro commit, allora tutto quello che c'è nell'indice adesso è lì perché un precedente git checkout (o git reset ) lo mise nell'indice, e anche nell'albero del lavoro.

In altre parole, l'indice ha un accesso breve e veloce a Git - riepilogo o manifesto che descrive l'albero del lavoro corrente. Git usa quello per sapere cosa c'è nell'albero di lavoro ora. Quando chiedi a Git di passare a un altro commit, tramite git checkout o git reset --hard , Git può confrontare rapidamente l'indice esistente con il nuovo commit. Qualsiasi file che è stato modificato , Git deve estrarre dal nuovo commit (e aggiornare l'indice). Qualsiasi file che è stato appena aggiunto , Git deve anche estrarre (e aggiornare l'indice). Tutti i file che sono andati, che sono nell'indice esistente, ma non nel nuovo commit, Git deve rimuovere ... ed è ciò che fa Git. Git aggiorna, aggiunge e rimuove tali file nell'albero di lavoro, come indicato dal confronto tra l'indice corrente e il nuovo commit.

Ciò significa che se hai bisogno di git clean , devi aver fatto qualcosa al di fuori di Git che ha aggiunto i file. Questi file aggiunti non sono nell'indice, quindi per definizione sono non tracciati e / o ignorati. Se sono semplicemente non tracciati, git clean -f li rimuove, ma se vengono ignorati, solo git clean -fx li rimuove. (Volete -d solo per rimuovere le directory che sono o diventano vuote durante la pulizia.)

Commits abbandonati e garbage collection

Ho accennato, e ho disegnato nel grafico superficiale aggiornato, che quando abbiamo git fetch --depth 1 e poi git reset --hard , finiamo per abbandonare il precedente depth-1 graph graph commit. (Nel grafico che ho disegnato, questo è stato commesso B ). Tuttavia, in Git, i commit abbandonati sono raramente veramente abbandonati, almeno non immediatamente. Invece, alcuni nomi speciali come ORIG_HEAD appendono a loro per un po ', e ogni riferimento - i rami e i tag sono forms di riferimento - porta con sé un registro di "valori precedenti".

È ansible visualizzare ciascun reflog con git reflog refname . Ad esempio, git reflog master mostra non solo quali nomi master commit ora , ma anche quali commit ha nominato in passato . C'è anche un reflog per HEAD stesso, che è quello che git reflog mostra di default.

Le voci di Reflog scadono. La loro durata esatta varia, ma per impostazione predefinita possono essere scaduti dopo 30 giorni in alcuni casi e 90 giorni in altri. Una volta scadute, quelle voci di reflog non proteggono più i commit abbandonati (o, per i riferimenti di tag annotati, i tag object tag annotati non dovrebbero spostarsi, quindi questo caso non dovrebbe verificarsi, ma se lo fa, se forzate Git per spostare un tag: è gestito solo nello stesso modo di tutti gli altri riferimenti).

Una volta che qualsiasi object Git-commit, tag annotato, "albero" o "blob" (file) -è davvero non referenziato, a Git è permesso di rimuoverlo per davvero. 2 È solo a questo punto che i dati di repository sottostanti per i commit e i file scompaiono. Anche allora, succede solo quando qualcosa git gc viene eseguito. Quindi, un repository superficiale aggiornato con git fetch --depth 1 non è esattamente lo stesso di un clone nuovo con --depth 1 : il repository superficiale probabilmente ha alcuni nomi persistenti per i commit originali e non rimuoverà gli oggetti repository extra fino a quando questi nomi scadono o sono altrimenti cancellati.


2 Oltre al controllo di riferimento, gli oggetti ottengono un tempo minimo prima della scadenza. Il valore predefinito è due settimane. Ciò impedisce a git gc di eliminare oggetti temporanei creati da Git, ma deve ancora stabilire un riferimento a. Ad esempio, quando si effettua un nuovo commit, Git prima trasforma l'indice in una serie di oggetti ad tree che si riferiscono l'un l'altro ma non hanno riferimenti di livello superiore. Quindi crea un nuovo object commit che fa riferimento all'albero di livello superiore, ma ancora non fa riferimento al commit. Infine, aggiorna il nome attuale del ramo. Fino a quando non termina l'ultimo passo, gli alberi e il nuovo commit sono irraggiungibili!


Considerazioni speciali per i --single-branch e / o poco profondi

Ho notato sopra che il nome che date a git clone -b può riferirsi a un tag . Per i cloni normali (non superficiali o non a un ramo), questo funziona come ci si aspetterebbe: si ottiene un clone regolare, quindi Git esegue un git checkout con il nome del tag. Il risultato è il solito TESTO distaccato, in un clone perfettamente ordinario.

Con i cloni superficiali o con un solo ramo, tuttavia, ci sono molte conseguenze insolite. Questi sono tutti, in una certa misura, il risultato di Git che lascia trasparire l'implementazione.

Innanzitutto, se si utilizza --single-branch , Git modifica la normale configurazione di fetch nel nuovo repository. La normale configurazione di fetch dipende dal nome scelto per il telecomando , ma il default è l' origin quindi userò solo l' origin qui. Si legge:

 fetch = +refs/heads/*:refs/remotes/origin/* 

Di nuovo, questa è la normale configurazione per un clone normale (non a singolo ramo). Questa configurazione dice a git fetch cosa scaricare , che è "all branches". Quando si utilizza --single-branch , si ottiene invece una linea di recupero che fa riferimento solo a un ramo:

 fetch = +refs/heads/zorg:refs/remotes/origin/zorg 

se stai clonando il ramo di zorg .

Qualunque ramo cloni, è quello che entra nella linea di fetch . Ogni futuro git fetch obbedirà a questa linea, 3 quindi non recupererai nessun altro ramo. Se vuoi recuperare altri rami in un secondo momento, dovrai modificare questa linea o aggiungere più linee.

Secondo, se usi --single-branch e quello che cloni è un tag , Git inserirà una linea di fetch piuttosto strana. Ad esempio, con git clone --single-branch -b v2.1 ... ottengo:

 fetch = +refs/tags/v2.1:refs/tags/v2.1 

Ciò significa che non avrai rami e, a meno che qualcuno non abbia spostato il tag, 4 git fetch non farà nulla !

In terzo luogo, il comportamento del tag predefinito è un po 'strano a causa del modo in cui git clone e git fetch ottengono i tag. Ricorda che i tag sono semplicemente un riferimento a un particolare commit, proprio come i rami e tutti gli altri riferimenti. Ci sono due differenze chiave tra rami e tag, tuttavia: i rami dovrebbero spostarsi (ei tag non lo sono) e le diramazioni vengono rinominate (e i tag non lo fanno).

Ricorda che in tutto quanto sopra, continuiamo a scoprire che l'altro (a monte) il master di Git diventa la nostra origin/master e così via. Questo è un esempio del processo di ridenominazione. Abbiamo anche visto, in breve, con precisione come funziona il cambio di nome, attraverso la linea fetch = : il nostro Git prende i propri refs/heads/master e lo cambia in refs/remotes/origin/master . Questo nome non è solo di aspetto diverso ( origin/master ), ma letteralmente non può essere lo stesso di nessuno dei nostri rami. Se creiamo un ramo di nome origin/master , 5 il "nome completo" di questo ramo è in realtà refs/heads/origin/master che è diverso dagli altri riferimenti completi refs/remotes/origin/master . È solo quando Git usa il nome più breve che abbiamo un ramo (regolare, locale) di origin/master e un altro ramo (di localizzazione remota) di nome origin/master . (È come essere in un gruppo in cui tutti si chiamano Bruce .)

I tag non passano attraverso tutto questo. Il tag v2.1 è appena chiamato refs/tags/v2.1 . Ciò significa che non c'è modo di separare il "loro" tag dal tag "tuo". Puoi avere il tuo tag o il suo tag. Finché nessuno muove mai un tag, questo non ha importanza: se entrambi hanno il tag, deve puntare allo stesso object . (Se qualcuno inizia a spostare tag, le cose diventano brutte).

In ogni caso, Git implementa il "normale" recupero di tag con una semplice regola: 6 quando Git ha già un commit, se alcuni nomi di tag che si impegnano, Git copia anche il tag. Con i cloni ordinari, il primo clone ottiene tutti i tag e quindi le successive operazioni di git fetch ottengono i nuovi tag. Un clone poco profondo, tuttavia, per definizione omette alcuni commit (s), vale a dire tutto sotto qualsiasi punto di innesto nel grafico. Questi commit non raccoglieranno i tag. Non possono : per avere i tag, è necessario avere il commit. Git non è permesso (tranne attraverso gli innesti poco profondi) di avere l'ID di un commit senza effettivamente avere il commit.


3 Puoi dare git fetch alcuni refspec (s) sulla riga di comando, e quelli sostituiranno il default. Questo si applica solo a un recupero di default. Puoi anche utilizzare più linee fetch = nella configurazione, ad esempio, per recuperare solo un insieme specifico di rami, sebbene il modo normale di "de-restringere" un clone di un ramo iniziale sia quello di rimettere i soliti +refs/heads/*:refs/remotes/origin/* linea di recupero.

4 Poiché i tag non dovrebbero spostarsi, potremmo semplicemente dire "questo non fa nulla". Se si muovono, però, il + nel refspec rappresenta la bandiera della forza, quindi il tag si sposta in alto.

5 Non farlo. È confusionario. Git lo gestirà bene: il ramo locale si trova nello spazio dei nomi locali e il ramo di localizzazione remota si trova nello spazio dei nomi di localizzazione remota, ma è davvero confuso.

6 Questa regola non corrisponde alla documentazione. Ho provato contro Git versione 2.10.1; i vecchi Gits potrebbero usare un metodo diverso.

Sul processo di aggiornamento clone poco profondo, vedi commit 649b0c3 modulo Git 2.12 (1 ° trim . 2017).
Quel commit è parte di:

Confida 649b0c3 , commit f2386c6 , commit 6bc3d8c , commit 0afd307 (06 dic 2016) di Nguyễn Thái Ngọc Duy ( pclouds ) . Vedi commit 1127b3c , commit 381aa8e (06 dic 2016) di Rasmus Villemoes ( ravi-prevas ) . (Fuso da Junio ​​C Hamano – gitster – in commit 3c9979b , 21 dic 2016)

shallow.c

Questo paint_down() fa parte del passaggio 6 di 58babff (shallow.c: gli 8 passaggi per selezionare nuovi commit per .git / shallow – 2013-12-05) .
Quando recuperiamo da un repository poco profondo, abbiamo bisogno di sapere se uno dei nuovi / aggiornati ref necessita di nuovi “shallow commit” in .git/shallow (perché non abbiamo abbastanza storia di quei ref) e quale.

La domanda al punto 6 è: quali (nuovi) commit superficiali sono richiesti in altri per mantenere la raggiungibilità in tutto il repository senza ridurre la nostra storia?
Per rispondere, contrassegniamo tutti i commit raggiungibili dai refs esistenti con UNINTERESTING (” rev-list --not --all “), contrassegna i commit poco profondi con BOTTOM, quindi per ogni nuovo / aggiornato ref, passa attraverso il grafico di commit fino a quando non colpiamo UNINTERESTING o BOTTOM, segnando il ref sul commit mentre camminiamo.

Dopo che tutto il cammino è terminato, controlliamo i nuovi commit superficiali. Se non abbiamo visto alcun nuovo riferimento segnato su un nuovo commit poco profondo, sappiamo che tutti i nuovi / aggiornati ref sono raggiungibili usando solo la nostra cronologia e .git/shallow .
L’impegno superficiale in questione non è necessario e può essere gettato via.

Quindi, il codice.

Il loop qui (per camminare attraverso i commit) è fondamentalmente:

  1. ottenere un commit dalla coda
  2. ignorare se è VISTO o NON INTERROTTO
  3. segnalo
  4. passa attraverso tutti i genitori e ..
    • 5.aa segnalo se non è mai segnato prima
    • 5.b rimettilo in coda

Quello che facciamo in questa patch è il passaggio 5a perché non è necessario.
Il commit marcato su 5a viene rimesso in coda e verrà contrassegnato al passaggio 3 alla successiva iterazione. L’unico caso in cui non verrà contrassegnato è quando il commit è già contrassegnato UNINTERESTING (5a non lo controlla), che verrà ignorato nel passaggio 2.