代码之家  ›  专栏  ›  技术社区  ›  Nick Sergeant

如何仅克隆Git存储库的子目录?

  •  1120
  • Nick Sergeant  · 技术社区  · 17 年前

    我有我的Git存储库,它在根目录下有两个子目录:

    /finisht
    /static
    

    这是什么时候 SVN /finisht /static 是在其他地方查到的,就像这样:

    svn co svn+ssh://admin@domain.com/home/admin/repos/finisht/static static
    

    有没有办法用Git做到这一点?

    11 回复  |  直到 5 年前
        1
  •  4
  •   Carson    5 年前

    你想做的事叫做 ,并且该功能已添加到git 1.7.0中(2012年2月)。进行稀疏搜索的步骤 克隆 详情如下:

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

    这将创建一个空的远程存储库,并获取所有对象,但不签出它们。然后做:

    git config core.sparseCheckout true
    

    .git/info/sparse-checkout ,例如:

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

    最后但并非最不重要的一点是,使用远程服务器的状态更新您的空回购:

    git pull origin master
    

    some/dir another/sub/tree 在文件系统上(这些路径仍然存在),并且不存在其他路径。

    extended tutorial 你也许应该看看官方的报告 documentation for sparse checkout read-tree .

    作为一项功能:

    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
    )
    

    用法:

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

    udondan's answer 下面是有关如何组合浅层的信息 clone


    自git 2.25.0(2020年1月)起,试验性 sparse-checkout 命令已添加到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
    
        2
  •  2
  •   Eric Stricklin    5 年前

    git clone --filter

    此选项是与远程协议的更新一起添加的,它确实可以防止从服务器下载对象。

    例如,仅克隆 d1 https://github.com/cirosantilli/test-git-partial-clone 我可以做到:

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

    这里有一个不那么简单和更现实的版本 https://github.com/cirosantilli/test-git-partial-clone-big-small

    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
    

    该存储库包含:

    • 包含10个10MB文件的大目录
    • 一个小目录,包含1000个大小为一字节的文件

    所有内容都是伪随机的,因此不可压缩。

    36.4 Mbps internet上的克隆次数:

    • 完整:24秒
    • 部分:“瞬时”

    这个 sparse-checkout

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

    但这种方法是有原因的 downloads files one by one very slowly ,使其无法使用,除非目录中的文件很少。

    分析最小存储库中的对象

    clone命令仅获取:

    然后, git sparse-checkout set 命令仅从服务器获取丢失的blob(文件):

    • d1/a
    • d1/b

    更好的是,稍后GitHub可能会开始支持:

      --filter=blob:none \
      --filter=tree:0 \
    

    --filter=tree:0 from Git 2.20 将防止不必要的 clone 获取所有树对象,并允许将其延迟到 checkout . 但在我的2020-09-18测试中,失败的原因是:

    fatal: invalid filter-spec 'combine:blob:none+tree:0'
    

    --filter=combine: 复合过滤器(在Git 2.24中添加,由多个 --filter )尚未实施。

    我观察到哪些对象是通过以下方式获取的:

    git verify-pack -v .git/objects/pack/*.pack
    

    如所述: How to list ALL git objects in the database? 它并没有给我一个超清晰的指示,每个对象是什么,但它确实说明了每个对象的类型( commit tree , blob

    git rev-list --objects --all 确实为树/blob生成了更清晰的路径输出,但不幸的是,当我运行它时,它会提取一些对象,这使得很难确定提取了什么。如果有人有更好的命令,请告诉我。

    https://github.blog/2020-01-17-bring-your-monorepo-down-to-size-with-sparse-checkout/ --filter blob:none .

    git sparse-checkout

    我认为这个命令是用来管理一个设置文件,上面写着“我只关心这些子树”,这样将来的命令只会影响这些子树。但这有点难以确定,因为当前的文档有点。。。稀疏;-)

    它本身并不阻止抓取blob。

    如果这种理解是正确的,那么这将是对 git克隆--过滤器

    当我在Git 2.25.1上试用时:

    git clone \
      --depth 1 \
      --filter=blob:none \
      --no-checkout \
      https://github.com/cirosantilli/test-git-partial-clone \
    ;
    cd test-git-partial-clone
    git sparse-checkout init
    

    init 实际获取了所有对象。

    然而,在Git2.28中,它没有按照需要获取对象。但如果我这样做了:

    git sparse-checkout set d1
    

    d1 未提取并签出,即使这明确说明它应该: https://github.blog/2020-01-17-bring-your-monorepo-down-to-size-with-sparse-checkout/#sparse-checkout-and-partial-clones

    请注意部分克隆功能是否可以普遍使用[1]。

    [1] :GitHub仍在内部评估此功能,同时在一些选定的存储库(包括本文中使用的示例)上启用此功能。随着功能的稳定和成熟,我们会不断更新它的进度。

    所以是的,现在很难确定,部分原因是GitHub开源带来的乐趣。但是让我们注意一下。

    命令分解

    服务器应配置为:

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

    • --filter=blob:none 跳过所有blob,但仍然获取所有blob tree objects

    • --筛选器=树:0 跳过不需要的树: https://www.spinics.net/lists/git/msg342006.html

    • --depth 1 已经暗示 --single-branch How do I clone a single branch in Git?

    • file://$(path) git clone 协议诡计: How to shallow clone a local git repository with a relative path?

    • --filter=combine:FILTER1+FILTER2 是一次使用多个筛选器并尝试传递的语法 由于某些原因,失败的原因是:“无法组合多个过滤器规格”。这是在Git 2.24的E987DF5FE62B8B29BE4CDCDEB3704681ADA29E“列表对象过滤器:实现复合过滤器”中添加的

      编辑:在Git2.28上,我实验性地看到了这一点 --filter=FILTER1 --filter FILTER2 也有同样的效果,因为GitHub没有实现 combine: fatal: invalid filter-spec 'combine:blob:none+tree:0' . TODO在哪个版本中引入?

    --滤器 man git-rev-list .

    Git树上的文档:

    https://github.com/cirosantilli/test-git-partial-clone 本地存储库,进行本地克隆,并观察克隆的内容:

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

    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
    

    结论:所有斑点均来自外部 d1/ 他们失踪了。例如。 0975df9b39e23c15f63db194df7f45c76528bccb ,即 d2/b d1/a .

    注意 root/root mybranch/mybranch 也不见了,但是 --深度1

    我有一个梦想

    这个特性可能会彻底改变Git。

    in a single repo 没有 ugly third-party tools like repo

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

    想象一下,如果GitHub允许 per file / directory metadata 就像明星和权限一样,你可以在一次回购下储存你所有的个人物品。

    想象一下如果 submodules were treated exactly like regular directories :只需请求一棵树,然后 DNS-like mechanism resolves your request ,首先看看你的 local ~/.git ,然后首先是较近的服务器(企业的镜像/缓存),最后是GitHub。

        3
  •  1
  •   Mike Slinn    5 年前

    编辑 :从Git 2.19中可以看出,这最终是可能的 answer .

    考虑一下投票的答案。

    注意:在Git2.19中,只实现了客户端支持,而服务器端支持仍然缺失,因此它只在克隆本地存储库时起作用。还要注意的是,大型Git主机,例如GitHub,实际上并不使用Git服务器,而是使用自己的实现,因此,即使支持出现在Git服务器上,也并不自动意味着它可以在Git主机上工作。(OTOH,因为他们不使用Git服务器,所以在Git服务器出现之前,他们可以在自己的实现中更快地实现它。)


    不,这在Git中是不可能的。

    在Git中实现类似的东西将是一项巨大的工作,这将意味着客户端存储库的完整性不再能够得到保证。如果您感兴趣,请在git邮件列表中搜索关于“稀疏克隆”和“稀疏获取”的讨论。

    Git Submodules .

        4
  •  0
  •   Friedrich -- Слава Україні    4 年前

    稀疏校验 浅克隆 浅克隆 断绝历史和历史 只提取与您的模式匹配的文件。

    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
    

    您需要最低git 1.9才能正常工作。我自己只用2.2.0和2.2.2进行了测试。

    ,这是不可能的 git archive .

    推荐文章