Múltiples directorios de trabajo con Git?

242

No estoy seguro de si esto es algo respaldado por Git, pero en teoría parece que debería funcionar para mí.

Mi flujo de trabajo a menudo implica la edición de archivos en varias ramas simultáneamente. En otras palabras, a menudo quiero abrir algunos archivos en una rama mientras edito el contenido de otro archivo en otra rama.

Mi solución típica para esto es hacer dos pagos, pero es una pena que no pueda compartir sucursales y referencias entre ellos. Lo que me gustaría es tener solo dos directorios de trabajo administrados por la misma carpeta .git.

Conozco las soluciones locales de clonación de git (el valor predeterminado, que es para vincular objetos compartidos, y la opción --shared, que configura un almacén de objetos alternativo con el repositorio original), pero estas soluciones solo reducen el uso de espacio en disco , y especialmente en el caso de --compartido, parece estar lleno de peligros.

¿Hay alguna manera de usar una carpeta .git y tener dos directorios de trabajo respaldados por ella? ¿O Git está codificado para tener un solo directorio de trabajo desprotegido en cualquier momento?

jtolds
fuente
1
git-new-workdirserá reemplazado por git checkout --to=<path>en Git 2.5. Vea mi respuesta a continuación
VonC
3
En realidad, el comando será git worktree add <path> [<branch>](Git 2.5 rc2). Vea mi respuesta editada a continuación
VonC
debe cambiar la respuesta aceptada de VonC, ya que las cosas han cambiado desde que hizo la pregunta originalmente.
xaxxon
gracias por la respuesta actualizada!
jtolds

Respuestas:

293

Git 2.5 propone desde julio de 2015 un reemplazo para contrib/workdir/git-new-workdir: git worktree

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

La nota de lanzamiento menciona :

Un reemplazo para contrib/workdir/git-new-workdireso no se basa en enlaces simbólicos y hace que el intercambio de objetos y referencias sea más seguro al hacer que el prestatario y los prestatarios se conozcan entre sí.

Ver commit 799767cc9 (Git 2.5rc2)

Eso significa que ahora puedes hacer ungit worktree add <path> [<branch>]

Crear <path>y pagar <branch>en él. El nuevo directorio de trabajo está vinculado al repositorio actual, compartiendo todo excepto archivos específicos del directorio de trabajo como HEAD, index, etc. La git worktreesección agrega:

Un repositorio git puede soportar múltiples árboles de trabajo , lo que le permite retirar más de una rama a la vez.
Con git worktree addun nuevo árbol de trabajo está asociado con el repositorio.

Este nuevo árbol de trabajo se denomina "árbol de trabajo vinculado" en oposición al "árbol de trabajo principal" preparado por " git init" o " git clone" .
Un repositorio tiene un árbol de trabajo principal (si no es un repositorio simple) y cero o más árboles de trabajo vinculados.

detalles:

Cada árbol de trabajo vinculado tiene un subdirectorio privado en el directorio del repositorio $GIT_DIR/worktrees.
El nombre del subdirectorio privado suele ser el nombre base de la ruta del árbol de trabajo vinculado, posiblemente junto con un número para hacerlo único.
Por ejemplo, cuando $GIT_DIR=/path/main/.gitel comando git worktree add /path/other/test-next nextcrea:

  • el árbol de trabajo vinculado /path/other/test-nexty
  • también crea un $GIT_DIR/worktrees/test-nextdirectorio (o $GIT_DIR/worktrees/test-next1si test-nextya está tomado).

Dentro de un árbol de trabajo vinculado:

  • $GIT_DIRestá configurado para apuntar a este directorio privado (por ejemplo, /path/main/.git/worktrees/test-nexten el ejemplo) y
  • $GIT_COMMON_DIRestá configurado para señalar de nuevo al árbol de trabajo principal $GIT_DIR(por ejemplo /path/main/.git).

Esta configuración se realiza en un .gitarchivo ubicado en el directorio superior del árbol de trabajo vinculado.

Cuando haya terminado con un árbol de trabajo vinculado, simplemente puede eliminarlo.
Los archivos administrativos del árbol de trabajo en el repositorio eventualmente se eliminarán automáticamente (ver gc.pruneworktreesexpireen git config), o puede ejecutar git worktree pruneen el árbol de trabajo principal o vinculado para limpiar los archivos administrativos obsoletos.


Advertencia: todavía hay una sección de git worktree"ERRORES" para tener en cuenta.

El soporte para submódulos está incompleto .
NO se recomienda realizar múltiples pagos de un superproyecto.


Nota: con git 2.7rc1 (noviembre de 2015) puede enumerar sus árboles de trabajo.
Ver cometer bb9c03b , comprometerse 92718b7 , comprometerse 5.193.490 , comprometerse 1ceb7f9 , comprometerse 1ceb7f9 , comprometerse 5.193.490 , comprometerse 1ceb7f9 , cometen 1ceb7f9 (viernes, 08 Oct 2015), comprometerse 92718b7 , comprometerse 5.193.490 , comprometerse 1ceb7f9 , cometen 1ceb7f9 (viernes, 08 Oct 2015), comprometerse 5.193.490 , commit 1ceb7f9 (08 de octubre de 2015), commit 1ceb7f9 (08 de octubre de 2015) y commit ac6c561(02 de octubre de 2015) por Michael Rappazzo ( rappazzo) .
(Fusionada por Junio ​​C Hamano - gitster- en commit a46dcfb , 26 oct 2015)

worktree: Agregar listcomando ' '

' git worktree list' itera a través de la lista de árbol de trabajo y muestra detalles del árbol de trabajo, incluida la ruta al árbol de trabajo, la revisión y la rama actualmente desprotegidos, y si el árbol de trabajo está vacío.

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

También hay una opción de formato de porcelana disponible.

El formato de porcelana tiene una línea por atributo.

  • Los atributos se enumeran con una etiqueta y un valor separados por un solo espacio.
  • Los atributos booleanos (como 'desnudo' y 'separado') se enumeran solo como una etiqueta, y solo están presentes si y solo si el valor es verdadero.
  • Una línea vacía indica el final de un árbol de trabajo.

Por ejemplo:

$ 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: si MOVES una carpeta de árbol de trabajo, debes actualizar el archivo manualmentegitdir .

Ver commit 618244e (22 de enero de 2016) y commit d4cddd6 (18 de enero de 2016) por Nguyễn Thái Ngọc Duy ( pclouds) .
Ayudado por: Eric Sunshine ( sunshineco) .
(Fusionada por Junio ​​C Hamano - gitster- en commit d0a1cbc , 10 feb 2016)

El nuevo documento en git 2.8 (marzo de 2016) incluirá:

Si mueve un árbol de trabajo vinculado, debe actualizar el gitdirarchivo ' ' en el directorio de la entrada.
Por ejemplo, si se mueve un árbol de trabajo vinculado /newpath/test-nexty su .gitarchivo apunta /path/main/.git/worktrees/test-next, entonces actualice /path/main/.git/worktrees/test-next/gitdira referencia en su /newpath/test-nextlugar.


Tenga cuidado al eliminar una rama: antes de git 2.9 (junio de 2016), puede eliminar una en uso en otro árbol de trabajo.

Cuando la " git worktree" característica está en uso "," git branch -d"permitió la eliminación de una rama que está desprotegida en otro árbol de trabajo.

Ver commit f292244 (29 de marzo de 2016) por Kazuki Yamaguchi ( rhenium) .
Ayudado por: Eric Sunshine ( sunshineco) .
(Fusionada por Junio ​​C Hamano - gitster- en commit 4fca4e3 , 13 abr 2016)

branch -d: rechazar la eliminación de una rama que está actualmente desprotegida

Cuando una rama es desprotegida por el árbol de trabajo actual, está prohibido eliminar la rama.
Sin embargo, cuando la rama se desprotege solo por otros árboles de trabajo, la eliminación se realiza incorrectamente.
Utilícelo find_shared_symref()para verificar si la rama está en uso, no solo comparándola con el HEAD del árbol de trabajo actual.


De manera similar, antes de git 2.9 (junio de 2016), al cambiar el nombre de una rama desprotegida en otro árbol de trabajo no se ajustó la CABEZA simbólica en dicho otro árbol de trabajo.

Ver commit 18eb3a9 (08 de abril de 2016), y commit 70999e9 , commit 2233066 (27 de marzo de 2016) por Kazuki Yamaguchi ( rhenium) .
(Fusionada por Junio ​​C Hamano - gitster- en commit 741a694 , 18 abr 2016)

branch -m: actualizar todos los HEADs por árbol de trabajo

Al cambiar el nombre de una rama, actualmente solo se actualiza el HEAD del árbol de trabajo actual, pero debe actualizar HEADs de todos los árboles de trabajo que apuntan a la rama anterior.

Este es el comportamiento actual, / path / to / wt's HEAD no se actualiza:

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

Este parche corrige este problema actualizando todos los HEAD de trabajo relevantes al cambiar el nombre de una rama.


El mecanismo de bloqueo es oficialmente compatible con git 2.10 (Q3 2016)

Ver commit 080739b , commit 6d30862 , commit 58142c0 , commit 346ef53 , commit 346ef53 , commit 58142c0 , commit 346ef53 , commit 346ef53 (13 de junio de 2016) y commit 984ad9e , commit 6835314 (03 jun 2016) por Nguyễn Thái Ngọc Duy (pclouds . )
Sugerido por: Eric Sunshine ( sunshineco) .
(Fusionada por Junio ​​C Hamano - gitster- en commit 2c608e0 , 28 jul 2016)

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

Si un árbol de trabajo vinculado se almacena en un dispositivo portátil o recurso compartido de red que no siempre está montado, puede evitar que sus archivos administrativos se eliminen emitiendo el git worktree lockcomando, especificando opcionalmente --reasonpara explicar por qué el árbol de trabajo está bloqueado.

<worktree>: Si los últimos componentes de la ruta del árbol de trabajo son únicos entre los árboles de trabajo, se pueden usar para identificar árboles de trabajo.
Por ejemplo, si solo tiene que trabajar árboles en " /abc/def/ghi" y " /abc/def/ggg", entonces " ghi" o " def/ghi" es suficiente para señalar el árbol de trabajo anterior.


Git 2.13 (Q2 2017) agrega una lockopción en commit 507e6e9 (12 de abril de 2017) por Nguyễn Thái Ngọc Duy ( pclouds) .
Sugerido por: David Taylor ( dt) .
Ayudado por: Jeff King ( peff) .
(Fusión por Junio ​​C Hamano - gitster- en commit e311597 , 26 abr 2017)

Permitir bloquear un árbol de trabajo inmediatamente después de crearlo
Esto ayuda a prevenir una carrera entre " git worktree add; git worktree lock" y " git worktree prune".

Entonces git worktree add' --lock es el equivalente de git worktree lockdespués git worktree add, pero sin condición de carrera.


Git 2.17+ (Q2 2018) agrega git worktree move/ git worktree remove: vea esta respuesta .


Git 2.19 (Q3 2018) agrega una " --quiet" opción para hacer " git worktree add" menos detallado.

Ver commit 371979c (15 de agosto de 2018) por Elia Pinto ( devzero2000) .
Ayudado por: Martin Ågren, Duy Nguyen ( pclouds) y Eric Sunshine ( sunshineco) .
(Fusionada por Junio ​​C Hamano - gitster- en commit a988ce9 , 27 ago 2018)

worktree: agregar --quietopción

Agregue la --quietopción ' ' a git worktree, como para los otros gitcomandos.
' add' es el único comando afectado por él, ya que todos los demás comandos, excepto ' list', están actualmente en silencio de forma predeterminada.


Tenga en cuenta que " git worktree add" solía hacer un "buscar un nombre disponible con stat y luego mkdir", que es propenso a la carrera.
Esto se ha solucionado con Git 2.22 (Q2 2019) mediante el uso mkdiry la reacción EEXISTen bucle.

Ver commit 7af01f2 (20 de febrero de 2019) por Michal Suchanek ( hramrach) .
(Fusionada por Junio ​​C Hamano - gitster- en commit 20fe798 , 09 abr 2019)

worktree: worktree addcarrera fija

Git ejecuta un bucle de estadísticas para encontrar un nombre de árbol de trabajo que esté disponible y luego lo hace mkdiren el nombre encontrado. Conviértalo
en mkdirbucle para evitar otra invocación de worktree add encontrando el mismo nombre libre y creando primero el directorio.


Git 2.22 (Q2 2019) corrige la lógica para saber si un repositorio de Git tiene un árbol que funciona " git branch -D" protege de la eliminación de la rama que actualmente está desprotegida por error.
La implementación de esta lógica se rompió para los repositorios con un nombre inusual, que desafortunadamente es la norma para los submódulos en estos días.

Ver commit f3534c9 (19 abr 2019) por Jonathan Tan ( jhowtan) .
(Fusionada por Junio ​​C Hamano - gitster- en commit ec2642a , 08 de mayo de 2019)

Solicitudes de extracción de código 178 Insights

worktree: actualizar is_bareheurística

Cuando git branch -D <name>se ejecuta " ", Git generalmente primero verifica si esa rama está actualmente desprotegida.
Pero esta comprobación no se realiza si el directorio Git de ese repositorio no está en " <repo>/.git", que es el caso si ese repositorio es un submódulo que tiene su directorio Git almacenado como " super/.git/modules/<repo>", por ejemplo.
Esto hace que la rama se elimine aunque esté desprotegida.

Esto se debe a que get_main_worktree()en los worktree.cconjuntos is_barede un árbol de trabajo solo se utiliza la heurística de que un repositorio está vacío si la ruta del árbol de trabajo no termina en " /.git", y no está vacío de lo contrario.
Este is_barecódigo se introdujo en 92718b7 (" worktree: agregar detalles a la estructura del árbol de trabajo", 2015-10-08, Git v2.7.0-rc0), siguiendo una pre-core.bareheurística.

Este parche hace 2 cosas:

  • Enseñe get_main_worktree()a usar is_bare_repository()en su lugar, introducido en 7d1864c ("Introducir is_bare_repository () y core.bare configuration variable", 2007-01-07, Git v1.5.0-rc1) y actualizado en e90fdc3 ("Limpieza del manejo del árbol de trabajo", 2007 -08-01, Git v1.5.3-rc4).
    Esto resuelve el " git branch -D <name>" problema descrito anteriormente.

Sin embargo ... Si un repositorio tiene core.bare=1pero el gitcomando " " se está ejecutando desde uno de sus árboles de trabajo secundarios, is_bare_repository()devuelve falso (lo cual está bien, ya que hay un árbol de trabajo disponible).

Y, tratar el árbol de trabajo principal como no desnudo cuando está desnudo causa problemas:

Por ejemplo, no eliminar una rama de un árbol de trabajo secundario al que se refiere el HEAD de un árbol de trabajo principal, incluso si ese árbol de trabajo principal está vacío.

Para evitar eso, verifique también core.bareal configurar is_bare.
Si core.bare=1, confía en él, y de lo contrario, úsalo is_bare_repository().

VonC
fuente
1
Esto es lo mejor que han creado, justo lo que estaba buscando. ¡Gracias por eso!
Cómo eliminar solo el árbol de trabajo y aún mantener la rama
Randeep Singh
@DotnetRocks puede eliminar cualquier árbol de trabajo (una carpeta local en su computadora): eso no tendrá ninguna influencia en la rama: el repositorio principal de .git aún incluirá el historial completo, con todas sus ramas, ya sea que El árbol de trabajo ha sido eliminado.
VonC
Sí, pero si simplemente elimino el árbol de trabajo yendo a esa carpeta en mi sistema y elimino, git no me deja pagar en esa rama y dice que ya está en <path> (<path> worktree path). Pero parece que si hago lo siguiente: rm -rf <path> git worktree prune entonces funciona. Está bien ?
Randeep Singh
1
@ Jayan Gracias. He dejado en claro que 2.5 y 2.7 ya están disponibles.
VonC
113

La gitdistribución viene con un script contribuido llamado git-new-workdir. Lo usarías de la siguiente manera:

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

donde project-dir es el nombre del directorio que contiene su .gitrepositorio. Este script crea otro .gitdirectorio con muchos enlaces simbólicos al original, excepto los archivos que no se pueden compartir (como la rama actual), lo que le permite trabajar en dos ramas diferentes.

Suena un poco frágil, pero es una opción.

adl
fuente
3
+1 Estoy corregido, esto es bastante impresionante. Parece compartir instantáneamente el historial y las ramas entre dos repositorios diferentes que no han sido presionados / jalados, solo simblinking. No sabía por completo que Git podría manejar esto. Por desgracia, no está incluido en mi distribución.
meagar
2
Para aquellos que usan msysgit (windows) pueden usar esta versión portada del script: github.com/joero74/git-new-workdir
amos el
99
Por lo general, funciona bien, pero si accidentalmente editó la misma rama en diferentes ubicaciones, no es trivial arreglar las cosas nuevamente.
grep
Para aquellos atrapados en Git <2.5 y que tienen submódulos, pruebe git-new-workdir-recursivecuál es un contenedor para git-new-workdir.
Walf
13

Encontré esta pregunta esperando una solución que no encontré aquí. Así que ahora que me he encontrado lo que buscaba, me decidí a publicar aquí para otros.

Advertencia: Probablemente esta no sea una buena solución si necesita editar varias ramas simultáneamente, como los estados de OP. Es por tener varias ramas desprotegidas simultáneamente que no tiene la intención de editar. (Varios directorios de trabajo respaldados por una carpeta .git).

Aprendí algunas cosas desde que llegué a esta pregunta la primera vez:

  1. Qué es un " repositorio desnudo ". Es esencialmente el contenido del .gitdirectorio, sin estar ubicado en un árbol de trabajo.

  2. El hecho de que puede especificar la ubicación del repositorio que está utilizando (la ubicación de su .gitdirectorio) en la línea de comando con la gitopción--git-dir=

  3. El hecho de que puede especificar la ubicación de su copia de trabajo con --work-tree=

  4. Qué es un "repositorio de espejos".

Esta última es una distinción bastante importante. En realidad no quiero trabajar en el repositorio, solo necesito tener copias de diferentes ramas y / o etiquetas revisadas simultáneamente. De hecho, necesito garantizar que las ramas no sean diferentes de las ramas de mi control remoto. Entonces un espejo es perfecto para mí.

Entonces, para mi caso de uso, obtuve lo que necesitaba haciendo:

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 gran advertencia sobre esto es que no hay un HEAD separado para las dos copias. Entonces, después de lo anterior, la ejecución git --git-dir=<localgitdir> --work-tree=firstcopy statusmostrará todas las diferencias de branch2 a branch1 como cambios no confirmados, porque HEAD apunta a branch2. (Es por eso que uso la -fopción checkout, porque en realidad no estoy planeando hacer ningún cambio localmente. Puedo verificar cualquier etiqueta o rama para cualquier árbol de trabajo, siempre que use la -fopción).

Para mi caso de uso de múltiples pagos coexistentes en la misma computadora sin necesidad de editarlos , esto funciona perfectamente. No sé si hay alguna manera de tener múltiples HEADs para los múltiples árboles de trabajo sin un script como se cubre en las otras respuestas, pero espero que esto sea útil para alguien más de todos modos.

Comodín
fuente
Esto es exactamente lo que estaba buscando, pero no puedo hacer que funcione ... Me estoy poniendo "fatal: No es un repositorio git: '<localgitdir>'". ¿Algunas ideas?
Dan R
No importa, resulta que estaba usando "~" en el nombre de mi directorio, y a git no le gustó eso. Cuando usé la ruta completa, funcionó bien.
Dan R
@ Danr, me alegro de que haya ayudado. :) También puedes usar $HOME. Hay otra advertencia sobre el método anterior que descubrí más adelante, que tiene que ver con archivos que no existen en una u otra rama. Si desprotege A en dir1, luego desprotege B en dir2, luego fuerza forzar desprotección C en dir1, si hay un archivo que existe en A pero no en B o C, el archivo no se eliminará de dir1 ni siquiera mediante la desviación forzada . Por lo tanto, es posible que necesite experimentar git cleanen tal caso, o hacer lo que hice, y simplemente usar este método para llenar un directorio recién creado.
Comodín el
Gracias por el consejo. Voy a envolver esto como parte de una herramienta CLI en ruby ​​de todos modos, así que me aseguraré de que siempre comience desde cero.
Dan R
@ DanR, podría interesarle ver el código que estaba escribiendo en ese momento . La mayor parte es generalmente aplicable a cualquier script de puesta en escena git, con la excepción de las comprobaciones de sintaxis de CFEngine. (Quizás podría reemplazar eso con las comprobaciones de sintaxis de Ruby.) :)
Comodín el
3

La única solución que se me ocurre es clonar dos directorios y agregarlos como repositorios remotos entre sí. A continuación, puede seguir arrastrando cosas del uno modificado al otro sin enviar nada al repositorio remoto.

Supongo que desea tener dos directorios que funcionen y no dos clones del control remoto porque no desea insertar algunas ramas en el control remoto. De lo contrario, dos clones de su control remoto funcionarían bien, solo necesita hacer algunos empujones y tirones para mantener los tres sincronizados.

vhallac
fuente
Hola. El chico de arriba compartió una solución genial, el comando git worktree . Funciona mejor que clonar varias veces el mismo repositorio. Pruébelo, le gustará esta nueva característica.