Vários diretórios de trabalho com o Git?

242

Não tenho certeza se isso é algo suportado pelo Git, mas em teoria parece que deve funcionar para mim.

Meu fluxo de trabalho geralmente envolve minha edição de arquivos em várias ramificações simultaneamente. Em outras palavras, geralmente desejo abrir alguns arquivos em uma ramificação enquanto edito o conteúdo de outro arquivo em outra ramificação.

Minha solução típica para isso é fazer dois checkouts, mas é uma pena não poder compartilhar ramos e referências entre eles. O que eu gostaria é ter apenas dois diretórios de trabalho gerenciados pela mesma pasta .git.

Estou ciente das soluções locais de clones git (o padrão, que é o de vincular objetos compartilhados, e a opção --shared, que configura um armazenamento de objetos alternativo com o repositório original), mas essas soluções reduzem o uso de espaço em disco , e especialmente no caso de --shared, parecem repletos de perigos.

Existe uma maneira de usar uma pasta .git e ter dois diretórios de trabalho apoiados por ela? Ou o Git está codificado para ter apenas um diretório ativo em check-out a qualquer momento?

jtolds
fonte
1
git-new-workdirserá substituído por git checkout --to=<path>no Git 2.5. Veja minha resposta abaixo
VonC
3
Na verdade, o comando será git worktree add <path> [<branch>](Git 2.5 rc2). Veja minha resposta editada abaixo
VonC:
você deve alterar a resposta aceita A resposta do VonC, pois as coisas mudaram desde que você fez a pergunta originalmente.
Xaxxon
obrigado pela resposta atualizada!
Jtolds # 02/07

Respostas:

293

O Git 2.5 propõe desde julho de 2015 um substituto para contrib/workdir/git-new-workdir: git worktree

Veja commit 68a2e6a por Junio ​​C Hamano ( gitster) .

A nota de versão menciona :

Uma substituição para contrib/workdir/git-new-workdirisso não depende de links simbólicos e torna o compartilhamento de objetos e referências mais seguro, tornando o mutuário e os mutuários cientes um do outro.

Consulte commit 799767cc9 (Git 2.5rc2)

Isso significa que agora você pode fazer umagit worktree add <path> [<branch>]

Crie <path>e faça o checkout <branch>nele. O novo diretório de trabalho está vinculado ao repositório atual, compartilhando tudo, exceto arquivos específicos do diretório de trabalho, como HEAD, índice, etc. A git worktreeseção adiciona:

Um repositório git pode suportar várias árvores de trabalho , permitindo que você verifique mais de uma ramificação por vez.
Com git worktree add, uma nova árvore de trabalho é associada ao repositório.

Essa nova árvore de trabalho é chamada de "árvore de trabalho vinculada" em oposição à "árvore de trabalho principal" preparada por " git init" ou " git clone" .
Um repositório possui uma árvore principal de trabalho (se não for um repositório vazio) e zero ou mais árvores de trabalho vinculadas.

detalhes:

Cada árvore de trabalho vinculada tem um subdiretório privado no diretório do repositório $GIT_DIR/worktrees.
O nome do subdiretório particular geralmente é o nome base do caminho da árvore de trabalho vinculada, possivelmente anexado a um número para torná-lo único.
Por exemplo, quando $GIT_DIR=/path/main/.gito comando git worktree add /path/other/test-next nextcria:

  • a árvore de trabalho vinculada /path/other/test-nexte
  • também cria um $GIT_DIR/worktrees/test-nextdiretório (ou $GIT_DIR/worktrees/test-next1se test-nextjá estiver em uso).

Dentro de uma árvore de trabalho vinculada:

  • $GIT_DIRestá definido para apontar para este diretório privado (por exemplo, /path/main/.git/worktrees/test-nextno exemplo) e
  • $GIT_COMMON_DIRestá definido para apontar de volta para a árvore principal de trabalho $GIT_DIR(por exemplo /path/main/.git).

Essas configurações são feitas em um .gitarquivo localizado no diretório superior da árvore de trabalho vinculada.

Quando você termina uma árvore de trabalho vinculada, pode simplesmente excluí-la.
Arquivos administrativos da árvore de trabalho no repositório eventualmente será removido automaticamente (ver gc.pruneworktreesexpireem git config), ou você pode executar git worktree pruneno principal ou em qualquer árvore de trabalho ligado para limpar quaisquer arquivos administrativos obsoletos.


Aviso: ainda existe uma seção git worktree"BUGS" para estar ciente.

O suporte para submódulos está incompleto .
NÃO é recomendado fazer vários checkouts de um superprojeto.


Nota: com o git 2.7rc1 (novembro de 2015), você pode listar suas ruas de trabalho.
Veja cometer bb9c03b , cometer 92718b7 , cometer 5.193.490 , cometer 1ceb7f9 , cometer 1ceb7f9 , cometer 5.193.490 , cometer 1ceb7f9 , cometer 1ceb7f9 (08 de outubro de 2015), cometer 92718b7 , cometer 5.193.490 , cometer 1ceb7f9 , cometer 1ceb7f9 (08 de outubro de 2015), cometer 5.193.490 , confirmar 1ceb7f9 (08 de outubro de 2015), confirmar 1ceb7f9 (08 de outubro de 2015) e confirmar ac6c561(02 de outubro de 2015) por Michael Rappazzo ( rappazzo) .
(Mesclado por Junio ​​C Hamano - gitster- in commit a46dcfb , 26 de outubro de 2015)

worktree: adicionar listcomando ' '

' git worktree list' itera pela lista da árvore de trabalho e gera detalhes da árvore de trabalho, incluindo o caminho para a árvore de trabalho, a revisão e ramificação atualmente verificadas e se a árvore de trabalho está vazia.

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

Há também opção de formato de porcelana disponível.

O formato de porcelana tem uma linha por atributo.

  • Os atributos são listados com um rótulo e um valor separados por um único espaço.
  • Atributos booleanos (como 'bare' e 'desanexado') são listados apenas como um rótulo e só estão presentes se e somente se o valor for verdadeiro.
  • Uma linha vazia indica o final de uma árvore de trabalho

Por exemplo:

$ 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 você mover uma pasta da árvore de trabalho, precisará atualizar manualmente o gitdirarquivo.

Consulte commit 618244e (22 de janeiro de 2016) e commit d4cddd6 (18 de janeiro de 2016) por Nguyễn Thái Ngọc Duy ( pclouds) .
Ajudado por: Eric Sunshine ( sunshineco) .
(Mesclado por Junio ​​C Hamano - gitster- na confirmação d0a1cbc , 10 de fevereiro de 2016)

O novo documento no git 2.8 (março de 2016) incluirá:

Se você mover uma árvore de trabalho vinculada, precisará atualizar o gitdirarquivo ' ' no diretório da entrada.
Por exemplo, se uma árvore de trabalho vinculada for movida para /newpath/test-nexte seu .gitarquivo apontar para /path/main/.git/worktrees/test-next, atualize /path/main/.git/worktrees/test-next/gitdirpara fazer referência /newpath/test-next.


Cuidado ao excluir uma ramificação: antes do git 2.9 (junho de 2016), era possível excluir uma em uso em outra árvore de trabalho.

Quando o git worktreerecurso " " está em uso, " git branch -d" permitiu a exclusão de uma ramificação que foi retirada em outra árvore de trabalho.

Consulte commit f292244 (29 de março de 2016) por Kazuki Yamaguchi ( rhenium) .
Ajudado por: Eric Sunshine ( sunshineco) .
(Incorporado por Junio ​​C Hamano - gitster- in commit 4fca4e3 , 13 de abril de 2016)

branch -d: recusar excluir uma ramificação que está com check-out atualmente

Quando uma ramificação é retirada pela árvore de trabalho atual, a exclusão da ramificação é proibida.
No entanto, quando o ramo é retirado somente por outras árvores de trabalho, a exclusão incorreta é bem-sucedida.
Use find_shared_symref()para verificar se o ramo está em uso, não apenas comparando com o HEAD da árvore de trabalho atual.


Da mesma forma, antes do git 2.9 (junho de 2016), renomear uma ramificação com check-out em outra árvore de trabalho não ajustava o HEAD simbólico na referida outra árvore de trabalho.

Consulte commit 18eb3a9 (08 de abril de 2016) e commit 70999e9 , commit 2233066 (27 de março de 2016) por Kazuki Yamaguchi ( rhenium) .
(Mesclado por Junio ​​C Hamano - gitster- na confirmação 741a694 , 18 de abril de 2016)

branch -m: atualizar todos os HEADs por árvore de trabalho

Ao renomear uma ramificação, atualmente apenas o HEAD da árvore de trabalho atual é atualizado, mas ele deve atualizar os HEADs de todas as árvores de trabalho que apontam para a ramificação antiga.

Este é o comportamento atual, / path / to / wt HEAD não é atualizado:

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

Esse patch corrige esse problema atualizando todos os HEADs relevantes da árvore de trabalho ao renomear uma ramificação.


O mecanismo de bloqueio é oficialmente suportado pelo git 2.10 (terceiro trimestre de 2016)

Consulte commit 080739b , commit 6d30862 , commit 58142c0 , commit 346ef53 , commit 346ef53 , commit 58142c0 , commit 346ef53 , commit 346ef53 (13 de junho de 2016) e commit 984ad9e , commit 684349 (03 jun 2016) por Nguyễn Thái Ngọc Duy ( pclouds) .
Sugerido por: Eric Sunshine ( sunshineco) .
(Mesclado por Junio ​​C Hamano - gitster- na confirmação 2c608e0 , 28 de julho de 2016)

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

Se uma árvore de trabalho vinculada estiver armazenada em um dispositivo portátil ou em um compartilhamento de rede que nem sempre é montado, você poderá impedir que seus arquivos administrativos sejam removidos emitindo o git worktree lockcomando, especificando opcionalmente --reasonpara explicar por que a árvore de trabalho está bloqueada.

<worktree>: Se os últimos componentes do caminho no caminho da árvore de trabalho forem exclusivos entre as árvores de trabalho, ele poderá ser usado para identificar as ruas de trabalho.
Por exemplo, se você só precisa trabalhar com árvores em " /abc/def/ghi" e " /abc/def/ggg", então " ghi" ou " def/ghi" é suficiente para apontar para a antiga árvore de trabalho.


Git 2.13 (Q2 2017) adiciona uma lockopção no commit 507e6e9 (12 de abril de 2017) por Nguyễn Thái Ngọc Duy ( pclouds) .
Sugerido por: David Taylor ( dt) .
Ajudado por: Jeff King ( peff) .
(Mesclado por Junio ​​C Hamano - gitster- no commit e311597 , 26 de abril de 2017)

Permitir bloquear uma árvore de trabalho imediatamente após ser criada.
Isso ajuda a impedir uma corrida entre " git worktree add; git worktree lock" e " git worktree prune".

Assim git worktree add' --lock é o equivalente a git worktree lockdepois git worktree add, mas sem condição de corrida.


O Git 2.17+ (Q2 2018) adiciona git worktree move/ git worktree remove: veja esta resposta .


O Git 2.19 (terceiro trimestre de 2018) adiciona uma --quietopção " " para tornar " git worktree add" menos detalhada.

Veja commit 371979c (15 de agosto de 2018) de Elia Pinto ( devzero2000) .
Ajudado por: Martin Ågren, Duy Nguyen ( pclouds) e Eric Sunshine ( sunshineco) .
(Incorporado por Junio ​​C Hamano - gitster- in commit a988ce9 , 27 de agosto de 2018)

worktree: adicionar --quietopção

Adicione a --quietopção ' ' a git worktree, como nos outros gitcomandos.
' add' é o único comando afetado por ele, pois todos os outros comandos, exceto ' list', estão silenciosos no momento por padrão.


Note que " git worktree add" costumava fazer um "encontre um nome disponível com stat e depois mkdir", que é propenso à corrida.
Isso foi corrigido no Git 2.22 (Q2 2019) usando mkdire reagindo EEXISTem um loop.

Veja commit 7af01f2 (20 fev 2019) por Michal Suchanek ( hramrach) .
(Mesclado por Junio ​​C Hamano - gitster- in commit 20fe798 , 09 abr 2019)

worktree: corrigir worktree addcorrida

O Git executa um loop stat para encontrar um nome da árvore de trabalho disponível e mkdiro nome encontrado.
Gire-o para fazer um mkdirloop para evitar outra chamada da árvore de trabalho. Encontre o mesmo nome gratuito e crie o diretório primeiro.


O Git 2.22 (Q2 2019) corrige a lógica para saber se um repositório do Git tem uma árvore de trabalho " git branch -D" que protege de remover o ramo que está atualmente com check-out por engano.
A implementação dessa lógica foi interrompida para repositórios com nome incomum, que infelizmente é a norma para submódulos atualmente.

Veja commit f3534c9 (19 Apr 2019) por Jonathan Tan ( jhowtan) .
(Mesclado por Junio ​​C Hamano - gitster- in commit ec2642a , 08 de maio de 2019)

Solicitações Code Pull 178 Insights

worktree: atualizar is_bareheurísticas

Quando " git branch -D <name>" é executado, o Git geralmente verifica primeiro se esse ramo está atualmente com check-out.
Mas essa verificação não será realizada se o diretório Git desse repositório não estiver em " <repo>/.git", o que acontece se esse repositório for um submódulo que tenha seu diretório Git armazenado como " super/.git/modules/<repo>", por exemplo.
Isso resulta na exclusão do ramo, mesmo que tenha sido retirado.

Isso ocorre porque get_main_worktree()em worktree.cconjuntos is_bareem uma árvore de trabalho usando apenas a heurística de que um repositório está vazio se o caminho da árvore de trabalho não terminar em " /.git" e não estiver em contrário.
Este is_barecódigo foi introduzido em 92718b7 (" worktree: adicione detalhes à estrutura da árvore de trabalho", 08/10/2015, Git v2.7.0-rc0), seguindo uma pre-core.bareheurística.

Este patch faz duas coisas:

  • Em vez disso, ensine get_main_worktree()a usar is_bare_repository(), introduzido em 7d1864c ("Introduzir is_bare_repository () e variável de configuração core.bare", 07-01-2007, Git v1.5.0-rc1) e atualizado em e90fdc3 ("Limpar o manuseio da árvore de trabalho", 2007 -08-01, Git v1.5.3-rc4).
    Isso resolve o " git branch -D <name>" problema descrito acima.

No entanto ... Se um repositório core.bare=1" git" mas tiver o comando " " sendo executado em uma de suas ruas de trabalho secundárias, is_bare_repository()retornará false (o que é bom, pois há uma árvore de trabalho disponível).

E tratar a árvore de trabalho principal como não nua quando está vazia causa problemas:

Por exemplo, falha ao excluir uma ramificação de uma árvore de trabalho secundária mencionada pelo HEAD de uma árvore de trabalho principal, mesmo se essa árvore de trabalho principal estiver vazia.

Para evitar isso, verifique também core.bareao definir is_bare.
Se core.bare=1, confie nele e caso contrário, use is_bare_repository().

VonC
fonte
1
Essa é a coisa mais legal que eles criaram, exatamente o que eu estava procurando. Obrigado por isso!
Como excluir a árvore de trabalho somente e ainda mantendo o ramo
Randeep Singh
@DotnetRocks, você pode excluir qualquer árvore de trabalho (uma pasta local no seu computador): que não terá nenhuma influência no ramo: o repositório principal do .git ainda incluirá o histórico completo confirmado, com todos os seus ramos, independentemente de o arquivo árvore de trabalho foi excluída.
VonC
Sim, mas se simplesmente excluir a árvore de trabalho indo para a pasta no meu sistema e excluindo, o git não me permite fazer check-out para esse ramo e diz que já faz check-out em <caminho> (<caminho> caminho da árvore de trabalho). Mas parece que se eu fizer o seguinte: rm -rf <path> git worktree ameixa, então ele funciona. Isso está certo ?
Randall Singh
1
@ Jayan Obrigado. Eu deixei mais claro que 2.5 e 2.7 já estão disponíveis.
VonC 18/01/16
113

A gitdistribuição vem com um script contribuído chamado git-new-workdir. Você usaria da seguinte maneira:

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

onde project-dir é o nome do diretório que contém seu .gitrepositório. Esse script cria outro .gitdiretório com muitos links simbólicos para o original, exceto arquivos que não podem ser compartilhados (como o ramo atual), permitindo que você trabalhe em dois ramos diferentes.

Parece um pouco frágil, mas é uma opção.

adl
fonte
3
+1 Estou corrigido, isso é incrível. Parece compartilhar instantaneamente histórico e ramificações entre dois repositórios com check-out diferentes, sem pressionar / puxar, apenas com ligações simbólicas. Eu não sabia que o git poderia lidar com isso. Infelizmente, não está incluído na minha distribuição.
meagar
2
Para aqueles que usam o msysgit (windows), você pode usar esta versão portada do script: github.com/joero74/git-new-workdir
amos
9
Geralmente funciona bem, mas se você editou acidentalmente o mesmo ramo em locais diferentes, não é trivial consertar as coisas.
grep
Para aqueles presos no Git <2.5 e que possuem submódulos, tente git-new-workdir-recursivequal é o invólucro git-new-workdir.
Walf
13

Me deparei com essa pergunta esperando uma solução que não encontrei aqui. Portanto, agora que eu tinha encontrar o que eu precisava, eu decidi postá-lo aqui para os outros.

Advertência: Essa provavelmente não é uma boa solução se você precisar editar várias ramificações simultaneamente, como estados OP. É por ter várias ramificações verificadas simultaneamente que você não pretende editar. (Vários diretórios de trabalho suportados por uma pasta .git.)

Aprendi algumas coisas desde que vim a essa pergunta pela primeira vez:

  1. O que é um " repositório vazio ". É essencialmente o conteúdo do .gitdiretório, sem estar localizado em uma árvore de trabalho.

  2. O fato de você poder especificar o local do repositório que está usando (o local do seu .gitdiretório) na linha de comando com a gitopção--git-dir=

  3. O fato de você poder especificar o local da sua cópia de trabalho com --work-tree=

  4. O que é um "repositório de espelho".

Esta última é uma distinção muito importante. Na verdade, não quero trabalhar no repositório, só preciso que cópias de diferentes ramos e / ou tags sejam verificadas simultaneamente. Na verdade, preciso garantir que as ramificações não sejam diferentes das ramificações do meu controle remoto. Então, um espelho é perfeito para mim.

Portanto, no meu caso de uso, consegui o que precisava fazendo:

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

A grande ressalva sobre isso é que não há um HEAD separado para as duas cópias. Portanto, após o exposto, a execução git --git-dir=<localgitdir> --work-tree=firstcopy statusmostrará todas as diferenças de branch2 para branch1 como alterações não confirmadas - porque HEAD está apontando para branch2. (É por isso que uso a -fopção checkout, porque, na verdade, não pretendo fazer alterações localmente. Posso fazer check-out de qualquer tag ou ramificação para qualquer árvore de trabalho, desde que use a -fopção.)

Para o meu caso de uso, com vários checkouts coexistindo no mesmo computador sem precisar editá-los , isso funciona perfeitamente. Não sei se existe alguma maneira de ter vários HEADs para as várias árvores de trabalho sem um script como o descrito nas outras respostas, mas espero que isso seja útil para outra pessoa.

Curinga
fonte
Isto é exatamente o que eu estava procurando, mas não consigo fazê-lo funcionar ... Estou ficando "fatal: não é um repositório git: '<localgitdir>'". Alguma ideia?
Dan R
Deixa pra lá, eu estava usando "~" no nome do meu diretório, e o git não gostou disso. Quando usei o caminho completo, funcionou bem.
Dan R
@ DanR, feliz que ajudou. :) Você também pode usar $HOME. Há uma outra ressalva sobre o método acima que descobri mais tarde, que tem a ver com arquivos que não existem em um ou outro ramo. Se você fizer check-out de A em dir1, check-out de B em dir2 e forçar check-out C em dir1, se houver um arquivo que exista em A, mas não em B ou C, o arquivo não será removido do dir1, nem mesmo pelo force checkout . Portanto, você pode precisar experimentar git cleannesse caso - ou fazer o que eu fiz, e simplesmente usar esse método para preencher um diretório recém-criado.
Curinga
Obrigado pela dica. De qualquer forma, vou envolver isso como parte de uma ferramenta CLI em ruby, para garantir que ela sempre comece do zero.
Dan R
@ DanR, pode ser interessante ver o código que eu estava escrevendo no momento . Geralmente, é geralmente aplicável a qualquer script de teste do git, com exceção das verificações de sintaxe do CFEngine. (Talvez você pode substituir esse com cheques de Ruby sintaxe.) :)
Wildcard
3

A única solução em que consigo pensar é clonar dois diretórios e adicioná-los como repositórios remotos um do outro. Em seguida, você pode continuar puxando coisas de uma para outra sem realmente enviar nada para o repositório remoto.

Suponho que você queira ter dois diretórios de trabalho e não dois clones do controle remoto, porque não deseja enviar algumas ramificações para o controle remoto. Caso contrário, dois clones do seu controle remoto funcionariam bem - você só precisa pressionar e puxar para manter os três sincronizados.

vhallac
fonte
Oi. O cara acima compartilhou uma solução interessante, o comando git worktree . Funciona mais bem do que clonar várias vezes o mesmo repositório. Experimente, você gostará deste novo recurso.