Più directory di lavoro con Git?


242

Non sono sicuro che questo sia qualcosa supportato da Git, ma in teoria sembra che dovrebbe funzionare per me.

Il mio flusso di lavoro comporta spesso la modifica di file in più rami contemporaneamente. In altre parole, spesso desidero aprire alcuni file in un ramo mentre modifico il contenuto di un altro file in un altro ramo.

La mia soluzione tipica a questo è quella di fare due checkout, ma è un peccato non poter condividere rami e riferimenti tra di loro. Quello che vorrei è avere solo due directory di lavoro gestite dalla stessa cartella .git.

Sono a conoscenza delle soluzioni locali di clonazione git (l'impostazione predefinita, che è il collegamento hardlink di oggetti condivisi, e l'opzione --shared, che imposta un archivio oggetti alternativo con il repository originale), ma queste soluzioni riducono solo l'utilizzo dello spazio su disco , e specialmente nel caso di --shared, sembrano carichi di pericoli.

C'è un modo per usare una cartella .git e avere due directory di lavoro supportate da essa? O Git è hardcoded per avere solo una directory di lavoro verificata in qualsiasi momento?


1
git-new-workdirsarà sostituito da git checkout --to=<path>in Git 2.5. Vedi la mia risposta di seguito
VonC,

3
In realtà, il comando sarà git worktree add <path> [<branch>](Git 2.5 rc2). Vedi la mia risposta modificata di seguito
VonC

dovresti cambiare la risposta accettata, la risposta di VonC, poiché le cose sono cambiate da quando hai posto la domanda.
xaxxon,

grazie per la risposta aggiornata!
jtolds,

Risposte:


293

Git 2.5 propone da luglio 2015 un sostituto di contrib/workdir/git-new-workdir: git worktree

Vedi commit 68a2e6a di Junio ​​C Hamano ( gitster) .

La nota di rilascio menziona :

Un sostituto per contrib/workdir/git-new-workdirquesto non si basa su collegamenti simbolici e rende più sicura la condivisione di oggetti e riferimenti rendendo consapevoli tra loro il mutuatario e i mutuatari.

Vedi commit 799767cc9 (Git 2.5rc2)

Ciò significa che è ora possibile fare ungit worktree add <path> [<branch>]

Crea <path>e fai il checkout <branch>. La nuova directory di lavoro è collegata al repository corrente, condividendo tutto tranne i file specifici della directory di lavoro come HEAD, index, ecc. La git worktreesezione aggiunge:

Un repository git può supportare più alberi di lavoro , consentendo di estrarre più di un ramo alla volta.
A git worktree add, un nuovo albero di lavoro è associato al repository.

Questo nuovo albero di lavoro è chiamato "albero di lavoro collegato" in contrapposizione al "albero di lavoro principale" preparato da " git init" o " git clone" .
Un repository ha un albero di lavoro principale (se non è un repository nudo) e zero o più alberi di lavoro collegati.

dettagli:

Ogni albero di lavoro collegato ha una sottodirectory privata nella directory del repository $GIT_DIR/worktrees.
Il nome della sottodirectory privata è in genere il nome base del percorso dell'albero di lavoro collegato, eventualmente aggiunto con un numero per renderlo univoco.
Ad esempio, quando $GIT_DIR=/path/main/.gitil comando git worktree add /path/other/test-next nextcrea:

  • l'albero di lavoro collegato in /path/other/test-nexte
  • crea anche una $GIT_DIR/worktrees/test-nextdirectory (o $GIT_DIR/worktrees/test-next1se test-nextè già stata presa).

All'interno di un albero di lavoro collegato:

  • $GIT_DIRè impostato per puntare a questa directory privata (ad /path/main/.git/worktrees/test-nextesempio nell'esempio) e
  • $GIT_COMMON_DIRè impostato per puntare indietro all'albero di lavoro principale $GIT_DIR(ad es /path/main/.git.).

Queste impostazioni vengono effettuate in un .gitfile situato nella directory superiore dell'albero di lavoro collegato.

Quando hai finito con un albero di lavoro collegato puoi semplicemente cancellarlo.
I file amministrativi dell'albero di lavoro nel repository verranno infine rimossi automaticamente (vedere gc.pruneworktreesexpirein git config), oppure è possibile eseguirli git worktree prunenell'albero di lavoro principale o collegato per ripulire eventuali file amministrativi non aggiornati.


Attenzione: c'è ancora una sezione git worktree"BUGS" da tenere presente.

Il supporto per i sottomoduli è incompleto .
NON è consigliabile effettuare più checkout di un superprogetto.


Nota: con git 2.7rc1 (novembre 2015) puoi elencare i tuoi esercizi.
Vedere commettere bb9c03b , commettere 92718b7 , commettere 5.193.490 , commettere 1ceb7f9 , commettere 1ceb7f9 , commettere 5.193.490 , commettere 1ceb7f9 , commettono 1ceb7f9 (8 ottobre 2015), commettere 92718b7 , commettere 5.193.490 , commettere 1ceb7f9 , commettono 1ceb7f9 (8 ottobre 2015), commettere 5.193.490 , commit 1ceb7f9 (08 ott 2015), commit 1ceb7f9 (08 ott 2015) e commit ac6c561(02 ott 2015) di Michael Rappazzo ( rappazzo) .
(Unito da Junio ​​C Hamano - gitster- in commit a46dcfb , 26 ott 2015)

worktree: aggiungi listil comando ' '

' git worktree list' scorre l'elenco dei worktree e restituisce i dettagli del worktree, incluso il percorso del worktree, la revisione e il ramo attualmente estratti e se l'albero di lavoro è nudo.

$ git worktree list
/path/to/bare-source            (bare)
/path/to/linked-worktree        abcd1234 [master]
/path/to/other-linked-worktree  1234abc  (detached HEAD)

C'è anche l'opzione di formato porcellana disponibile.

Il formato porcellana ha una riga per attributo.

  • Gli attributi sono elencati con un'etichetta e un valore separati da un singolo spazio.
  • Gli attributi booleani (come "nudo" e "distaccato") sono elencati solo come etichetta e sono presenti solo se e solo se il valore è vero.
  • Una riga vuota indica la fine di un albero di lavoro

Per esempio:

$ git worktree list --porcelain

worktree /path/to/bare-source
bare

worktree /path/to/linked-worktree
HEAD abcd1234abcd1234abcd1234abcd1234abcd1234
branch refs/heads/master

worktree /path/to/other-linked-worktree
HEAD 1234abc1234abc1234abc1234abc1234abc1234a
detached

Nota: se SPOSTA una cartella worktree, devi aggiornare manualmente il gitdirfile.

Vedi commit 618244e (22 gennaio 2016) e commit d4cddd6 (18 gennaio 2016) di Nguyễn Thái Ngọc Duy ( pclouds) .
Aiutato da: Eric Sunshine ( sunshineco) .
(Unita da Junio ​​C Hamano - gitster- in commit d0a1cbc , 10 feb 2016)

Il nuovo documento in git 2.8 (marzo 2016) includerà:

Se si sposta un albero di lavoro collegato, è necessario aggiornare il gitdirfile " " nella directory della voce.
Ad esempio, se un albero di lavoro collegato viene spostato /newpath/test-nexte il suo .gitfile punta a /path/main/.git/worktrees/test-next, quindi aggiornare /path/main/.git/worktrees/test-next/gitdiral riferimento /newpath/test-next.


Fai attenzione quando elimini un ramo: prima di git 2.9 (giugno 2016), potresti eliminarne uno in uso in un altro albero di lavoro.

Quando " git worktree" è in uso la funzione, " git branch -d" ha permesso la cancellazione di un ramo che è stato estratto in un altro worktree.

Vedi commit f292244 (29 mar 2016) di Kazuki Yamaguchi ( rhenium) .
Aiutato da: Eric Sunshine ( sunshineco) .
(Unita da Junio ​​C Hamano - gitster- in commit 4fca4e3 , 13 apr 2016)

branch -d: rifiuta l'eliminazione di una filiale attualmente estratta

Quando un ramo viene estratto dall'albero di lavoro corrente, è vietato eliminare il ramo.
Tuttavia, quando il ramo viene estratto solo da altri alberi di lavoro, l'eliminazione ha esito negativo.
Utilizzare find_shared_symref()per verificare se il ramo è in uso, non solo confrontandolo con HEAD dell'albero di lavoro corrente.


Allo stesso modo, prima di git 2.9 (giugno 2016), la ridenominazione di un ramo verificato in un altro gruppo di lavoro non modificava la TESTA simbolica in detto altro gruppo di lavoro.

Vedi commit 18eb3a9 (08 apr 2016) e commit 70999e9 , commit 2233066 (27 marzo 2016) di Kazuki Yamaguchi ( rhenium) .
(Unito da Junio ​​C Hamano - gitster- in commit 741a694 , 18 apr 2016)

branch -m: aggiorna tutti i HEAD per ogni worktree

Quando si rinomina un ramo, attualmente viene aggiornato solo HEAD dell'albero di lavoro corrente, ma è necessario aggiornare HEAD di tutti gli alberi di lavoro che puntano al vecchio ramo.

Questo è il comportamento corrente, / path / to / wt's HEAD non è aggiornato:

  % git worktree list
  /path/to     2c3c5f2 [master]
  /path/to/wt  2c3c5f2 [oldname]
  % git branch -m master master2
  % git worktree list
  /path/to     2c3c5f2 [master2]
  /path/to/wt  2c3c5f2 [oldname]
  % git branch -m oldname newname
  % git worktree list
  /path/to     2c3c5f2 [master2]
  /path/to/wt  0000000 [oldname]

Questa patch risolve questo problema aggiornando tutte le HEAD dei worktree rilevanti durante la ridenominazione di un ramo.


Il meccanismo di blocco è ufficialmente supportato con git 2.10 (3 ° trimestre 2016)

Vedi commit 080739b , commit 6d30862 , commit 58142c0 , commit 346ef53 , commit 346ef53 , commit 58142c0 , commit 346ef53 , commit 346ef53 (13 giu 2016) e commit 984ad9e , commit 6835314 (03 giu 2016) di Nguyễn Thái Ngọc Duy ( pclouds) .
Suggerito da: Eric Sunshine ( sunshineco) .
(Unita da Junio ​​C Hamano - gitster- in commit 2c608e0 , 28 lug 2016)

git worktree lock [--reason <string>] <worktree>
git worktree unlock <worktree>

Se un albero di lavoro collegato è archiviato su un dispositivo portatile o su una condivisione di rete che non è sempre montato, è possibile impedire che i suoi file amministrativi vengano eliminati emettendo il git worktree lockcomando, specificando facoltativamente --reasondi spiegare perché l'albero di lavoro è bloccato.

<worktree>: Se gli ultimi componenti del percorso nel percorso dell'albero di lavoro sono univoci tra gli alberi di lavoro, possono essere utilizzati per identificare i campi di lavoro.
Ad esempio, se devi solo lavorare alberi su " /abc/def/ghi" e " /abc/def/ggg", allora " ghi" o " def/ghi" sono sufficienti per puntare al precedente albero di lavoro.


Git 2.13 (2 ° trimestre 2017) aggiunge lockun'opzione in commit 507e6e9 (12 aprile 2017) di Nguyễn Thái Ngọc Duy ( pclouds) .
Suggerito da: David Taylor ( dt) .
Aiutato da: Jeff King ( peff) .
(Unita da Junio ​​C Hamano - gitster- in commit e311597 , 26 apr 2017)

Consentire di bloccare un worktree immediatamente dopo la sua creazione.
Questo aiuta a prevenire una corsa tra " git worktree add; git worktree lock" e " git worktree prune".

Quindi git worktree add' --lock è l'equivalente di git worktree lockafter git worktree add, ma senza condizioni di gara.


Git 2.17+ (Q2 2018) aggiunge git worktree move/ git worktree remove: vedi questa risposta .


Git 2.19 (Q3 2018) aggiunge --quietun'opzione " " per rendere " git worktree add" meno prolisso.

Vedi commit 371979c (15 agosto 2018) di Elia Pinto ( devzero2000) .
Aiutato da: Martin Ågren, Duy Nguyen ( pclouds) e Eric Sunshine ( sunshineco) .
(Unita da Junio ​​C Hamano - gitster- in commit a988ce9 , 27 ago 2018)

worktree: aggiungi --quietopzione

Aggiungi l' --quietopzione ' ' a git worktree, come per gli altri gitcomandi.
' add' è l'unico comando interessato da esso poiché tutti gli altri comandi, tranne ' list', sono attualmente silenziosi per impostazione predefinita.


Nota che " git worktree add" era solito fare un "trova un nome disponibile con stat e poi mkdir", che è soggetto a razza.
Questo problema è stato risolto con Git 2.22 (Q2 2019) usando mkdire reagendo EEXISTin un ciclo.

Vedi commit 7af01f2 (20 febbraio 2019) di Michal Suchanek ( hramrach) .
(Unita da Junio ​​C Hamano - gitster- in commit 20fe798 , 09 apr 2019)

worktree: correzione worktree addgara

Git esegue un ciclo stat per trovare un nome di worktree disponibile e quindi lo fa mkdirsul nome trovato.
Trasformalo in mkdirloop per evitare un'altra invocazione di worktree aggiungi la ricerca dello stesso nome gratuito e la creazione della directory prima.


Git 2.22 (Q2 2019) corregge la logica per dire se un repository Git ha un albero di lavoro protegge " git branch -D" dalla rimozione del ramo che è attualmente estratto per errore.
L'implementazione di questa logica è stata interrotta per i repository con un nome insolito, che purtroppo è la norma per i sottomoduli in questi giorni.

Vedi commit f3534c9 (19 apr 2019) di Jonathan Tan ( jhowtan) .
(Unito da Junio ​​C Hamano - gitster- in commit ec2642a , 08 maggio 2019)

Codice Pull richiede 178 Approfondimenti

worktree: aggiornamento is_bareeuristico

Quando " git branch -D <name>" viene eseguito, Git di solito controlla prima se quel ramo è attualmente estratto.
Ma questo controllo non viene eseguito se la directory Git di quel repository non è su " <repo>/.git", ad esempio se quel repository è un sottomodulo che ha la sua directory Git memorizzata come " super/.git/modules/<repo>", ad esempio.
Ciò comporta l'eliminazione del ramo anche se è stato estratto.

Questo perché get_main_worktree()negli worktree.cinsiemi is_baresu un worktree usando solo l'euristica che un repository è nudo se il percorso del worktree non termina in " /.git", e non nuda altrimenti.
Questo is_barecodice è stato introdotto nel 92718b7 (" worktree: aggiungi dettagli alla struttura del worktree", 2015-10-08, Git v2.7.0-rc0), seguendo un pre-core.bareeuristico.

Questa patch fa 2 cose:

  • Insegna invece get_main_worktree()a utilizzare is_bare_repository(), introdotto in 7d1864c ("Introduci is_bare_repository () e variabile di configurazione core.bare", 2007-01-07, Git v1.5.0-rc1) e aggiornato in e90fdc3 ("Pulizia della gestione dell'albero di lavoro", 2007 -08-01, Git v1.5.3-rc4).
    Questo risolve il " git branch -D <name>" problema descritto sopra.

Tuttavia ... Se un repository ha core.bare=1ma il gitcomando " " viene eseguito da uno dei suoi worktrees secondari, is_bare_repository()restituisce false (il che va bene, poiché è disponibile un worktree).

E, trattare il gruppo di lavoro principale come non nudo quando è nudo causa problemi:

Ad esempio, la mancata eliminazione di un ramo da un albero di lavoro secondario a cui fa riferimento HEAD di un albero di lavoro principale, anche se tale albero di lavoro principale è nudo.

Per evitare ciò, controllare anche core.baredurante l'impostazione is_bare.
Se core.bare=1, fidati, e altrimenti, usa is_bare_repository().


1
Questa è la cosa più bella che hanno creato, proprio quello che stavo cercando. Grazie per quello!

Come eliminare solo l'albero di lavoro e mantenere comunque il ramo
Randeep Singh,

@DotnetRocks puoi eliminare qualsiasi albero funzionante (una cartella locale sul tuo computer): che non avrà alcuna influenza sul ramo: il repository .git principale includerà comunque l'intera cronologia impegnata, con tutti i suoi rami, indipendentemente dal fatto che l'albero di lavoro è stato cancellato.
VonC,

Sì, ma se elimini semplicemente l'albero di lavoro andando su quella cartella sul mio sistema ed elimini, git non mi permette di fare il checkout su quel ramo e dice che già checkout su <percorso> (<percorso> percorso worktree). Ma sembra che se faccio quanto segue: rm -rf <percorso> git worktree prune allora funziona. È giusto ?
Randeep Singh,

1
@Jayan Grazie. Ho chiarito che 2.5 e 2.7 sono ora disponibili.
VonC,

113

La gitdistribuzione viene fornita con uno script contribuito chiamato git-new-workdir. Lo useresti come segue:

git-new-workdir project-dir new-workdir branch

dove project-dir è il nome della directory che contiene il .gitrepository. Questo script crea un'altra .gitdirectory con molti collegamenti simbolici a quella originale ad eccezione dei file che non possono essere condivisi (come il ramo corrente), consentendo di lavorare in due rami diversi.

Sembra un po 'fragile, ma è un'opzione.


3
+1 Sono corretto, questo è davvero fantastico. Sembra condividere istantaneamente la storia e i rami tra due diversi repository estratti senza spingere / tirare, solo link simbolici. Ero completamente inconsapevole che Git potesse gestirlo. Purtroppo, non è incluso nella mia distribuzione.
meagar

2
Per quelli che usano msysgit (windows) puoi usare questa versione con porting dello script: github.com/joero74/git-new-workdir
amos,

9
Di solito funziona bene, ma se hai modificato accidentalmente lo stesso ramo in posizioni diverse, non è banale riparare le cose.
grep,

Per quelli bloccati su Git <2.5 e che hanno sottomoduli, prova git-new-workdir-recursivequale è un wrapper per git-new-workdir.
Walf

13

Mi sono imbattuto in questa domanda sperando in una soluzione che non ho trovato qui. Quindi ora che l'ho fatto a trovare quello che mi serviva, ho deciso di postare qui per gli altri.

Avvertenza: questa non è probabilmente una buona soluzione se è necessario modificare più rami contemporaneamente, come gli stati OP. È per avere simultaneamente estratto più rami che non si intende modificare. (Più directory di lavoro supportate da una cartella .git.)

Ci sono state alcune cose che ho imparato da quando sono arrivato a questa domanda la prima volta:

  1. Che cos'è un " repository nudo ". È essenzialmente il contenuto della .gitdirectory, senza trovarsi in un albero di lavoro.

  2. Il fatto che è possibile specificare la posizione del repository che si sta utilizzando (la posizione della .gitdir) sulla riga di comando con l' gitopzione--git-dir=

  3. Il fatto che è possibile specificare la posizione della copia di lavoro con --work-tree=

  4. Che cos'è un "repo mirror".

Quest'ultima è una distinzione piuttosto importante. In realtà non voglio lavorare sul repository, ho solo bisogno di avere copie di diversi rami e / o tag estratti contemporaneamente. In realtà, devo garantire che i rami non finiscano diversamente dai rami del mio telecomando. Quindi uno specchio è perfetto per me.

Quindi, per il mio caso d'uso, ho ottenuto ciò di cui avevo bisogno facendo:

git clone --mirror <remoteurl> <localgitdir> # Where localgitdir doesn't exist yet
mkdir firstcopy
mkdir secondcopy
git --git-dir=<localgitdir> --work-tree=firstcopy checkout -f branch1
git --git-dir=<localgitdir> --work-tree=secondcopy checkout -f branch2

Il grande avvertimento a riguardo è che non esiste una TESTA separata per le due copie. Quindi, dopo quanto sopra, l'esecuzione git --git-dir=<localgitdir> --work-tree=firstcopy statusmostra tutte le differenze da branch2 a branch1 come modifiche non impegnate, poiché HEAD punta a branch2. (Ecco perché utilizzo l' -fopzione per checkout, perché in realtà non ho intenzione di apportare modifiche localmente. Posso effettuare il checkout di qualsiasi tag o ramo per qualsiasi albero di lavoro, purché utilizzi il-f opzione.)

Per il mio caso d'uso di avere più checkout coesistenti sullo stesso computer senza doverli modificare , questo funziona perfettamente. Non so se esiste un modo per avere più HEAD per i vari alberi di lavoro senza uno script come quello descritto nelle altre risposte, ma spero che ciò sia comunque utile a qualcun altro.


Questo è esattamente quello che stavo cercando, ma non riesco a farlo funzionare ... Sto diventando "fatale: non un repository git: '<localgitdir>'". Qualche idea?
Dan R

Non importa, ho scoperto che stavo usando "~" nel mio nome di directory, e git non mi è piaciuto. Quando ho usato il percorso completo, ha funzionato bene.
Dan R

@DanR, felice che abbia aiutato. :) Potresti anche usare $HOME. C'è un altro avvertimento sul metodo sopra che ho scoperto in seguito, che ha a che fare con file che non esistono in uno o nell'altro ramo. Se esegui il checkout A in dir1, quindi checkout B in dir2, quindi forza il checkout C in dir1, se esiste un file esistente in A ma non in B o C, il file non verrà rimosso da dir1 anche dal checkout forzato . Quindi potrebbe essere necessario sperimentare git cleanin tal caso, oppure fare ciò che ho fatto e semplicemente usare questo metodo per popolare una directory appena creata.
Wildcard il

Grazie per il consiglio. Lo avvolgerò comunque come parte di uno strumento CLI in ruby, quindi mi assicurerò che inizi sempre da zero.
Dan R

@ Dan, potrebbe interessarti vedere il codice che stavo scrivendo in quel momento . La maggior parte di questi è generalmente applicabile a qualsiasi script di staging git, ad eccezione dei controlli di sintassi CFEngine. (Forse potresti sostituirlo con i controlli di sintassi di Ruby.) :)
Wildcard il

3

L'unica soluzione che mi viene in mente è quella di clonare due directory e aggiungerle come repository remoti l'una dell'altra. È quindi possibile continuare a estrarre le cose dall'una cambiata all'altra senza effettivamente spingere nulla nel repository remoto.

Suppongo che tu voglia avere due directory di lavoro e non due cloni del telecomando perché non vuoi spingere alcuni rami sul telecomando. Altrimenti, due cloni del tuo telecomando funzionerebbero bene - devi solo fare qualche spinta e tirare per mantenere tutti e tre sincronizzati.


Ciao. Il ragazzo sopra ha condiviso una soluzione interessante, il comando git worktree . Funziona più bene che clonare più volte lo stesso repository. Provalo, ti piacerà questa nuova funzionalità.
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.