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

2001

我有一个Git仓库,根目录下有两个子目录:

/finisht
/static

当这个项目在SVN上时,/finisht被检出到一个地方,而/static则被检出到另一个地方,如下所示:

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

有没有使用Git的方法可以实现这个?


18
可能是 Checkout subdirectories in Git? 的重复问题。 - Joachim Breitner
2
对于一个2014年的用户,git clone最简单的命令是什么?我使用了这个简单的答案。如果有更简单的方法,请评论。 - Peter Krauss
1
对于那些尝试克隆存储库内容(而不是创建根文件夹)的人来说,这是一个非常简单的解决方案:https://dev59.com/zG025IYBdhLWcg3wFxua - Marc
2
@NickSergeant:自Git 2.19发布三周以来,这终于成为可能,可以在此答案中看到:https://dev59.com/QHRB5IYBdhLWcg3wgXhV#52269934 考虑现在接受它。注意:在Git 2.19中,仅实现了客户端支持,服务器端支持仍然缺失,因此仅在克隆本地存储库时才起作用。还要注意,大型Git主机(例如GitHub)实际上不使用Git服务器,而是使用自己的实现,因此即使支持出现在Git服务器上,也不意味着它会自动在Git主机上工作。(另一方面,他们可以更快地实现它。) - Jörg W Mittag
4
如果你想从 GitHub 存储库下载一个文件夹,https://download-directory.github.io/ 可能是个好选择。 - jemand771
显示剩余6条评论
31个回答

1839
你想要做的是叫做“稀疏检出”,这个特性在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”检出文件(仍具有这些路径),没有其他路径存在。 您可能想查看扩展教程,并且应该阅读官方稀疏检出文档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的答案以获取有关如何结合浅clone和稀疏检出的信息。
截至Git 2.25.0(2020年1月),Git中添加了一个实验性的sparse-checkout命令:
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

19
在苹果电脑上,'-f'参数无法正常工作。 只需执行以下命令:git remote add origin <url>,不要加上-f参数。 - Anno2001
169
这是一个改进,但仍然需要下载并存储远程仓库在origin的完整副本,如果只对代码库的部分感兴趣(或者像我一样有文档子文件夹),则可能希望尽可能避免这种情况。 - a1an
65
有没有办法将目标目录内容(而非目录本身)克隆到我的代码库中?例如,我想将https://github.com/Umkus/nginx-boilerplate/tree/master/src的内容直接克隆到 /etc/nginx - mac
28
@Chronial,@ErikE:你们两个都对/错:p git remote add 命令并不意味着要进行拉取(fetch), 但是在这里使用的 git remote add -f 命令会执行拉取(fetch)操作!这就是 -f 的含义。 - ntc2
25
使用以下命令:--depth=1,我克隆了 Chromium Devtools,只需要338MB的空间而不是完整的 Blink 源代码和历史记录,其大小为4.9GB。太好了! - Rudie
显示剩余28条评论

1217

git clone --filter + git sparse-checkout 可以仅下载所需的文件

例如,要在此测试存储库中仅克隆子目录small/中的文件: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
您也可以使用以下方法选择多个目录进行下载:
git sparse-checkout set --no-cone small small2

这种方法不能用于单个文件,但是有另一种方法可以实现:如何从git仓库中稀疏地只检出一个文件?

在这个测试中,克隆基本上是瞬间完成的,并且我们可以确认克隆的仓库非常小,符合我们的要求:

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

提供:

2.0K    small
226K    .git

该测试仓库包含以下内容:

  • 一个名为big/的子目录,其中包含10个大小为10MB的文件
  • 在顶层有10个大小为10MB的文件01、... 9(这是因为之前的某些尝试会下载顶层文件)
  • 一个名为small/small2/的子目录,其中包含1000个大小为一个字节的文件

所有内容都是伪随机的,因此不可压缩,因此我们可以轻松地注意到是否下载了任何大文件,例如使用ncdu

因此,如果您下载了任何不想要的内容,您将获得额外的100 MB,并且这将非常明显。

在上述情况下,git clone会下载单个对象,可能是提交:

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.

然后最终的结帐会下载我们请求的文件:

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'.

在2023年1月,已在git 2.37.2和Ubuntu 22.10上进行了测试。

TODO 还要防止下载不必要的树对象

上述方法会下载所有Git树对象(即目录列表,但不包括实际文件内容)。我们可以通过运行以下命令来确认:

git ls-files

而且看到它包含了像这样的大文件目录:

big/0
在大多数项目中,这不应该是一个问题,因为相对于实际文件内容来说,它们应该很小,但完美主义者的我想避免它们。 我还创建了一个非常极端的存储库,其中包含一些非常大的树对象(100 MB),位于目录big_tree下:https://github.com/cirosantilli/test-git-partial-clone-big-small 如果有人找到了从中克隆small/目录的方法,请告诉我! 关于命令: --filter选项是与远程协议更新一起添加的,它确实防止从服务器下载对象。 不幸的是,sparse-checkout部分也是必需的。您还可以使用更易理解的方式仅下载某些文件。
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

但是由于某种原因,该方法逐个下载文件非常缓慢,除非目录中只有很少的文件,否则它是无法使用的。

另一个不那么冗长但失败的尝试是:

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

但是这会下载顶级目录中的所有文件:如何防止 git clone --filter=blob:none --sparse 从根目录下载文件?

梦想:任何目录都可以拥有 Web 接口元数据

这个功能可以彻底改变 Git。

想象一下,将企业的所有代码库 存储在单个 monorepo 中,而不需要 丑陋的第三方工具,如 repo

想象一下,直接将大型 blob 存储在 repo 中,而不需要任何丑陋的第三方扩展

想象一下,如果GitHub允许像星标和权限这样的每个文件/目录元数据,那么您可以将所有个人物品存储在单个仓库中。

想象一下,如果子模块被视为常规目录:只需请求树SHA,以及类似于DNS的机制解决您的请求,首先查找您的本地 ~ / .git ,然后是更接近的服务器(您企业的镜像/缓存),最后到达GitHub。

我有一个梦想。

测试圆锥体单存储库哲学

这是一种无需子模块维护的单存储库维护的可能哲学。

我们希望避免使用子模块,因为每次更改具有子模块和非子模块组件时,都必须在两个不同的存储库中进行提交,这很麻烦。

每个具有Makefile或类似文件的目录都应该构建和测试自己。

这样的目录可能取决于以下两种情况:

  • 每个文件和其下直接最新版本的子目录
  • 外部目录仅在指定版本时可依赖

在git原生支持此功能之前(即只能跟踪子目录的子模块),我们可以通过git追踪的某些元数据来支持此功能:

monorepo.json

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

这里的sha指的是整个代码库的常规SHA。然后我们需要脚本来检出这些目录,例如在一个被Git忽略的monorepo文件夹下:

monorepo/som/useful/lib
每当您更改文件时,都必须向上遍历树并测试所有具有Makefile的目录。这是因为目录可以依赖于其最新版本的子目录,因此您可能会破坏上面的某些内容。 相关:

3
很遗憾,在 macOS 上使用的 git 版本出了问题。错误信息为“fatal: invalid filter-spec 'combine:blob:none+tree:0'”。无论如何,还是谢谢!也许更新的版本会有所改善。 - muru
2
当在Windows 10上使用GIT 2.24.1尝试运行时,此操作失败(抛出大量“无法读取sha1文件”+“文件xxx的取消链接失败”)。但在Linux上使用相同版本时,一切都很完美。 - Oyvind
3
这在 git 版本 2.26.1.windows.1 中仍然无法实现,出现 "unable to read sha1 file of..." 的错误。我已经提交了一个错误报告:https://github.com/git-for-windows/git/issues/2590 - nharrer
3
@CiroSantilli, 新疆棉花TRUMPBANBAD - 你已经找到了解决方案!只需删除--cone行,它就能正常工作。在您的测试存储库中尝试在顶层创建一个额外的文件。如果您按照您的指示操作,则除了您想要的目录树之外,您还将获得该文件的副本。移除“git sparse-checkout init --cone”,但按照您的所有其他指示操作,您将仅获取所需的目录树。我不太确定在什么情况下您会想使用--cone! - Mike Moreton
2
至少对于git 2.33和github.com,filter=tree:0也会防止下载blob(除了HEAD顶级目录中的文件)。因此,您不需要将其与blob:none组合使用。 - Socowi
显示剩余27条评论

770

编辑:截至Git 2.19,这是可能的,可参见此答案

请考虑为该答案点赞。

注意:在Git 2.19中,仅实现了客户端支持,服务器端支持仍然缺失,因此仅在克隆本地存储库时才能使用。另请注意,大型Git主机商,例如GitHub,实际上并未使用Git服务器,他们使用自己的实现,因此即使支持出现在Git服务器上,也不自动意味着它可以在Git主机商上工作。(另一方面,由于他们没有使用Git服务器,他们可以在Git服务器出现之前更快地在自己的实现中实现它。)


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

在Git中实现类似这样的功能需要大量的工作,并且意味着客户端存储库的完整性不能再得到保证。如果您感兴趣,请搜索有关git邮件列表中“稀疏克隆”和“稀疏获取”的讨论。

一般而言,Git社区的共识是,如果您有几个始终独立检出的目录,则这些目录实际上是两个不同的项目,应该存在于两个不同的存储库中。您可以使用Git子模块将它们粘合在一起。


6
根据情况,您可能想使用git subtree而不是git submodule。请参见http://alumnit.ca/~apenwarr/log/?m=200904#30。 - C Pirate
10
稀疏检出是在git-read-tree期间发生的,这是在get-fetch之后很久才发生的。问题不是关于仅检出子目录,而是关于仅克隆子目录。我不明白如何通过稀疏检出实现这一点,因为git-read-tree是在克隆完成后运行的。 - Jörg W Mittag
15
您想让我删除这个“存根”,以便Chronial的答案能够浮现到顶部,而不是保留这个“存根”吗?您无法自己删除它,因为它已被接受,但是管理员可以删除。由于这个回答非常古老,您将保留从中获得的声望。(我发现这是因为有人将其标记为“仅链接”。 :-) ) - Cody Gray
1
@CodyGray:Chronial的答案仍然克隆整个存储库,而不是只克隆子目录。(最后一段甚至明确说明了这一点。)在Git中,只克隆子目录是不可能的。网络协议不支持它,存储格式也不支持它。对于这个问题的每一个答案都总是克隆整个存储库。这个问题是一个简单的是/否问题,答案是两个字符:不。如果说有的话,我的回答是不必要地,而不是短的。 - Jörg W Mittag
2
@JörgWMittag:Ciro Santili的回答似乎与您的观点相矛盾。 - Dan Dascalescu
显示剩余7条评论

448

你可以结合使用稀疏检出浅克隆功能。 浅克隆会截断历史记录,而稀疏检出则只拉取与您模式匹配的文件。

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是不可能的。


28
这个回答很实用,可能是最佳答案,但它仍然“克隆”了你不关心的内容(如果它在你拉取的分支上),即使它在检出时不会显示。 - Brent Bradburn
1
你的git版本是什么?根据git帮助文档,深度选项是否可用? - udondan
2
当最后一个命令不是 git pull --depth=1 origin master 而是 git pull --depth=1 origin <any-other-branch> 时,对我来说无效。这太奇怪了,请参见我的问题:https://dev59.com/H5Tfa4cB1Zd3GeqPVcfR - Shuman
5
在Windows系统中,倒数第二行需要省略引号,否则会导致拉取失败。 - nateirvin
4
这仍然会下载所有数据!我找到了一个解决方案,使用 svn:https://dev59.com/questions/Tmw05IYBdhLWcg3wqzis#18324458 - electronix384128
显示剩余12条评论

202

对于其他只想从GitHub下载文件/文件夹的用户,只需使用:

svn export <repo>/trunk/<folder>
需要翻译的内容已经包含在了 "

" 和 "

" 标签之间,因此只需将这两个标签保留,并将其余部分翻译为中文即可,即 "例如。"
svn export https://github.com/lodash/lodash.com/trunk/docs
是的,这里用的是svn。显然在2016年,你仍然需要使用svn才能简单地下载一些Github文件。 来源:从GitHub repo下载单个文件夹或目录

重要提示 - 确保您更新Github网址并将/tree/master/替换为“/trunk/”。 作为bash脚本:
git-download(){
    folder=${@/tree\/master/trunk}
    folder=${folder/blob\/master/trunk}
    svn export $folder
}

注意:此方法下载的是文件夹,而不是克隆/检出它。您无法将更改推送回存储库。另一方面,与稀疏检出或浅层检出相比,这会导致更小的下载量。


14
这是我在Github上使用的唯一有效版本。Git命令检出了10k多个文件,而SVN仅导出了我想要的700个。谢谢! - Christopher Lörken
4
尝试使用https://github.com/tensorflow/tensorflow/tree/master/tensorflow/examples/trunk/udacity,但是出现了svn: E170000: URL 'https://github.com/tensorflow/tensorflow/tree/master/tensorflow/examples/trunk/udacity' doesn't exist的错误 :( 尝试使用此链接,但出现了错误提示,该链接不存在。 - zthomas.nc
12
您需要删除“trunk”前缀,将udacity中的/tree/master/替换为/trunk/。 - Speedy
4
这个命令对我很有用!我只是想从代码库中复制一个文件,以便在本地进行修改。幸好有老牌的SVN帮忙了! - Michael J
3
它能运行,但似乎有点慢。需要一点时间来启动,然后文件的滚动速度相对较慢。 - Aryeh Beitz
显示剩余8条评论

109

2022年的答案

我不确定为什么有这么多复杂的答案来回答这个问题。通过对存储库进行稀疏克隆,可以轻松地将其克隆到所需的文件夹中。

  1. 导航到您想要克隆子目录的文件夹。
  2. 打开cmd并运行以下命令。
  3. git clone --filter=blob:none --sparse %your-git-repo-url%
  4. cd %the repository directory%
  5. git sparse-checkout add %subdirectory-to-be-cloned%
  6. cd %your-subdirectory%

哇!现在您只克隆了您想要的子目录!

解释 - 这些命令实际上是做什么的?

git clone --filter=blob:none --sparse %your-git-repo-url%

在上述命令中,

  • --filter=blob:none => 你告诉 git 你只想克隆元数据文件。这样,git 会收集远程的基本分支详细信息和其他元数据,以确保以后从源代码库检出的时候更加顺利。
  • --sparse => 告诉 git 这是一个稀疏克隆。在这种情况下,git 只会检出根目录。

现在 git 已经获得了元数据并准备好检出任何你想要处理的子目录/文件。

git sparse-checkout add gui-workspace ==> Checkout folder

git sparse-checkout add gui-workspace/assets/logo.png ==> Checkout a file
通过稀疏克隆,特别是当存在一个包含多个子目录的大型仓库,并且您不总是在所有子目录上工作时,非常有用。在对大型仓库进行稀疏克隆时,可以节省大量时间和带宽。 此外,现在在这个部分克隆的仓库中,您可以继续像往常一样检出和工作。所有这些命令都能正常工作。
git switch -c  %new-branch-name% origin/%parent-branch-name% (or) git checkout -b %new-branch-name% origin/%parent-branch-name% 
git commit -m "Initial changes in sparse clone branch"
git push origin %new-branch-name%

4
好的更新。你能否用更现代的 git switch 命令替换 旧的、混乱的和过时的 git checkout 命令?git switch -c %new-branch-name% origin/%parent-branch-name% - VonC
好的,已经添加了。@VonC - Evan MJ
7
如果我从$CLONEDIR克隆,克隆将创建另一个带有.git目录的目录。我们称之为REPO_DIRNAME。在执行git sparse-checkout ...之前,我必须先cd $CLONEDIR/$REPO_DIRNAME。你能修改一下吗?谢谢 :-) - Paddy3118
我更新了你选择添加的“这是一个较旧的答案”标题,并减少了它的格式。我不确定你为什么这样做,但是随着2022年更近期的答案出现,似乎需要进行更新。但也许你想彻底删除它。 - Yunnosch
@Yunnosch 我从未添加过“这是一个较旧的答案”标题,我不确定你在评论中指的是什么。 - Evan MJ
显示剩余2条评论

81

如果你从未计划与克隆源互动,你可以使用完整的git clone命令并使用以下方式重写你的存储库

git filter-branch --subdirectory-filter <subdirectory>

这样做,至少可以保留历史记录。


13
对于不了解该命令的人,它是 git filter-branch --subdirectory-filter <subdirectory> - Jaime Hablutzel
10
这种方法的优点是您选择的子目录将成为新存储库的根目录,这恰好是我想要的。 - Andrew Schulman
这绝对是使用的最佳和最简单的方法。以下是一个一步命令,使用子目录过滤器 git clone https://github.com/your/repo_xx.git && cd repo_xx && git filter-branch --subdirectory-filter repo_xx_subdir - Alex
3
如果你的代码仓库达到数十GB,这种做法帮助不大。 - Adrian Maire

69

这个看起来简单得多:

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

19
在 GitHub 上执行此操作时,出现了“致命错误:协议不支持该操作。”和“命令流意外终止”的错误信息。 - Michael Fox
2
如果您正在使用Github,可以使用svn export代替。 - Milo Wielondek
3
无法在Github上工作 --> 无效命令:'git-upload-archive 'xxx/yyy.git'' 看起来您正在使用ssh克隆git:// URL。 请确保未设置core.gitProxy配置选项和GIT_PROXY_COMMAND环境变量。 致命错误:远程端意外中止。 - Nianliang
1
这个实际上是克隆仓库的一部分(具有git元数据),还是只下载/提取子目录树? - LarsH
4
这无法在GitHub上运行的原因是:“我们不支持使用git-archive直接从GitHub拉取存档。您可以先将存储库克隆到本地,然后运行git-archive,或者单击存储库页面上的“下载ZIP”按钮。” https://github.com/xuwupeng2000/capistrano-scm-gitcopy/issues/16 - Donn Lee
显示剩余2条评论


41
无法仅使用Git克隆子目录,但以下是一些解决方法。

筛选分支

您可能希望重写存储库,使其看起来像trunk/public_html/是其项目根目录,并丢弃所有其他历史记录(使用filter-branch),在已经检出的分支上尝试:

git filter-branch --subdirectory-filter trunk/public_html -- --all
注意:使用--将过滤选项与修订选项分开,以及--all重写所有分支和标签。所有信息,包括原始提交时间或合并信息,都将被保留。此命令遵守.git/info/grafts文件和refs/replace/命名空间中的引用,因此如果您定义了任何嫁接或替换refs,运行此命令将使它们永久化。 警告!重写的历史记录将对所有对象具有不同的对象名称,并且不会与原始分支收敛。您将无法轻松地将重写的分支推送和分发到原始分支上。如果您不知道完整含义,请不要使用此命令,并且如果一个简单的单个提交就足以解决您的问题,请避免使用它。

稀疏检出

这里是使用稀疏检出方法的简单步骤,它会在工作目录中稀疏地填充文件,因此您可以告诉Git哪个文件夹或文件值得检出。

  1. Clone repository as usual (--no-checkout is optional):

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

    You may skip this step, if you've your repository already cloned.

    Hint: For large repos, consider shallow clone (--depth 1) to checkout only latest revision or/and --single-branch only.

  2. Enable sparseCheckout option:

    git config core.sparseCheckout true
    
  3. Specify folder(s) for sparse checkout (without space at the end):

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

    or edit .git/info/sparse-checkout.

  4. Checkout the branch (e.g. master):

    git checkout master
    
现在您应该已经在当前目录中选择了文件夹。 如果您有太多层次的目录或需要过滤分支,则可以考虑使用符号链接。

过滤分支是否仍允许您执行“pull”操作? - sam
2
@sam:不行。filter-branch会重写父提交,因此它们将具有不同的SHA1 ID,因此您筛选后的树与远程树没有共同的提交。git pull不知道从哪里尝试合并。 - Peter Cordes
这种方法对我的情况大多数情况下是令人满意的答案。 - Abbas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,