跳到主要内容
  1. Blog/

如何部分克隆或者检出 github 上的源代码

··字数 3418·7 分钟
git howto

现在的开发人员,几乎都用 git 来作源代码的版本管理。有可能你就每天不断的使用的 git 下载别人的开源代码。不过,是否是有碰过这样的问题,某个开源库代码很大,只是想要下载或者复制其中某个目录到本地电脑,有办法避免下载整个代码库吗?

🤔 比如,过滤掉 git 仓库中可能非常大的 tests 测试目录(这个目录对你学习开源目标有帮助)?

🤔 比如,过滤掉 git 仓库中的 docswebsite 目录(这个目录是项目用于构建官网的代码,往往包含了图片这类大文件,并且对你的学习可能暂时无用)?

有悟总结了几个方法。git 这个工具本身也在不断发展,开发者在命令敲打是客户端,最终需要代码所在服务器之间共同合同,而提供 git 源代码服务的公开资源有多个,还可以自行搭建本地化版本。所以,本文中所提的方法,可能无法覆盖所有的版本,请知晓。有悟尽可能地到能接触的环境上测试。

就开篇所问问题,直入正题:

方法1 - 整库克隆 #

如果非 git 玩家,平常只是拿它当 svn 简单使用,上传和下载代码的,几乎第一想法就是把代码库下载,然后删除其它不需要的目录。没错,当代码库很小的时候,这么做是最直接最快的。

  • 优点:命令简单,就是日常用得非常多的 git clone
  • 缺点:对于大代码库需要考虑本地到服务端之间的网络情况,有可能所需要的只是整个库中的非常少一部分。

当网络状况很差,代码库比较大,可能连 .git 这个目录下的 git 历史信息都无法完整下载。到此停止下载也是时有的事。关键在线的 git 服务还不告诉你这个库会多大。

有没有不需要 git clone 整库的方法?

方法2 - 下载发行版 #

可以直接下载发行版的 release 中的 source code.zip,或者然后到本地解压后再删除不需要的目录。

  • 优点:不包含历史版本记录。对于体积较小的代码库来说比较直接。虽然有可能不是最新,但却是作者发布的 release 版本,质量高。
  • 缺点:对于大型开源软件来说,需要结合本地到服务端之间的网络情况,有可能这个压缩包要多次或者很长时间才能下载回来。平台不提前告诉这个源码包有多大,不能提前预估 ETA。

方法3 - 借助 svn #

从学习角度,本方法了解即可,你可能永远都用不着。

并且,使用起来,不是想象的那样简单易用,有非常明显的限制性。

以前很长一段时间,svn 是源代码版本管理之王。不管开源还闭源,大量的项目都基于 svn。

那个曾经访问量非常大的软件与源码分享网站 sourceforge.net 就是基于 svn。再一次感叹,在技术界,如果没有紧跟步伐,分分钟钟就会掉队甚至被淘汰。

github 是全球最大的男性交友网站,用户量大到 google 把自己的 google code 关闭掉。

几年前出现了一波 svn 往 git 迁移的浪潮,所以在 git 与 svn 之间并没有真正的鸿沟,只是不同时代背景下的产物。两者之间有一些可能联系的桥梁。是的,是代码之间相互传输。如果是商业闭源的,这种情况肯定就不会出现了。

虽然使用 svn -> git 的比 git -> svn 的多,但是 git -> svn 还是被 svn 的命令行支持了。

有悟在网上发现有说 svn checkout 或者 svn export 可以下载目录,github 官方也有文档说明 Support for Subversion clients ,结果拿 reactjs 的 github 地址测了下,用来下载整个库是可以的,但下载目录就没网上说的那么神,应该是服务商调整了策略,有网友留言说以前的方法现在不灵。所以这个成功率不高,还报莫名其妙的错误。

按照网友提供的方法,有悟试了 reactjs 的源码库 『github.com/facebook/react』 中的 packages/react/packages/react/src。 把 github.com/facebook/react/blob/main/packages/react 中的 blob/main 改为 trunk,有可能你看到的是 tree/main,同样,改为这个 仓库路径/trunk/目录格式。

~/Projects/go/svncheckout
➜  svn export https://github.com/facebook/react/trunk/packages/react
svn: E170000: URL “https://github.com/facebook/react/trunk/packages/react” 不存在

~/Projects/go/svncheckout
➜  svn export https://github.com/facebook/react/trunk/packages/react/src
svn: E170013: Unable to connect to a repository at URL 'https://github.com/facebook/react/trunk/packages/react/src'
svn: E120108: 执行上下文错误: The server unexpectedly closed the connection.

~/Projects/go/svncheckout
➜  svn export https://github.com/facebook/react/trunk/packages/react/src
A    src
A    src/BadMapPolyfill.js
A    src/React.js
A    src/ReactAct.js
A    src/ReactBaseClasses.js
A    src/ReactChildren.js

...

A    src/forks
A    src/forks/ReactCurrentDispatcher.www.js
A    src/forks/ReactCurrentOwner.www.js
A    src/forks/ReactSharedInternals.umd.js
A    src/jsx
A    src/jsx/ReactJSX.js
A    src/jsx/ReactJSXElement.js
A    src/jsx/ReactJSXElementValidator.js
已导出版本 39015。

❓为什么

packages/react 提示不存在,而 packages/react/src 反而可以导出。而且 src 目录执行两次才成功,看来网络不稳定对成功率的影响还是比较明显。

以上的方法,无须去 gitee.com 上测试,不支持的。

方法4 - git 的原生支持 sparse-checkout #

本方法可能是最科学的,因为是来自 git 本身的功能支持。

把这个最原生的命令支持放在后面,是因为它并不一开始就出现在官方的功能特性设计中。应该是因应后来场景需求的出现而生,在一部分人日常使用。如果不是 git迷,没有天天关注它发布新版本并阅读版本说明的,很难知道它的存在。所以你也不用惊讶,在通过谷歌搜索查找这文章问题的解决方法时,也不是一下子在前5的搜索结果中就会直接看到本方法。

大概在 git 2.25 以后,git 出现了一个 git sparsecheckout 的子命令,用来部分检出源代码,这在大型软件的开源协作间就非常有效,贡献者只需检出自己参与的那一部分代码,减少与别人产生代码冲突的产生。

同样,使用上节中的例子,从『github.com/facebook/react』 中下载目录 packages/react/packages/react/src

~/Projects/go/gitsparse
➜  git clone --filter=blob:none --sparse https://github.com/facebook/react
 这里会尽量少的下载仓库内容
...
~/Projects/go/gitsparse/react git:(main)
➜  git sparse-checkout init --cone
设置 sparse-checkout 

~/Projects/go/gitsparse/react git:(main)
➜  git sparse-checkout add packages/react
添加要检出的目录 packages/react,git 会立即的执行目录同步
....

~/Projects/go/gitsparse/react git:(main)
➜  git sparse-checkout add packages/react-dom
remote: Enumerating objects: 229, done.
remote: Counting objects: 100% (203/203), done.
remote: Compressing objects: 100% (194/194), done.
remote: Total 229 (delta 26), reused 9 (delta 9), pack-reused 26
接收对象中: 100% (229/229), 455.48 KiB | 223.00 KiB/s, 完成.
处理 delta 中: 100% (28/28), 完成.
正在更新文件: 100% (229/229), 完成.

有悟在测试时,网络不稳定,git sparse-checkout add 会卡住并且超时后会报错。 使用 sparse-checkout add 所添加的目录,记录在 .git/info/sparse-checkout 文本文件中,也可以手工批量增加。

关于 sparse-checkout 的更多说明,请看官方文档 Git - git-sparse-checkout Documentation .

下面是一段可用的 shell 函数,在有悟的 zsh 环境可以执行,各位可根据自己需要调整。

function gitsparse() {
    # 需要 三 个参数:
    # 第 1 个参数是 `git 仓库地址`
    # 第 2 个参数是 `仓库在当前目录下的名称`,
    # 第 3 个参数,需要跟踪或下载的代码目录,这个目录是相对路径

    [[ "$#" != 3 ]] && {
        echo "usage: gitsparse <repo url> <local folder name> <src path>"
        return
    }
    # init repo, only the ".git" folder
    # $1 repo url, $2 folder name
    # 步骤,先创建一个 git 本地仓库,然后初始化 sparse checkout,接着添加下载的代码目录。
    # `--depth=1` 只下载最后一次的历史
    git clone --filter=blob:none --depth=1 --sparse $1 $2 && {
        cd $2

        # init sparse checkout
        git sparse-checkout init --cone

        # add tracker path
        git sparse-checkout add $3

        cd ..
    }
}

说明,上面在创建本地仓库时,使用 --filter=blob:none 过滤掉所有内容,而 --depth=1 是让 .git 目录尽量的小,如果不添加 --depth=1,会下载整个仓库的历史,各位根据自己需要调整。

也可以在 clone 时,不检出,设置好目录了再检出,如:

# 加上 --no-checkout
git clone --filter=blob:none --sparse git仓库地址 本地目录名称 --no-checkout
cd 本地目录名称
git sparse-checkout init --cone
git sparse-checkout add 想要检出的目录
# 最后再 git checkout
git checkout

本节介绍了 git sparse-checkout 的一般用法,将要检出的部分目录添加到 .git/info/sparse-checkout 文件中,它默认启用 cone 模式。 cone (圆锥体、锥形体) 模式,它所描述的是 .git/info/sparse-checkout 文件中所描述目录,正好类似于一个三角形,如同锥体。

/*
!/*/
/A/
!/A/*/
/A/B/
!/A/B/*/
/A/B/C/

上面模式匹配的文件:

  1. / 目录下的文件
  2. /A/B/C 其下的文件与目录

这个配置可以使用命令 git sparse-checkout set /A/B/C 来完成。关于 cone 模式,可能不好理解,请多阅读几遍官方文档 Git - git-sparse-checkout Documentation

方法5 - 网络在线转载 #

通过一些开发者公开的在线功能网页,来下载特定目录。

这些网站是利用了 github 提供的 api 来实现的,当然你也可以自己去实现一个。使用方法比较简单,在页面上输入对应的路径,网站就会自动打包下载。 比如上面的 react 目录,输入的路径为 https://github.com/facebook/react/tree/main/packages

示例网站:

  • download-directory.github.io
  • downgit.github.io

方法6 - sparse-checkout exclude 排除某个目录 #

方法4 介绍了 git clone --sparse 克隆一个远程 git 仓库。如果仓库目录文件非常多,除了罗列出来并添加到 .git/info/sparse-checkout 配置文件中,还可以 exclude 排除 某个目录。

比如仓库R下的目录A非常大,并且你并不需要其中的内容,那么可以这样做。

# 加上 --no-checkout
git clone --filter=blob:none --sparse R的地址 本地目录名称 --no-checkout
cd 本地目录名称
git sparse-checkout init --cone
vi .git/info/sparse-checkout

git sparse-checkout init 后, 此时 .git/info/sparse-checkout 文件生成,修改其中的内容如下:

/*
# !/*/
!/A/

去掉原有 !/*/,加上 !/要排除的目录/,本例为 !/A/,然后执行:

git checkout
warning: unrecognized negative pattern: '/A'
warning: disabling cone pattern matching

无须理会 warning:unrecognized negative pattern,它只是提示 disable cone pattern matching ,因为排除方式并不构成锥形,git 会自动禁用,即使 core.sparsecheckoutcone 可能为 true

方法7 - 利用 sparse-checkout 隐藏一些目录 #

方法4、6 是介绍部分检出,已经减少下载到本地文件与空间占用。但对于本地本来已经检出(checkout)的复本,我们还可以隐藏某些不关心的目录,如:

# 进入仓库目录
cd local_git_repo
git sparse-checkout init     # core.sparseCheckout = true

# 设置票显示的目录
git sparse-checkout set "/*" "!/*/" /显示的目录/ # .git/info/sparse-checkout

ls
`/` 下的文件, 显示的目录s

# 关闭隐藏
git sparse-checkout disable  # core.sparseCheckout = false

ls
`/` 下的文件, 显示的目录s,其它之前未显示的目录

总结 #

有悟平常关于这种 sparse checkouts (部分检出)的使用比较少,关于这种特定场景下需要同步的源代码信息的测试场景想象并不全面。所有,以上所列方法,可能未能满足你的需要。 当是用来下载、阅读学习一些开源库代码片段或者部分库时,却非常有用。

参考资料:

本文 svn 下载 git 目录的部分参考了 stackoverflow 上的问答 git - Download a single folder or directory from a GitHub repo - Stack Overflow