¿Cómo clono un subdirectorio únicamente de un repositorio Git?

Resuelto Nick Sergeant asked hace 15 años • 31 respuestas

Tengo mi repositorio Git que, en la raíz, tiene dos subdirectorios:

/finisht
/static

Cuando esto estaba en SVN , /finishtse desprotegía en un lugar, mientras que /staticse desprotegía en otro lugar, así:

svn co svn+ssh://[email protected]/home/admin/repos/finisht/static static

¿Hay alguna manera de hacer esto con Git?

Nick Sergeant avatar Mar 01 '09 23:03 Nick Sergeant
Aceptado

Lo que está intentando hacer se llama pago disperso y esa característica se agregó en Git 1.7.0 (febrero de 2012). Los pasos para hacer un clon disperso son los siguientes:

mkdir <repo>
cd <repo>
git init
git remote add -f origin <url>

Esto crea un repositorio vacío con su control remoto y recupera todos los objetos pero no los desprotege. Entonces hazlo:

git config core.sparseCheckout true

Ahora necesita definir qué archivos/carpetas desea verificar. Esto se hace enumerándolos en .git/info/sparse-checkout, por ejemplo:

echo "some/dir/" >> .git/info/sparse-checkout
echo "another/sub/tree" >> .git/info/sparse-checkout

Por último, pero no menos importante, actualiza tu repositorio vacío con el estado del control remoto:

git pull origin master

Ahora tendrá archivos "protegidos" para some/diry another/sub/treeen su sistema de archivos (con esas rutas todavía), y no habrá otras rutas presentes.

Es posible que desees echar un vistazo al tutorial ampliado y probablemente deberías leer la documentación oficial para el pago disperso y el árbol de lectura .

Como una función:

function git_sparse_clone() (
  rurl="$1" localdir="$2" && shift 2

  mkdir -p "$localdir"
  cd "$localdir"

  git init
  git remote add -f origin "$rurl"

  git config core.sparseCheckout true

  # Loops over remaining args
  for i; do
    echo "$i" >> .git/info/sparse-checkout
  done

  git pull origin master
)

Uso:

git_sparse_clone "http://github.com/tj/n" "./local/location" "/bin"

Tenga en cuenta que esto seguirá descargando todo el repositorio desde el servidor; solo el tamaño del proceso de pago se reduce. Por el momento no es posible clonar un solo directorio. Pero si no necesita el historial del repositorio, al menos puede ahorrar ancho de banda creando un clon superficial. Consulte la respuesta de undodan a continuación para obtener información sobre cómo combinar clonación superficial y pago disperso.


A partir de Git 2.25.0 (enero de 2020), se agrega un comando experimental de pago disperso en Git:

git sparse-checkout init
# same as:
# git config core.sparseCheckout true

git sparse-checkout set "A/B"
# same as:
# echo "A/B" >> .git/info/sparse-checkout

git sparse-checkout list
# same as:
# cat .git/info/sparse-checkout
Chronial avatar Dec 06 '2012 07:12 Chronial

git clone --filter+ git sparse-checkoutdescarga solo los archivos requeridos

Por ejemplo, para clonar solo archivos en el subdirectorio small/de este repositorio de prueba: https://github.com/cirosantilli/test-git-partial-clone-big-small-no-bigtree

git clone -n --depth=1 --filter=tree:0 \
  https://github.com/cirosantilli/test-git-partial-clone-big-small-no-bigtree
cd test-git-partial-clone-big-small-no-bigtree
git sparse-checkout set --no-cone small
git checkout

También puede seleccionar varios directorios para descargar con:

git sparse-checkout set --no-cone small small2

Sin embargo, este método no funciona para archivos individuales, pero aquí hay otro método que sí funciona: ¿ Cómo extraer escasamente un solo archivo de un repositorio de Git?

En esta prueba, la clonación es básicamente instantánea y podemos confirmar que el repositorio clonado es muy pequeño como deseamos:

du --apparent-size -hs * .* | sort -hs

donación:

2.0K    small
226K    .git

Ese repositorio de pruebas contiene:

  • un big/subdirectorio con 10 archivos de 10 MB
  • 10 archivos de 10 MB 0, 1, ... 9en el nivel superior (esto se debe a que ciertos intentos anteriores descargarían archivos de nivel superior)
  • a small/y small2/subdirectorios con 1000 archivos de un tamaño de un byte cada uno

Todos los contenidos son pseudoaleatorios y, por lo tanto, no se pueden comprimir, por lo que podemos notar fácilmente si alguno de los archivos grandes se descargó, por ejemplo, conncdu .

Entonces, si descargas algo que no deseas, obtendrás 100 MB adicionales y será muy notorio.

En lo anterior, git clonedescarga un único objeto, presumiblemente el compromiso:

Cloning into 'test-git-partial-clone-big-small'...
remote: Enumerating objects: 1, done.
remote: Counting objects: 100% (1/1), done.
remote: Total 1 (delta 0), reused 1 (delta 0), pack-reused 0
Receiving objects: 100% (1/1), done.

y luego el pago final descarga los archivos que solicitamos:

remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 3 (delta 0), pack-reused 0
Receiving objects: 100% (3/3), 10.19 KiB | 2.04 MiB/s, done.
remote: Enumerating objects: 253, done.
remote: Counting objects: 100% (253/253), done.
Receiving objects: 100% (253/253), 2.50 KiB | 2.50 MiB/s, done.
remote: Total 253 (delta 0), reused 253 (delta 0), pack-reused 0
Your branch is up to date with 'origin/master'.

Probado en git 2.37.2, Ubuntu 22.10, en enero de 2023.

TODO también evita la descarga de objetos de árbol innecesarios

El método anterior descarga todos los objetos del árbol Git (es decir, listados de directorios, pero no el contenido real del archivo). Podemos confirmarlo ejecutando:

git ls-files

y viendo que contiene los directorios archivos grandes como:

big/0

En la mayoría de los proyectos esto no será un problema, ya que deberían ser pequeños en comparación con el contenido real del archivo, pero al perfeccionista que hay en mí le gustaría evitarlos.

También creé un repositorio muy extremo con algunos objetos de árbol muy grandes (100 MB) en el directorio big_tree: https://github.com/cirosantilli/test-git-partial-clone-big-small

¡Avíseme si alguien encuentra una manera de clonar solo el small/directorio!

Sobre los comandos

La --filteropción se agregó junto con una actualización del protocolo remoto y realmente evita que se descarguen objetos del servidor.

Desafortunadamente, la sparse-checkoutpieza también es necesaria. También puedes descargar solo ciertos archivos con el formato mucho más comprensible:

git clone --depth 1  --filter=blob:none  --no-checkout \
  https://github.com/cirosantilli/test-git-partial-clone-big-small
cd test-git-partial-clone-big-small
git checkout master -- d1

pero ese método, por alguna razón, descarga archivos uno por uno muy lentamente , lo que lo hace inutilizable a menos que tenga muy pocos archivos en el directorio.

Otro intento menos detallado pero fallido fue:

git clone --depth 1 --filter=blob:none --sparse \
  https://github.com/cirosantilli/test-git-partial-clone-big-small
cd test-git-partial-clone-big-small
git sparse-checkout set small

but that downloads all files in the toplevel directory: How to prevent git clone --filter=blob:none --sparse from downloading files on the root directory?

The dream: any directory can have web interface metadata

This feature could revolutionize Git.

Imagine having all the code base of your enterprise in a single monorepo without ugly third-party tools like repo.

Imagine storing huge blobs directly in the repo without any ugly third party extensions.

Imagine if GitHub would allow per file / directory metadata like stars and permissions, so you can store all your personal stuff under a single repo.

Imagine if submodules were treated exactly like regular directories: just request a tree SHA, and a DNS-like mechanism resolves your request, first looking on your local ~/.git, then first to closer servers (your enterprise's mirror / cache) and ending up on GitHub.

I have a dream.

The test cone monorepo philosophy

This is a possible philosophy for monorepo maintenance without submodules.

We want to avoid submodules because it is annoying to have to commit to two separate repositories every time you make a change that has a submodule and non-submodule component.

Every directory with a Makefile or analogous should build and test itself.

Such directories can depend on either:

  • every file and subdirectory under it directly at their latest versions
  • external directories can be relied upon only at specified versions

Until git starts supporting this natively (i.e. submodules that can track only subdirectories), we can support this with some metadata in a git tracked file:

monorepo.json

{
    "path": "some/useful/lib",
    "sha": 12341234123412341234,
}

where sha refers to the usual SHA of the entire repository. Then we need scripts that will checkout such directories e.g. under a gitignored monorepo folder:

monorepo/som/useful/lib

Whenever you change a file, you have to go up the tree and test all directories that have Makefile. This is because directories can depend on subdirectories at their latest versions, so you could always break something above you.

Related:

  • https://github.com/josh-project/josh
  • https://www.pantsbuild.org/