Como faço para clonar um subdiretório apenas de um repositório Git?

tenho o meu repositório Git que, na raiz, tem duas sub-pastas:

/finisht
/static

Quando isto estava em SVN, /finisht foi verificado em um lugar, enquanto /static foi verificado em outro lugar, assim:

svn co svn+ssh://[email protected]/home/admin/repos/finisht/static static
Há alguma maneira de fazer isto com o Git?

Author: Peter Mortensen, 2009-03-01

11 answers

Não, isso não é possível em Git.

Implementar algo como isto no Git seria um esforço substancial e significaria que a integridade do repositório de clientes não poderia mais ser garantida. Se você estiver interessado, procure por discussões sobre" sparse clone "e" sparse fetch " na git mailinglist.

Em geral, o consenso na comunidade Git é que se você tem vários diretórios que são sempre verificados independentemente, então estes são realmente dois projetos diferentes e deve viver em dois repositórios diferentes. Você pode colá-los novamente usando submódulos Git .

 429
Author: Jörg W Mittag, 2018-08-31 14:51:53

O que você está tentando fazer é chamado de checkoutesparso , e esse recurso foi adicionado no git 1.7.0 (Feb. 2012). Os passos para fazer um clone esparso são os seguintes:

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

Isto cria um repositório vazio com o seu remoto, e obtém todos os objectos mas não os verifica. Então faça:

git config core.sparseCheckout true

Agora você precisa definir quais arquivos / pastas você deseja realmente verificar. Isto é feito listando-os em .git/info/sparse-checkout, por exemplo:

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

Por último, mas não no mínimo, actualize o seu repo vazio com o estado a partir do remoto:

git pull origin master

Agora você terá arquivos "checked out" para some/dir e another/sub/tree em seu sistema de arquivos (com esses caminhos ainda), e nenhum outro caminho presente.

Você pode querer dar uma olhada no tutorial extended e você deve provavelmente ler a documentação oficial para checkout esparso.

Em função:

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
)

Utilização:

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

Note que isto ainda vai faça o download de todo o repositório do servidor-apenas o checkout é reduzido em tamanho. De momento, não é possível clonar apenas um único diretório. Mas se você não precisa do histórico do repositório, você pode pelo menos economizar na largura de banda criando um clone raso. Veja a resposta de udondan abaixo para obter informações sobre como combinar o clone raso e a checkout esparsa.

 1392
Author: Chronial, 2018-04-03 14:05:35

Pode combinar as opçõesesparsa e as funcionalidadesdo clone raso . O Clone raso corta o histórico e a caixaesparsa só puxa os ficheiros que correspondem aos seus padrões.

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
Vais precisar de um mínimo de 1,9 git para isto funcionar. Eu próprio o testei apenas com 2.2.0 e 2.2.2.

Assim ainda poderás empurrar, o que não é possível com git archive.

 341
Author: udondan, 2015-02-25 00:22:18

Para outros utilizadores que Só quero transferir um ficheiro / pasta do github, basta usar:

svn export <repo>/trunk/<folder>

Por exemplo

svn export https://github.com/lodash/lodash.com/trunk/docs
(Sim, é a svn aqui. aparentemente, em 2016 você ainda precisa svn para simplesmente baixar alguns arquivos github) {[[10]}

Cortesia: obter uma única pasta ou directório de um acordo GitHub

Important - certifique-se que actualiza o URL do github e substitui /tree/master/ por '/trunk/'.

Como bash script:

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

Nota Este método transfere uma pasta, não a copia/verifica. Você não pode empurrar as mudanças de volta para o repositório. Por outro lado-isso resulta em download menor em comparação com o checkout esparso ou checkout raso.

 90
Author: Anona112, 2018-06-18 11:32:16

O Git 1.7.0 tem "check-out esparsos". Ver "Nucleo.sparseCheckout " no git config manpage , "Caixa escassa" na git read-tree manpage, e "Skip-worktree bit" No git update-index manpage .

A interface não é tão conveniente como a do SVN (por exemplo, não há maneira de fazer um checkout esparso no momento de um clone inicial), mas a funcionalidade base sobre a qual as interfaces mais simples podem ser construídas é agora disponivel.

 63
Author: Chris Johnsen, 2012-06-06 04:42:07

Se nunca planear interagir com o repositório a partir do qual clonou, poderá fazer um clone completo do git e reescrever o seu repositório usando o git filter-branch --subdirectory-filter. Assim, pelo menos a história será preservada.

 63
Author: hillu, 2015-06-14 09:09:34

Este parece muito mais simples:

git archive --remote=<repo_url> <branch> <path> | tar xvf -
 60
Author: ErichBSchulz, 2016-09-02 19:22:44

Não é possível clonar subdiretório apenas com Git, mas abaixo estão poucos workarounds.

Ramificação do filtro

Você pode querer reescrever o repositório para olhar como se trunk/public_html/ tivesse sido a raiz do seu projecto, e descartar todo o outro histórico (usando filter-branch), experimente já o 'checkout branch':

git filter-branch --subdirectory-filter trunk/public_html -- --all

notas: o -- que separa as opções do ramo do filtro das opções de revisão, e o --all para reescrever todas as ramificações e marcas. Todas as informações, incluindo as horas de envio originais ou a informação da junção serão preservadas . Este comando Honra .git/info/grafts o ficheiro e os Ref no espaço de nomes refs/replace/, por isso, se tiver algum enxerto ou substituição definido refs, se executar este comando irá torná-los permanentes.

Atenção! O histórico reescrito terá diferentes nomes de objetos para todos os objetos e não convergirá com o ramo original. Você não será capaz de empurrar e distribuir facilmente o ramo reescrito em cima do original ramo. Por favor, não use este comando se você não sabe todas as implicações, e evite usá-lo de qualquer maneira, se um único commit simples seria suficiente para corrigir o seu problema.

Xeque-out esparso

Aqui estão os passos simples com a abordagem esparsa que irá povoar a pasta de trabalho de forma esparsa, para que possa dizer ao Git que Pasta(s) ou Ficheiro(s) na pasta de trabalho valem a pena verificar.

  1. Repositório de clones como de costume (--no-checkout é facultativo):

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

    você pode saltar este passo, se você já clonou o seu repositório.

    Dica: para repos grandes, considere clone superficial (--depth 1) para obter apenas a última revisão ou / e --single-branch apenas.

  2. Activar sparseCheckout opção:

    git config core.sparseCheckout true
    
  3. Indique a(s) Pasta (s) para a obtenção esparsa (sem espaço no fim):

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

    Ou editar .git/info/sparse-checkout.

  4. Verifique o ramo (ex. master):

    git checkout master
    

Agora deverá ter seleccionado as pastas na sua pasta actual.

Poderá considerar ligações simbólicas se tiver demasiados níveis de pastas ou ramificação de filtragem em alternativa.


 23
Author: kenorb, 2016-07-21 22:12:12

Eu apenas escrevi um guiãopara o GitHub.

Utilização:

python get_git_sub_dir.py path/to/sub/dir <RECURSIVE>
 11
Author: david_adler, 2016-09-02 19:22:28

Aqui está um script shell que eu escrevi para o caso de uso de uma única pasta de checkout esparse

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
 4
Author: jxramos, 2018-03-08 19:39:46

git clone --filter do Git 2.19

Esta opção irá de facto saltar a obtenção de objectos sem necessidade do servidor:

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

O servidor deve ser configurado com:

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

Não existe suporte de servidor a partir de v2. 19. 0, mas já pode ser testado localmente.

TODO: --filter=blob:none Salta todas as bolhas, mas ainda Apanha todos os objectos das árvores. Mas em um repo normal, este deve ser minúsculo em comparação com os arquivos em si, então isso já é bom o suficiente. Perguntado em: https://www.spinics.net/lists/git/msg342006.html Devs respondeu que um --filter=tree:0 está em obras para fazer isso.

Lembre-se que --depth 1 já implica --single-branch, Veja também: Como clonar um único ramo no git?

file://$(path) é necessário ultrapassar git clone as brincadeiras do protocolo: Como clonar um repositório de git local com um caminho relativo?

O formato de --filter está documentado em man git-rev-list.

Docs on Git árvore:

Teste-o

#!/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 upstream .

Saída no 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

Conclusões: todas as bolhas de fora de {[13] } estão desaparecidos. Por exemplo, 0975df9b39e23c15f63db194df7f45c76528bccb, que é d2/b não está lá depois de sair d1/a.

Note que root/root e mybranch/mybranch também estão em falta, mas --depth 1 esconde isso da lista de ficheiros em falta. Se você remover --depth 1, então eles aparecem na lista de arquivos em falta.