remota - manejo de git
Git: ¿Es posible utilizar la misma copia de trabajo de submódulo en múltiples proyectos? (4)
Soy nuevo en Git. Digamos que tengo dos repositorios git que tienen la misma biblioteca agregada como submódulo:
/home/projects/project1/library_XYZ
/home/projects/project2/library_XYZ
También digamos, estoy trabajando en los proyectos y la biblioteca simultáneamente. Cuando hice cambios en la biblioteca, digamos en /home/projects/project1/library_XYZ
, tendría que impulsar estos cambios y luego jalarlos en /home/projects/project2/library_XYZ
para que estén disponibles para project2
, ¿verdad? Creo que esto es poco elegante por dos razones:
- Tendré que construir
library_XYZ
dos veces. - Tengo una redundancia no deseada que contradice la organización real del proyecto.
¿Hay alguna manera de hacer que Git clone el submódulo library_XYZ
en el mismo directorio local, es decir, haciendo que los archivos se organicen de esta manera?
/home/projects/project1
/home/projects/project2
/home/projects/library_XYZ
¿Mientras que library_XYZ
sigue siendo un submódulo de ambos proyectos?
Creo que esto podría estar relacionado con this , que no tiene respuesta, aunque mi configuración es algo diferente.
Configurar dependencias compartidas como submódulos es fácil. El comando git submodule
no lo hace automáticamente, pero un submódulo no es más que un repositorio anidado, y git no requiere que ningún repositorio real o su árbol de trabajo estén en ningún lugar en particular.
Configure un repositorio libraryXYZ para usar como un submódulo compartido
# a submodule is just a repository. We''re going to share this one.
git clone u://r/libraryXYZ
# and keep its worktree right here:
( cd libraryXYZ; git config core.worktree .. )
Luego, desde cualquier lugar, clone el proyecto utilizando el submódulo y configúrelo para usar el compartido:
git clone u://r/project1
cd project1
git submodule init
echo gitdir: path/to/shared/libraryXYZ/.git > libraryXYZ/.git
Ahora, el submódulo libraryXYZ
utilizará el repositorio y el área de trabajo de libraryXYZ
compartidos.
Configura tu sistema de compilación para usar ese mismo árbol de trabajo y listo. Por supuesto, puedes hacer que git te diga dónde se encuentran en un repositorio determinado:
# for example to see where all a project''s submodules'' parts are kept
git submodule foreach git rev-parse --git-dir --show-toplevel
# or for just one:
git --git-dir=$project1/libraryXYZ/.git rev-parse --git-dir --show-toplevel
(edición tardía: vale la pena tener en cuenta la observación de @ twalberg , esto podría hacer que sea un poco más fácil hacer una git submodule update
desde un proyecto sin darse cuenta de que también ha modificado el entorno de compilación para todos los demás proyectos que comparten dependencias).
En Linux, simplemente
sudo mount -o bind /home/projects/library_XYZ /home/projects/project1/library_XYZ
Si ya ha inicializado los submódulos, quizás también debería hacer lo que Guenther Brunthaler sugirió antes.
cd /home/projects/project1/
git submodule deinit library_XYZ
rm -rf .git/modules/library_XYZ
Puedes hacer esto desde git 2.5:
- Eliminar / inicio / proyectos / proyecto 2 / library_XYZ
- Eliminar / inicio / proyectos / proyecto 2 /.git/modules/library_XYZ
-
cd /home/projects/project1/library_XYZ
- crear una rama proyecto2 en / home / projects / project 1 / library_XYZ
- ejecutar
git worktree add ../../project2/library_XYZ project2
Ahora, /home/projects/project1/.git/modules/library_XYZ se comparte entre 2 proyectos.
Tuve el mismo problema que tú: una gran biblioteca de utilidad genérica como submódulo y muchos proyectos que dependen de ella. No quería crear un pago por separado para cada instancia de la biblioteca de utilidades.
La solución sugerida por jthill funciona bien, pero solo resuelve la primera mitad del problema, es decir, cómo mantener feliz a Git.
Lo que faltaba es cómo mantener contento a su sistema de compilación, que espera que los archivos reales funcionen y no se preocupa por las referencias de gitlink.
Pero si combinas su idea con un enlace simbólico, ¡obtienes lo que quieres!
Para implementar esto, comencemos con los proyectos de su ejemplo.
/home/projects/project1
/home/projects/project2
/home/projects/library_XYZ
asumiendo que tanto project1 como project2 tienen library_XYZ ya agregado como submódulo, y que actualmente los tres proyectos contienen un checkout completo de library_XYZ.
Para reemplazar los registros completos de los submódulos de la biblioteca por un enlace simbólico compartido al proceso de pago de la biblioteca, haga lo siguiente:
sharedproject="/home/projects/library_XYZ"
superproject="/home/projects/project1"
submodule="library_XYZ"
cd "$superproject"
(cd -- "$submodule" && git status) # Verify that no uncommited changes exist!
(cd -- "$submodule" && git push -- "$sharedproject") # Save any local-only commits
git submodule deinit -- "$submodule" # Get rid of submodule''s check-out
rm -rf .git/modules/"$submodule" # as well as of its local repository
mkdir -p .submods
git mv -- "$submodule" .submods/
echo "gitdir: $sharedproject.git" > ".submods/$submodule/.git"
ln -s -- "$sharedproject" "$submodule"
echo "/$submodule" >> .gitignore
y luego repita los mismos pasos para / home / projects / project2 como $ superproject.
Y aquí hay una explicación de lo que se ha hecho:
Primero, la comprobación de submódulos se elimina con "git submodule deinit", dejando a library_XYZ como un directorio vacío. ¡Asegúrese de confirmar cualquier cambio antes de hacer esto, ya que eliminará el pago!
A continuación, guardamos las confirmaciones locales en el proceso de pago que aún no se han enviado al proyecto compartido con "git push" en / home / projects / library_XYZ.
Si esto no funciona porque no configuró un control remoto o refspec para eso, puede hacer esto:
(saved_from=$(basename -- "$superproject"); /
cd -- "$submodule" /
&& git push -- "$sharedproject" /
"refs/heads/*:refs/remotes/$saved_from/*")
Esto guardará copias de seguridad de todas las sucursales del repositorio local del submódulo como sucursales remotas en / home / projects / library_XYZ. El nombre base del directorio $ superproject se usará como el nombre del control remoto, es decir, project1 o project2 en nuestro ejemplo.
Por supuesto, no existe realmente un control remoto de ese nombre en / home / projects / library_XYZ, pero las ramas guardadas se mostrarán como si lo hiciera cuando se ejecutará allí "git branch -r".
Como medida de seguridad, el refspec en el comando anterior no comienza con un "+", por lo que el "git push" no puede sobrescribir accidentalmente ninguna rama que ya exista en / home / projects / library_XYZ.
A continuación, .git / modules / library_XYZ se eliminará para ahorrar espacio. Podemos hacer esto porque ya no necesitamos usar "git submodule init" o "git submodule update". Este es el caso porque compartiremos el registro de salida y el directorio .git de / home / projects / library_XYZ con el submódulo, evitando una copia local de ambos.
Luego dejamos que git cambie el nombre del directorio de submódulo vacío a ".submods / library_XYZ", un directorio (oculto) que los archivos en los proyectos nunca usarán directamente.
A continuación, aplicamos la solución parcial de jthill al problema y creamos un archivo gitlink en .submods / library_XYZ, que hace que git vea / home / projects / library_XYZ como el árbol de trabajo y git repo del submódulo.
Y ahora viene lo nuevo: creamos un enlace simbólico con el nombre relativo "library_XYZ" que apunta a / home / projects / library_XYZ. Este enlace simbólico no se pondrá bajo el control de versiones, por lo que lo agregamos al archivo .gitignore.
Todos los archivos de compilación en project1 y project2 usarán el enlace simbólico library_XYZ como si fuera un subdirectorio normal, pero en realidad encontrarán los archivos del árbol de trabajo en / home / projects / library_XYZ allí.
¡Nadie, excepto git, en realidad usa .submods / library_XYZ!
Sin embargo, como el enlace simbólico ./library_XYZ no está versionado, no se creará al revisar project1 o project2. Por lo tanto, tenemos que cuidar que se cree automáticamente cuando falta.
Esto debe hacerse mediante la infraestructura de compilación de project1 / project2 con un comando equivalente a los siguientes comandos de shell:
$ test ! -e library_XYZ && ln -s .submods/library_XYZ
Por ejemplo, si el proyecto 1 se construye utilizando un Makefile y contiene la siguiente regla de destino para actualizar el subproyecto
library_XYZ/libsharedutils.a:
cd library_XYZ && $(MAKE) libsharedutils.a
luego insertamos la línea desde arriba como la primera línea de la acción de la regla:
library_XYZ/libsharedutils.a:
test ! -e library_XYZ && ln -s .submods/library_XYZ
cd library_XYZ && $(MAKE) libsharedutils.a
Si su proyecto usa otro sistema de compilación, generalmente puede hacer lo mismo creando una regla personalizada para crear el subdirectorio library_XYZ.
Si su proyecto solo contiene scripts o documentos y no utiliza ningún tipo de sistema de compilación, puede agregar un script que el usuario pueda ejecutar para crear los "directorios faltantes" (en realidad: enlaces simbólicos) de la siguiente manera:
(n=create_missing_dirs.sh && cat > "$n" << ''EOF'' && chmod +x -- "$n")
#! /bin/sh
for dir in .submods/*
do
sym=${dir#*/}
if test -d "$dir" && test ! -e "$sym"
then
echo "Creating $sym"
ln -snf -- "$dir" "$sym"
fi
done
EOF
Esto creará enlaces simbólicos a todas las comprobaciones de submódulos en .submods, pero solo si aún no existen o están rotos.
Hasta ahora, la transformación de un diseño de submódulo convencional a un nuevo diseño que permite compartir.
Una vez que ya hayas confirmado ese diseño, revisa el superproyecto en algún lugar, ve a su directorio de nivel superior y haz lo siguiente para habilitar el uso compartido:
sharedproject="/home/projects/library_XYZ"
submodule="library_XYZ"
ln -sn -- "$sharedproject" "$submodule"
echo "gitdir: $sharedproject.git" > ".submods/$submodule/.git"
Espero que se entere: el subdirectorio library_XYZ utilizado por project1 y project2 es un enlace simbólico no versionado en lugar de corresponder a la ruta del submódulo como se define en ".gitmodules".
El enlace simbólico será creado automáticamente por la propia infraestructura de construcción y luego apuntará a .submods / library_XYZ, pero solo, y esto es importante, si el enlace simbólico aún no existe.
Esto permite crear el enlace simbólico manualmente en lugar de permitir que el sistema de compilación lo cree, por lo que también se puede hacer que apunte a una única salida compartida en lugar de a .submods / library_XYZ.
De esa manera, puede utilizar una salida compartida en su máquina si lo desea.
Pero si otra persona no hace nada especial y solo verifica el proyecto 1 y realiza una "actualización de submódulo de git --init library_XYZ", las cosas funcionarán igual sin un check-out compartido.
¡No se necesitan cambios en los archivos de compilación desprotegidos en ninguno de los casos!
En otras palabras, un check-out de project1 y project2 funcionará fuera de la caja como de costumbre, no es necesario que otras personas sigan las instrucciones especiales que usan su repo.
Pero al crear manualmente el archivo gitlink y el enlace simbólico library_XYZ antes de que el sistema de compilación tenga la oportunidad de crear el enlace simbólico, puede "anular" localmente el enlace simbólico e imponer un pago compartido de la biblioteca.
Y aún hay otra ventaja: resulta que no necesita meterse con "git submodule init" o "git submodule update" en absoluto si usa la solución anterior: ¡Simplemente no funciona!
Esto se debe a que "git submodule init" solo es necesario como preparación para "git submodule update". Pero no necesitará esto último porque la biblioteca ya está desprotegida en otro lugar y también tiene su propio directorio .git allí. Así que no hay nada que hacer para la "actualización de submódulos de git", y no lo necesitamos.
Como efecto secundario de no usar más "git submodule update", tampoco se requerirá un subdirectorio .git / module. Tampoco queda ninguna necesidad de establecer alternativas (opción de referencia) para los submódulos.
Además, ya no necesita controles remotos para presionar / jalar / home / projects / library_XYZ en / home / projects / project1 y / home / projects / project2. Por lo tanto, puede eliminar el control remoto para acceder a library_XYZ desde project1 y project2.
¡Una situación de ganar-ganar!
La única desventaja obvia de esta solución es que requiere que los enlaces simbólicos funcionen.
Eso significa que no será posible revisar project1 en, por ejemplo, un sistema de archivos VFAT.
Pero entonces, ¿quién hace eso?
E incluso al hacerlo, los proyectos como http://sourceforge.net/projects/posixovl pueden ser capaces de solucionar cualquier limitación del enlace simbólico de un sistema de archivos.
Finalmente, algunos consejos para usuarios de Windows aquí:
Los enlaces simbólicos están disponibles desde VISTA a través del comando mklink, pero requieren privilegios especiales.
Pero cuando se usa el comando "junction" de sysinternals, los enlaces simbólicos a los directorios ya se pueden crear incluso en tiempos de Windows XP.
Además, tiene la opción de usar CygWin, que puede (AFAIK) emular enlaces simbólicos incluso sin el soporte del sistema operativo.