Plusieurs répertoires de travail avec Git?


242

Je ne sais pas si c'est quelque chose soutenu par Git, mais en théorie, il semble que cela devrait fonctionner pour moi.

Mon flux de travail implique souvent ma modification de fichiers dans plusieurs branches simultanément. En d'autres termes, je veux souvent ouvrir quelques fichiers dans une branche pendant que je modifie le contenu d'un autre fichier dans une autre branche.

Ma solution typique à cela est de faire deux extractions, mais c'est dommage que je ne puisse pas partager les branches et les références entre elles. Ce que je voudrais, c'est d'avoir juste deux répertoires de travail gérés par le même dossier .git.

Je connais les solutions locales de clonage git (la valeur par défaut, qui consiste à lier en dur les objets partagés, et l'option --shared, qui configure un autre magasin d'objets avec le dépôt d'origine), mais ces solutions ne réduisent que l'utilisation de l'espace disque , et en particulier dans le cas de --shared, semblent lourdes de dangers.

Existe-t-il un moyen d'utiliser un dossier .git et de sauvegarder deux répertoires de travail? Ou est-il codé en dur pour qu'un seul répertoire de travail soit extrait à tout moment?


1
git-new-workdirsera remplacé par git checkout --to=<path>dans Git 2.5. Voir ma réponse ci
VonC

3
En fait, la commande sera git worktree add <path> [<branch>](Git 2.5 rc2). Voir ma réponse modifiée ci
VonC

vous devriez changer la réponse acceptée La réponse de VonC, car les choses ont changé depuis que vous avez posé la question à l'origine.
xaxxon

merci pour la réponse mise à jour!
jtolds

Réponses:


293

Git 2.5 propose depuis juillet 2015 un remplacement pour contrib/workdir/git-new-workdir: git worktree

Voir commit 68a2e6a par Junio ​​C Hamano ( gitster) .

La note de version mentionne :

Un remplacement pour contrib/workdir/git-new-workdircela ne repose pas sur des liens symboliques et rend le partage d'objets et de références plus sûrs en sensibilisant l'emprunteur et les emprunteurs.

Voir commit 799767cc9 (Git 2.5rc2)

Cela signifie que vous pouvez maintenant fairegit worktree add <path> [<branch>]

Créez <path>et passez <branch>en caisse . Le nouveau répertoire de travail est lié au référentiel actuel, partageant tout sauf les fichiers spécifiques au répertoire de travail tels que HEAD, index, etc. La git worktreesection ajoute:

Un référentiel git peut prendre en charge plusieurs arborescences de travail , vous permettant de vérifier plus d'une branche à la fois.
Avec git worktree add, une nouvelle arborescence de travail est associée au référentiel.

Ce nouvel arbre de travail est appelé "arbre de travail lié" par opposition à "l'arbre de travail principal" préparé par " git init" ou " git clone" .
Un référentiel a une arborescence de travail principale (s'il ne s'agit pas d'un référentiel nu) et zéro ou plusieurs arborescences de travail liées.

détails:

Chaque arborescence de travail liée possède un sous-répertoire privé dans le répertoire du référentiel $GIT_DIR/worktrees.
Le nom du sous-répertoire privé est généralement le nom de base du chemin de l'arbre de travail lié, éventuellement accompagné d'un numéro pour le rendre unique.
Par exemple, lorsque $GIT_DIR=/path/main/.gitla commande git worktree add /path/other/test-next nextcrée:

  • l'arbre de travail lié dans /path/other/test-nextet
  • crée également un $GIT_DIR/worktrees/test-nextrépertoire (ou $GIT_DIR/worktrees/test-next1s'il test-nextest déjà pris).

Dans un arbre de travail lié:

  • $GIT_DIRest défini pour pointer vers ce répertoire privé (par exemple /path/main/.git/worktrees/test-nextdans l'exemple) et
  • $GIT_COMMON_DIRest réglé pour pointer vers l'arborescence de travail principale $GIT_DIR(par exemple /path/main/.git).

Ces paramètres sont définis dans un .gitfichier situé dans le répertoire supérieur de l'arborescence de travail liée.

Lorsque vous avez terminé avec un arbre de travail lié, vous pouvez simplement le supprimer.
Les fichiers administratifs de l'arborescence de travail dans le référentiel seront finalement supprimés automatiquement (voir gc.pruneworktreesexpiredans git config), ou vous pouvez exécuter git worktree prunedans l'arborescence de travail principale ou liée pour nettoyer les fichiers administratifs périmés.


Attention: il y a encore une section git worktree"BUGS" à connaitre .

Le support des sous - modules est incomplet .
Il n'est PAS recommandé d'effectuer plusieurs extractions d'un superprojet.


Remarque: avec git 2.7rc1 (novembre 2015), vous pouvez répertorier vos arbres de travail.
Voir commettre bb9c03b , engager 92718b7 , engager 5.193.490 , engager 1ceb7f9 , engager 1ceb7f9 , engager 5.193.490 , engager 1ceb7f9 , commettre 1ceb7f9 (8 octobre 2015), engager 92718b7 , engager 5.193.490 , engager 1ceb7f9 , commettre 1ceb7f9 (8 octobre 2015), engager 5193490 , commit 1ceb7f9 (08 oct 2015), commit 1ceb7f9 (08 oct 2015) et commit ac6c561(02 oct. 2015) de Michael Rappazzo ( rappazzo) .
(Fusionné par Junio ​​C Hamano - gitster- dans commit a46dcfb , 26 oct.2015 )

worktree: ajouter listla commande ' '

' git worktree list' parcourt la liste des arbres de travail et affiche les détails de l'arbre de travail, y compris le chemin d'accès à l'arbre de travail, la révision et la branche actuellement extraites et si l'arborescence de travail est vide.

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

Il existe également une option de format en porcelaine.

Le format porcelaine a une ligne par attribut.

  • Les attributs sont répertoriés avec une étiquette et une valeur séparées par un seul espace.
  • Les attributs booléens (comme «nus» et «détachés») sont répertoriés comme étiquette uniquement et ne sont présents que si et seulement si la valeur est vraie.
  • Une ligne vide indique la fin d'un arbre de travail

Par exemple:

$ 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

Remarque: si vous déplacez un dossier d'arbre de travail, vous devez mettre à jour manuellement le gitdirfichier.

Voir commit 618244e (22 janvier 2016) et commit d4cddd6 (18 janvier 2016) par Nguyễn Thái Ngọc Duy ( pclouds) .
Aidé par: Eric Sunshine ( sunshineco) .
(Fusionné par Junio ​​C Hamano - gitster- dans commit d0a1cbc , 10 février 2016)

Le nouveau document dans git 2.8 (mars 2016) comprendra:

Si vous déplacez une arborescence de travail liée, vous devez mettre à jour le gitdirfichier ' ' dans le répertoire de l'entrée.
Par exemple, si un arbre de travail lié est déplacé vers /newpath/test-nextet que son .gitfichier pointe vers /path/main/.git/worktrees/test-next, mettez-le /path/main/.git/worktrees/test-next/gitdirà jour à la /newpath/test-nextplace.


Soyez prudent lors de la suppression d'une branche: avant git 2.9 (juin 2016), vous pouviez en supprimer une en cours d'utilisation dans une autre arborescence de travail.

Lorsque la fonctionnalité " git worktree" est en cours d'utilisation, " git branch -d" la suppression d'une branche récupérée dans un autre arbre de travail est autorisée.

Voir commit f292244 (29 mars 2016) par Kazuki Yamaguchi ( rhenium) .
Aidé par: Eric Sunshine ( sunshineco) .
(Fusionné par Junio ​​C Hamano - gitster- en commit 4fca4e3 , 13 avril 2016)

branch -d: refuser de supprimer une branche actuellement extraite

Lorsqu'une branche est extraite par l'arborescence de travail actuelle, la suppression de la branche est interdite.
Toutefois, lorsque la branche est extraite uniquement par d'autres arborescences de travail, la suppression échoue correctement.
Utilisez find_shared_symref()pour vérifier si la branche est en cours d'utilisation, pas seulement pour comparer avec la tête HEAD de l'arbre de travail actuel.


De même, avant git 2.9 (juin 2016), renommer une branche extraite dans un autre arbre de travail n'a pas ajusté la TÊTE symbolique dans cet autre arbre de travail.

Voir commit 18eb3a9 (08 avril 2016), et commit 70999e9 , commit 2233066 (27 mars 2016) par Kazuki Yamaguchi ( rhenium) .
(Fusionné par Junio ​​C Hamano - gitster- dans commit 741a694 , 18 avr 2016)

branch -m: mettre à jour toutes les têtes par arbre de travail

Lorsque vous renommez une branche, actuellement seule la tête de l'arborescence de travail actuelle est mise à jour, mais elle doit mettre à jour les têtes de toutes les arborescences de travail qui pointent vers l'ancienne branche.

C'est le comportement actuel, / path / to / wt's HEAD n'est pas mis à jour:

  % 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]

Ce correctif résout ce problème en mettant à jour toutes les têtes de worktree pertinentes lors du changement de nom d'une branche.


Le mécanisme de verrouillage est officiellement pris en charge avec git 2.10 (Q3 2016)

Voir commit 080739b , commit 6d30862 , commit 58142c0 , commit 346ef53 , commit 346ef53 , commit 58142c0 , commit 346ef53 , commit 346ef53 (13 juin 2016) et commit 984ad9e , commit 6835314 (03 juin 2016) par Nguyễn Thái Ngọc Duy ( pclouds) .
Proposé par: Eric Sunshine ( sunshineco) .
(Fusionné par Junio ​​C Hamano - gitster- en commit 2c608e0 , 28 juil.2016 )

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

Si une arborescence de travail liée est stockée sur un périphérique portable ou un partage réseau qui n'est pas toujours monté, vous pouvez empêcher l'élagage de ses fichiers administratifs en exécutant la git worktree lockcommande, en spécifiant éventuellement --reasonpour expliquer pourquoi l'arborescence de travail est verrouillée.

<worktree>: Si les derniers composants de chemin dans le chemin de l'arborescence de travail sont uniques parmi les arbres de travail, ils peuvent être utilisés pour identifier les arbres de travail.
Par exemple, si vous n'avez qu'à travailler les arbres à " /abc/def/ghi" et " /abc/def/ggg", alors " ghi" ou " def/ghi" suffit pour pointer vers l'ancien arbre de travail.


Git 2.13 (Q2 2017) ajoute une lockoption dans commit 507e6e9 (12 avril 2017) par Nguyễn Thái Ngọc Duy ( pclouds) .
Proposé par: David Taylor ( dt) .
Aidé par: Jeff King ( peff) .
(Fusionné par Junio ​​C Hamano - gitster- en commit e311597 , 26 avril 2017)

Permet de verrouiller un arbre de travail immédiatement après sa création.
Cela permet d'éviter une course entre " git worktree add; git worktree lock" et " git worktree prune".

Il en git worktree add' --lock va de même de l' git worktree lockaprès git worktree add, mais sans condition de course.


Git 2.17+ (Q2 2018) ajoute git worktree move/ git worktree remove: voir cette réponse .


Git 2.19 (Q3 2018) ajoute une --quietoption " " pour rendre " git worktree add" moins verbeux.

Voir commit 371979c (15 août 2018) par Elia Pinto ( devzero2000) .
Aidé par: Martin Ågren, Duy Nguyen ( pclouds) et Eric Sunshine ( sunshineco) .
(Fusionné par Junio ​​C Hamano - gitster- dans commit a988ce9 , 27 août 2018)

worktree: ajouter une --quietoption

Ajoutez l' --quietoption ' ' à git worktree, comme pour les autres gitcommandes.
' add' est la seule commande affectée car toutes les autres commandes, à l'exception de ' list', sont actuellement silencieuses par défaut.


Notez que " git worktree add" utilisé pour faire un "trouve un nom disponible avec stat puis mkdir", qui est sujet à la course.
Cela a été corrigé avec Git 2.22 (Q2 2019) en utilisant mkdiret en réagissant EEXISTdans une boucle.

Voir commit 7af01f2 (20 février 2019) par Michal Suchanek ( hramrach) .
(Fusionné par Junio ​​C Hamano - gitster- en commit 20fe798 , 09 avr.2019 )

worktree: worktree addcourse fixe

Git exécute une boucle de statistiques pour trouver un nom d'arbre de travail disponible, puis le fait mkdirsur le nom trouvé.
Tournez-le en mkdirboucle pour éviter une autre invocation de worktree, ajoutez la recherche du même nom libre et créez d'abord le répertoire.


Git 2.22 (Q2 2019) corrige la logique pour dire si un référentiel Git a une arborescence de travail qui protège " git branch -D" contre la suppression de la branche qui est actuellement extraite par erreur.
L'implémentation de cette logique a été interrompue pour les référentiels au nom inhabituel, ce qui est malheureusement la norme pour les sous-modules de nos jours.

Voir commit f3534c9 (19 avril 2019) par Jonathan Tan ( jhowtan) .
(Fusionné par Junio ​​C Hamano - gitster- en commit ec2642a , 08 mai 2019)

Code Pull demandes 178 Insights

worktree: mise à jour is_bareheuristique

Lorsque " git branch -D <name>" est exécuté, Git vérifie généralement d'abord si cette branche est actuellement extraite.
Mais cette vérification n'est pas effectuée si le répertoire Git de ce référentiel n'est pas à " <repo>/.git", ce qui est le cas si ce référentiel est un sous-module dont le répertoire Git est stocké sous " super/.git/modules/<repo>", par exemple.
Cela entraîne la suppression de la branche même si elle est extraite.

En effet, get_main_worktree()dans les worktree.censembles is_bared'un arbre de travail utilisant uniquement l'heuristique, un dépôt est nu si le chemin de l'arbre de travail ne se termine pas par " /.git", et non nu sinon.
Ce is_barecode a été introduit dans 92718b7 (" worktree: ajouter des détails à la structure du worktree", 2015-10-08, Git v2.7.0-rc0), suite à une pre-core.bareheuristique.

Ce patch fait 2 choses:

  • Apprendre get_main_worktree()à utiliser à la is_bare_repository()place, introduit dans 7d1864c ("Introduce is_bare_repository () and core.bare configuration variable", 2007-01-07, Git v1.5.0-rc1) et mis à jour dans e90fdc3 ("Clean up work-tree handling", 2007 -08-01, Git v1.5.3-rc4).
    Cela résout le git branch -D <name>problème " " décrit ci-dessus.

Cependant ... Si un référentiel a core.bare=1mais que la gitcommande " " est exécutée à partir de l'un de ses arbres de travail secondaires, is_bare_repository()renvoie false (ce qui est correct, car un arbre de travail est disponible).

De plus, le fait de traiter le worktree principal comme non nu lorsqu'il est nu entraîne des problèmes:

Par exemple, l'échec de la suppression d'une branche d'un arbre de travail secondaire auquel fait référence le HEAD d'un arbre de travail principal, même si cet arbre de travail principal est nu.

Afin d'éviter cela, vérifiez également core.barelors du réglage is_bare.
Si core.bare=1, faites-lui confiance et sinon, utilisez is_bare_repository().


1
C'est la chose la plus cool qu'ils ont créée, juste ce que je cherchais. Merci pour ça!

Comment supprimer uniquement l'arbre de travail et conserver la branche
Randeep Singh

@DotnetRocks, vous pouvez supprimer n'importe quelle arborescence de travail (un dossier local sur votre ordinateur): cela n'aura aucune influence sur la branche: le référentiel .git principal inclura toujours l'historique complet, avec toutes ses branches, que le l'arbre de travail a été supprimé.
VonC

Oui, mais si vous supprimez simplement l'arborescence de travail en accédant à ce dossier sur mon système et en supprimant, git ne me laisse pas passer à cette branche et dit que déjà retirer à <path> (<path> pathtree path). Mais ressemble si je fais ce qui suit: rm -rf <path> git worktree prune alors cela fonctionne. Est-ce correct ?
Randeep Singh

1
@Jayan Merci. J'ai précisé que 2.5 et 2.7 sont maintenant sortis.
VonC

113

La gitdistribution est livrée avec un script contribué appelé git-new-workdir. Vous l'utiliseriez comme suit:

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

où project-dir est le nom du répertoire contenant votre .gitréférentiel. Ce script crée un autre .gitrépertoire avec de nombreux liens symboliques vers le répertoire d'origine à l'exception des fichiers qui ne peuvent pas être partagés (comme la branche actuelle), vous permettant de travailler dans deux branches différentes.

Cela semble un peu fragile, mais c'est une option.


3
+1 Je suis corrigé, c'est assez génial. Il semble partager instantanément l'historique et les branches entre deux référentiels extraits différents sans pousser / tirer, uniquement avec des liens symboliques. J'ignorais complètement que git pouvait gérer ça. Hélas, il n'est pas inclus dans ma distribution.
meagar

2
Pour ceux qui utilisent msysgit (windows), vous pouvez utiliser cette version portée du script: github.com/joero74/git-new-workdir
amos

9
Habituellement, cela fonctionne bien, mais si vous avez accidentellement modifié la même branche à différents endroits, il n'est pas inutile de réparer les choses.
grep

Pour ceux coincés sur Git <2.5 et qui ont des sous-modules, essayez git-new-workdir-recursive qui est un wrapper pour git-new-workdir.
Walf

13

Je suis tombé sur cette question en espérant une solution que je n'ai pas trouvée ici. Alors maintenant que je ne trouve ce que je avais besoin, j'ai décidé de poster ici pour d' autres.

Mise en garde: ce n'est probablement pas une bonne solution si vous devez modifier plusieurs branches simultanément, comme les états OP. C'est pour faire vérifier simultanément plusieurs branches que vous n'avez pas l' intention de modifier. (Plusieurs répertoires de travail soutenus par un dossier .git.)

Il y a quelques choses que j'ai apprises depuis que je suis arrivé à cette question la première fois:

  1. Qu'est-ce qu'un " référentiel nu "? Il s'agit essentiellement du contenu du .gitrépertoire, sans être situé dans une arborescence de travail.

  2. Le fait que vous pouvez spécifier l'emplacement du dépôt que vous utilisez (l'emplacement de votre .gitrépertoire) sur la ligne de commande avec l' gitoption--git-dir=

  3. Le fait que vous puissiez spécifier l'emplacement de votre copie de travail avec --work-tree=

  4. Qu'est-ce qu'un "repo miroir"?

Ce dernier est une distinction assez importante. Je ne veux pas vraiment travailler sur le repo, j'ai juste besoin de faire vérifier simultanément des copies de différentes branches et / ou balises. En fait, je dois garantir que les succursales ne sont pas différentes des succursales de ma télécommande. Un miroir est donc parfait pour moi.

Donc pour mon cas d'utilisation, j'ai obtenu ce dont j'avais besoin en faisant:

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

La grande mise en garde à ce sujet est qu'il n'y a pas de tête séparée pour les deux copies. Donc, après ce qui précède, courirgit --git-dir=<localgitdir> --work-tree=firstcopy status montrera toutes les différences de branch2 à branch1 en tant que changements non validés - car HEAD pointe sur branch2. (C'est pourquoi j'utilise l' -foption pour checkout, parce que je ne prévois pas de faire de changements localement du tout. Je peux retirer n'importe quelle balise ou branche pour n'importe quel arbre de travail, tant que j'utilise l' -foption.)

Pour mon cas d'utilisation d' avoir plusieurs extractions coexistant sur le même ordinateur sans avoir besoin de les modifier , cela fonctionne parfaitement. Je ne sais pas s'il existe un moyen d'avoir plusieurs HEAD pour les multiples arborescences de travail sans script tel que celui couvert dans les autres réponses, mais j'espère que cela sera utile à quelqu'un d'autre de toute façon.


C'est exactement ce que je cherchais, mais je n'arrive pas à le faire fonctionner ... Je reçois "fatal: Pas un dépôt git: '<localgitdir>'" ". Des idées?
Dan R

Peu importe, il se trouve que j'utilisais "~" dans mon nom de répertoire, et git n'aimait pas ça. Lorsque j'ai utilisé le chemin complet, cela a bien fonctionné.
Dan R

@DanR, heureux que cela ait aidé. :) Vous pourriez également utiliser $HOME. Il y a une autre mise en garde concernant la méthode ci-dessus que j'ai découverte plus tard, qui concerne les fichiers qui n'existent pas dans l'une ou l'autre branche. Si vous extrayez A dans dir1, puis extrayez B dans dir2, puis forcez l'extraction C dans dir1, s'il existe un fichier qui existe dans A mais pas dans B ou C, le fichier ne sera pas supprimé de dir1 même par la force. . Vous devrez donc peut-être faire des essais git cleandans un tel cas - ou faire ce que j'ai fait, et simplement utiliser cette méthode uniquement pour remplir un répertoire fraîchement créé.
Wildcard

Merci pour le conseil. Je vais tout de même envelopper cela dans le cadre d'un outil CLI en ruby, donc je vais m'assurer que tout recommence à zéro.
Dan R

@DanR, cela pourrait vous intéresser de voir le code que j'écrivais à l'époque . La plupart d'entre elles sont généralement applicables à tout script de transfert git, à l'exception des vérifications de la syntaxe CFEngine. (Peut-être pourriez-vous remplacer cela par des vérifications de la syntaxe Ruby.) :)
Wildcard

3

La seule solution à laquelle je peux penser est de cloner deux répertoires et de les ajouter en tant que référentiels distants l'un de l'autre. Vous pouvez ensuite continuer à extraire des éléments de ceux qui ont été modifiés dans l’autre sans vraiment pousser quoi que ce soit vers le référentiel distant.

Je suppose que vous voulez avoir deux répertoires de travail et non deux clones de la télécommande parce que vous ne voulez pas pousser certaines branches vers la télécommande. Sinon, deux clones de votre télécommande fonctionneraient très bien - il vous suffit de faire quelques push et pulls pour les synchroniser.


Salut. Le gars ci-dessus a partagé une solution intéressante, la commande git worktree . Cela fonctionne mieux que de cloner plusieurs fois le même référentiel. Essayez-le, vous aimerez cette nouvelle fonctionnalité.
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.