web-dev-qa-db-fra.com

Comment cloner un sous-répertoire uniquement d'un référentiel Git?

J'ai mon dépôt Git qui, à la racine, a deux sous-répertoires:

/finisht
/static

Quand cela se trouvait dans SVN , /finisht a été extrait à un endroit, alors que /static a été extrait ailleurs, comme suit:

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

Y a-t-il un moyen de faire cela avec Git?

1242
Nick Sergeant

EDIT: A partir de Git 2.19, ceci est enfin possible, comme on peut le voir dans cette réponse: https: // stackoverflow. com/a/52269934/2988 .

Envisagez de faire voter cette réponse.

Remarque: dans Git 2.19, seul le support côté client est implémenté. Le support côté serveur est toujours manquant. Il ne fonctionne donc que lors du clonage de référentiels locaux. Notez également que les grands hébergeurs Git, par exemple GitHub, n'utilisez pas réellement le serveur Git, ils utilisent leur propre implémentation, donc même si le support apparaît sur le serveur Git, cela ne signifie pas automatiquement que cela fonctionne sur les hôtes Git. (OTOH, comme ils n'utilisent pas le serveur Git, ils pourraient l'implémenter plus rapidement dans leurs propres implémentations avant qu'il n'apparaisse dans le serveur Git.)


Non, ce n'est pas possible dans Git.

Implémenter quelque chose comme cela dans Git représenterait un effort considérable et signifierait que l'intégrité du référentiel côté client ne pourrait plus être garantie. Si vous êtes intéressé, lancez une discussion sur "clone rare" et "recherche partielle" sur la liste de diffusion git.

En général, dans la communauté Git, le consensus est que si plusieurs répertoires sont toujours extraits de manière indépendante, il s’agit en réalité de deux projets différents et ils doivent être placés dans deux référentiels différents. Vous pouvez les recoller en utilisant Git Submodules .

531
Jörg W Mittag

Ce que vous tentez de faire s'appelle une extraction clairsemée , et cette fonctionnalité a été ajoutée dans git 1.7.0 (février 2012). Les étapes pour faire un clone clairsemé sont les suivantes:

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

Cela crée un référentiel vide avec votre télécommande et récupère tous les objets sans les extraire. Alors fais:

git config core.sparseCheckout true

Vous devez maintenant définir les fichiers/dossiers que vous voulez réellement extraire. Ceci est fait en les listant dans .git/info/sparse-checkout, par exemple:

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

Dernier point mais non le moindre, mettez à jour votre référentiel vide avec l'état de la télécommande:

git pull Origin master

Vous aurez maintenant des fichiers "extraits" pour some/dir et another/sub/tree sur votre système de fichiers (avec ces chemins toujours), et aucun autre chemin présent.

Vous voudrez peut-être jeter un coup d'œil au tutoriel étend et vous devriez probablement lire le document officiel documentation relative au paiement fragmenté .

En tant que fonction:

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
)

Usage:

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

Notez que cela téléchargera tout le référentiel à partir du serveur - seule la taille de la caisse est réduite. Pour le moment, il n'est pas possible de cloner un seul répertoire. Mais si vous n'avez pas besoin de l'historique du référentiel, vous pouvez au moins économiser sur la bande passante en créant un clone peu profond. Voir réponse d'Udondan ci-dessous pour des informations sur la manière de combiner la procédure peu profonde clone et le contrôle clairsemé.

1461
Chronial

Vous pouvez combiner les fonctions de contrôle clairsemé et de clonage superficiel . Le clone peu profond coupe l'historique et le contrôle clairsemé ne tire que le fichiers correspondant à vos modèles.

git init <repo>
cd <repo>
git remote add Origin <url>
git config core.sparsecheckout true
echo "finisht/*" >> .git/info/sparse-checkout
git pull --depth=1 Origin master

Vous aurez besoin de minimum Git 1.9 pour que cela fonctionne. Testé moi-même uniquement avec 2.2.0 et 2.2.2.

De cette façon, vous pourrez toujours Push, ce qui n’est pas possible avec git archive.

377
udondan

git clone --filter depuis Git 2.19

En fait, cette option évite d'extraire des objets inutiles du serveur. En outre, y compris --filter=tree:0 de Git 2.2 nous nous retrouvons avec:

git clone \
  --depth 1 \
  --filter=blob:none \
  --filter=tree:0 \
  --no-checkout \
  "file://$(pwd)/server_repo" \
  local_repo \
;
cd local_repo
git checkout master -- mydir/

Le serveur doit être configuré avec:

git config --local uploadpack.allowfilter 1
git config --local uploadpack.allowanysha1inwant 1

Une extension du protocole distant Git a été réalisée pour prendre en charge cette fonctionnalité dans v2.19.0 et pour ignorer l'extraction d'objets inutiles, mais aucun serveur n'est pris en charge à ce moment-là. Mais il peut déjà être testé localement.

Répartition des commandes:

Le format de --filter est documenté sur man git-rev-list.

Documents sur l'arbre Git:

Testez-le

#!/usr/bin/env bash
set -eu

list-objects() (
  git rev-list --all --objects
  echo "master commit SHA: $(git log -1 --format="%H")"
  echo "mybranch commit SHA: $(git log -1 --format="%H")"
  git ls-tree master
  git ls-tree mybranch | grep mybranch
  git ls-tree master~ | grep root
)

# Reproducibility.
export GIT_COMMITTER_NAME='a'
export GIT_COMMITTER_EMAIL='a'
export GIT_AUTHOR_NAME='a'
export GIT_AUTHOR_EMAIL='a'
export GIT_COMMITTER_DATE='2000-01-01T00:00:00+0000'
export GIT_AUTHOR_DATE='2000-01-01T00:00:00+0000'

rm -rf server_repo local_repo
mkdir server_repo
cd server_repo

# Create repo.
git init --quiet
git config --local uploadpack.allowfilter 1
git config --local uploadpack.allowanysha1inwant 1

# First commit.
# Directories present in all branches.
mkdir d1 d2
printf 'd1/a' > ./d1/a
printf 'd1/b' > ./d1/b
printf 'd2/a' > ./d2/a
printf 'd2/b' > ./d2/b
# Present only in root.
mkdir 'root'
printf 'root' > ./root/root
git add .
git commit -m 'root' --quiet

# Second commit only on master.
git rm --quiet -r ./root
mkdir 'master'
printf 'master' > ./master/master
git add .
git commit -m 'master commit' --quiet

# Second commit only on mybranch.
git checkout -b mybranch --quiet master~
git rm --quiet -r ./root
mkdir 'mybranch'
printf 'mybranch' > ./mybranch/mybranch
git add .
git commit -m 'mybranch commit' --quiet

echo "# List and identify all objects"
list-objects
echo

# Restore master.
git checkout --quiet master
cd ..

# Clone. Don't checkout for now, only .git/ dir.
git clone --depth 1 --quiet --no-checkout --filter=blob:none "file://$(pwd)/server_repo" local_repo
cd local_repo

# List missing objects from master.
echo "# Missing objects after --no-checkout"
git rev-list --all --quiet --objects --missing=print
echo

echo "# Git checkout fails without internet"
mv ../server_repo ../server_repo.off
! git checkout master
echo

echo "# Git checkout fetches the missing directory from internet"
mv ../server_repo.off ../server_repo
git checkout master -- d1/
echo

echo "# Missing objects after checking out d1"
git rev-list --all --quiet --objects --missing=print

GitHub en amont .

Sortie dans Git v2.19.0:

# List and identify all objects
c6fcdfaf2b1462f809aecdad83a186eeec00f9c1
fc5e97944480982cfc180a6d6634699921ee63ec
7251a83be9a03161acde7b71a8fda9be19f47128
62d67bce3c672fe2b9065f372726a11e57bade7e
b64bf435a3e54c5208a1b70b7bcb0fc627463a75 d1
308150e8fddde043f3dbbb8573abb6af1df96e63 d1/a
f70a17f51b7b30fec48a32e4f19ac15e261fd1a4 d1/b
84de03c312dc741d0f2a66df7b2f168d823e122a d2
0975df9b39e23c15f63db194df7f45c76528bccb d2/a
41484c13520fcbb6e7243a26fdb1fc9405c08520 d2/b
7d5230379e4652f1b1da7ed1e78e0b8253e03ba3 master
8b25206ff90e9432f6f1a8600f87a7bd695a24af master/master
ef29f15c9a7c5417944cc09711b6a9ee51b01d89
19f7a4ca4a038aff89d803f017f76d2b66063043 mybranch
1b671b190e293aa091239b8b5e8c149411d00523 mybranch/mybranch
c3760bb1a0ece87cdbaf9a563c77a45e30a4e30e
a0234da53ec608b54813b4271fbf00ba5318b99f root
93ca1422a8da0a9effc465eccbcb17e23015542d root/root
master commit SHA: fc5e97944480982cfc180a6d6634699921ee63ec
mybranch commit SHA: fc5e97944480982cfc180a6d6634699921ee63ec
040000 tree b64bf435a3e54c5208a1b70b7bcb0fc627463a75    d1
040000 tree 84de03c312dc741d0f2a66df7b2f168d823e122a    d2
040000 tree 7d5230379e4652f1b1da7ed1e78e0b8253e03ba3    master
040000 tree 19f7a4ca4a038aff89d803f017f76d2b66063043    mybranch
040000 tree a0234da53ec608b54813b4271fbf00ba5318b99f    root

# Missing objects after --no-checkout
?f70a17f51b7b30fec48a32e4f19ac15e261fd1a4
?8b25206ff90e9432f6f1a8600f87a7bd695a24af
?41484c13520fcbb6e7243a26fdb1fc9405c08520
?0975df9b39e23c15f63db194df7f45c76528bccb
?308150e8fddde043f3dbbb8573abb6af1df96e63

# Git checkout fails without internet
fatal: '/home/ciro/bak/git/test-git-web-interface/other-test-repos/partial-clone.tmp/server_repo' does not appear to be a git repository
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

# Git checkout fetches the missing directory from internet
remote: Enumerating objects: 1, done.
remote: Counting objects: 100% (1/1), done.
remote: Total 1 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (1/1), 45 bytes | 45.00 KiB/s, done.
remote: Enumerating objects: 1, done.
remote: Counting objects: 100% (1/1), done.
remote: Total 1 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (1/1), 45 bytes | 45.00 KiB/s, done.

# Missing objects after checking out d1
?8b25206ff90e9432f6f1a8600f87a7bd695a24af
?41484c13520fcbb6e7243a26fdb1fc9405c08520
?0975df9b39e23c15f63db194df7f45c76528bccb

Conclusions: tous les blobs extérieurs à d1/ sont manquants. Par exemple. 0975df9b39e23c15f63db194df7f45c76528bccb, qui est d2/b n'est pas là après avoir extrait d1/a.

Notez que root/root et mybranch/mybranch sont également manquants, mais --depth 1 le masque dans la liste des fichiers manquants. Si vous supprimez --depth 1, ils apparaîtront dans la liste des fichiers manquants.

J'ai un rêve

Cette fonctionnalité pourrait révolutionner Git.

Imaginez avoir toute la base de code de votre entreprise dans un même référentiel sans outils tiers laids comme repo .

Imaginez stocker d'énormes blobs directement dans le dépôt sans aucune extension tierce laide .

Imaginez si GitHub autorisait par métadonnées de fichiers/répertoires comme les étoiles et les autorisations, de sorte que vous puissiez stocker toutes vos données personnelles sous un même référentiel.

Imaginez si les sous-modules ont été traités exactement de la même manière que les répertoires classiques : demandez simplement un arbre SHA, et un n mécanisme de type DNS résout votre demande , en cherchant d’abord sur votre local ~/.git , puis d'abord sur les serveurs les plus proches (le cache/le miroir de votre entreprise) et ensuite sur GitHub.

Pour les autres utilisateurs qui veulent simplement télécharger un fichier/dossier de github, utilisez simplement:

svn export <repo>/trunk/<folder>

par exemple.

svn export https://github.com/lodash/lodash.com/trunk/docs

(oui, c'est svn ici. apparemment en 2016, vous avez toujours besoin de svn pour télécharger simplement des fichiers github)

Courtesy: Télécharger un seul dossier ou répertoire depuis un dépôt GitHub

Important - Assurez-vous de mettre à jour l'URL de github et de remplacer /tree/master/ par '/ trunk /'.

Comme script bash:

git-download(){
    folder=${@/tree\/master/trunk}
    folder=${folder/blob\/master/trunk}
    svn export $folder
}

Remarque Cette méthode télécharge un dossier sans la cloner/la extraire. Vous ne pouvez pas repousser les modifications dans le référentiel. D'autre part, le téléchargement est moins volumineux que pour les caisses peu volumineuses ou peu profondes.

129
Anona112

Si vous n'envisagez jamais d'interagir avec le référentiel à partir duquel vous avez cloné, vous pouvez effectuer un git clone complet et réécrire votre référentiel à l'aide de git filter-branch --subdirectory-filter. De cette façon, au moins l'histoire sera préservée.

70
hillu

Git 1.7.0 a des "caisses rares". Voir “core.sparseCheckout” dans la config git , “Paiement partiel” dans la git read-tree page de manuel , et “Skip-worktree bit” dans git update-index page de manuel .

L’interface n’est pas aussi pratique que celle de SVN (par exemple, il n’existe aucun moyen de procéder à une extraction clairsemée au moment du clonage initial), mais la fonctionnalité de base sur laquelle des interfaces plus simples pourraient être construites est maintenant disponible.

63
Chris Johnsen

This semble beaucoup plus simple:

git archive --remote=<repo_url> <branch> <path> | tar xvf -
61
ErichBSchulz

Il n'est pas possible de cloner un sous-répertoire uniquement avec Git, mais voici quelques solutions de contournement.

Filtrer la branche

Vous voudrez peut-être réécrire le référentiel pour lui donner l’impression que _trunk/public_html/_ était sa racine de projet et supprimer tout autre historique (en utilisant filter-branch ), essayez une branche déjà extraite:

_git filter-branch --subdirectory-filter trunk/public_html -- --all
_

Remarques: Le _--_ qui sépare les options de branche de filtre des options de révision et le _--all_ pour réécrire toutes les branches et les balises. Toutes les informations, y compris les temps de validation ou de fusion d'origine, seront conservées . Cette commande honore le fichier _.git/info/grafts_ et fait référence à l'espace de noms _refs/replace/_. Par conséquent, si vous avez défini des greffes ou des remplacements refs, leur exécution les rendra permanents.

Attention! L'historique réécrit aura des noms d'objet différents pour tous les objets et ne convergera pas vers la branche d'origine. Vous ne pourrez pas facilement pousser et distribuer la branche réécrite au-dessus de la branche d'origine. Veuillez ne pas utiliser cette commande si vous ne connaissez pas toutes les implications et évitez de l'utiliser de toute façon, si un simple commit suffit à résoudre votre problème.


Caisse clairsemée

Voici quelques étapes simples avec l’approche contrôle parcellaire qui peuplera le répertoire de travail de manière parcimonieuse, afin que vous puissiez indiquer à Git le ou les dossiers ou fichiers dans le répertoire de travail qui valent la peine d’être vérifiés.

  1. Cloner le référentiel comme d'habitude (_--no-checkout_ est facultatif):

    _git clone --no-checkout git@foo/bar.git
    cd bar
    _

    Vous pouvez ignorer cette étape si votre référentiel est déjà cloné.

    Astuce: Pour les gros dépôts, pensez à clone peu profond (_--depth 1_) pour ne commander que la dernière révision ou/et _--single-branch_ uniquement.

  2. Activer l'option sparseCheckout:

    _git config core.sparseCheckout true
    _
  3. Spécifiez le (s) dossier (s) pour le contrôle fragmenté ( sans espace à la fin):

    _echo "trunk/public_html/*"> .git/info/sparse-checkout
    _

    ou éditez _.git/info/sparse-checkout_.

  4. Extraire la branche (par exemple, master):

    _git checkout master
    _

Vous devriez maintenant avoir sélectionné des dossiers dans votre répertoire actuel.

Vous pouvez envisager des liens symboliques si vous avez trop de niveaux de répertoires ou de branche de filtrage à la place.


34
kenorb

Je viens de a écrit un script pour GitHub .

Usage:

python get_git_sub_dir.py path/to/sub/dir <RECURSIVE>
9
david_adler

Voici un script de shell que j'ai écrit pour le cas d'utilisation d'un contrôle fragmenté simple

coSubDir.sh

localRepo=$1
remoteRepo=$2
subDir=$3


# Create local repository for subdirectory checkout, make it hidden to avoid having to drill down to the subfolder
mkdir ./.$localRepo
cd ./.$localRepo
git init
git remote add -f Origin $remoteRepo
git config core.sparseCheckout true

# Add the subdirectory of interest to the sparse checkout.
echo $subDir >> .git/info/sparse-checkout

git pull Origin master

# Create convenience symlink to the subdirectory of interest
cd ..
ln -s ./.$localRepo$subDir $localRepo
5
jxramos

Cela clonera un dossier spécifique et supprimera tout l'historique qui ne lui est pas lié.

git clone --single-branch -b {branch} [email protected]:{user}/{repo}.git
git filter-branch --subdirectory-filter {path/to/folder} HEAD
git remote remove Origin
git remote add Origin [email protected]:{user}/{new-repo}.git
git Push -u Origin master
5
BARJ

J'ai écrit un .gitconfig[alias] pour effectuer un "paiement clairsemé". Check it out (sans jeu de mots):

Sous Windows, exécutez cmd.exe

git config --global alias.sparse-checkout "!f(){ [ $# -eq 2 ] && L=${1##*/} L=${L%.git} || L=$2; mkdir -p \"$L/.git/info\" && cd \"$L\" && git init --template= && git remote add Origin \"$1\" && git config core.sparseCheckout 1; [ $# -eq 2 ] && echo \"$2\" >> .git/info/sparse-checkout || { shift 2; for i; do echo $i >> .git/info/sparse-checkout; done }; git pull --depth 1 Origin master;};f"

Autrement:

git config --global alias.sparse-checkout '!f(){ [ $# -eq 2 ] && L=${1##*/} L=${L%.git} || L=$2; mkdir -p "$L/.git/info" && cd "$L" && git init --template= && git remote add Origin "$1" && git config core.sparseCheckout 1; [ $# -eq 2 ] && echo "$2" >> .git/info/sparse-checkout || { shift 2; for i; do echo $i >> .git/info/sparse-checkout; done }; git pull --depth 1 Origin master;};f'

sage:

# Makes a directory ForStackExchange with Plug checked out
git sparse-checkout https://github.com/YenForYang/ForStackExchange Plug

# To do more than 1 directory, you have to specify the local directory:
git sparse-checkout https://github.com/YenForYang/ForStackExchange ForStackExchange Plug Folder

Les commandes git config sont 'minifiées' pour des raisons de commodité et de stockage, mais voici l'alias développé:

# Note the --template= is for disabling templates.
# Feel free to remove it if you don't have issues with them (like I did)
# `mkdir` makes the .git/info directory ahead of time, as I've found it missing sometimes for some reason
f(){
    [ "$#" -eq 2 ] && L="${1##*/}" L=${L%.git} || L=$2;
    mkdir -p "$L/.git/info"
        && cd "$L"
        && git init --template=
        && git remote add Origin "$1"
        && git config core.sparseCheckout 1;
    [ "$#" -eq 2 ]
        && echo "$2" >> .git/info/sparse-checkout
        || {
            shift 2;
            for i; do
                echo $i >> .git/info/sparse-checkout;
            done
        };
    git pull --depth 1 Origin master;
};
f
1
YenForYang