探索在不完整克隆的情况下使用 Git 操作远程仓库
背景
之前的几周一直在给 AList 做贡献,AList 是一个聚合存储中间件,它支持将本地磁盘、各类网盘、对象存储、FTP、WebDAV 等存储介质集中管理在一个虚拟文件系统中,并以网页端或 WebDAV、S3、FTP 等协议的方式提供访问接口。它非常适合网盘重度用户(尤其是那些因为资料太多而不得不分多个网盘存储的用户)摆脱网盘 APP 进行文件下载,免会员在线播放,跨网盘大文件复制等场景,感兴趣的人可以看看在线文档和项目仓库。
AList 有一个古早 issue 希望可以将 git 仓库挂载在 AList 中,这样就可以白嫖 GitHub 用作图床。尽管按照 git 的设计思想,如是的场景应该将作为图床的仓库完整克隆到本地,再返回被请求的资源(因为 git 本来就不是做网盘而是做版本管理的),但这样的方案无法实现节省本地磁盘空间的目的,完全退化为了多此一举的本地存储。因此要实现这样的功能,必须找到一种办法能够克隆仓库的一部分,并且让 git 只跟踪被克隆部分的修改来实现文件上传功能。为此我对 git 一些稀奇古怪的命令进行了探索,但本人最近有一些课题组那边的工作,所以暂时没有时间完整实现 git 仓库的挂载功能,在此将这部分探索的结果记录在此,供未来的我或有意完成这部分功能的人参考。
在不克隆仓库的情况下通过 git 查看远程仓库的目录结构
clone
指令实际上是如下四个指令的简写:
git init
git remote add origin <repo-url>
git fetch origin
git checkout main
其中 fetch
指令会将远程仓库的完整内容(包括 blob
、commit
、tree
、tag
等几种对象,简单的说就是文件内容和提交记录)下载到本地,因此想要在不完整克隆远程仓库的情况下操作远程仓库,fetch
就是首先要动手脚的一步。
首先“引用”但不真正克隆一个仓库:
mkdir linux
cd linux
git init
git remote add origin https://github.com/torvalds/linux.git
然后使用如下指令,只下载主分支最近一次提交记录和目录结构:
git fetch --depth 1 --filter=blob:none origin master
--depth 1
实现的是只下载最近一次提交,忽视更早以前的提交,节省本地的存储空间,--filter=blob:none
指的是不要下载 blob
对象,也就是文件内容。
在这样的状态下,远程仓库没有被 fetch
的内容会在之后第一次被使用到时下载,比如如果现在执行 checkout
,本地文件中就会出现仓库最新版本的内容,这显然就会用到 blob
对象,于是 git 就会下载 blob
对象,这显然是不符合我们的期望的,因此现在一定不能执行 checkout
。这也是为什么在 clone
命令中使用 --filter=blob:none
,blob
对象依然会被下载。
查看远程仓库的目录结构
ls-tree
命令允许我们查看远程仓库的目录结构,使用
git ls-tree origin/master
会得到如下输出
100644 blob fe1aa1a30d40267b71a2c90b23e16f7cfe473eea .clang-format
100644 blob e4c4eef10b28c1ff0c48792d095e647c4a8bfb28 .clippy.toml
100644 blob 43967c6b20151ee126db08e24758e3c789bcb844 .cocciconfig
...
040000 tree 70208637ececd5e37e1e42dfdec924315da59a53 Documentation
100644 blob 464b34a08f51ef9d2ae12e6902c92690b5dfa03b Kbuild
100644 blob 745bc773f567067a85ce6574fb41ce80833247d9 Kconfig
040000 tree 1b67f4f4f889154b0174bd3e531081e44aa60156 LICENSES
...
这里 blob
类型的对象是文件,tree
类型的对象是文件夹。
使用下面这样的命令继续查看子文件夹
git ls-tree origin/master arch/
ls-tree
命令的 -l
参数可以查看文件的大小,比如执行
git ls-tree -l origin/master
会得到
100644 blob fe1aa1a30d40267b71a2c90b23e16f7cfe473eea 22878 .clang-format
100644 blob e4c4eef10b28c1ff0c48792d095e647c4a8bfb28 335 .clippy.toml
100644 blob 43967c6b20151ee126db08e24758e3c789bcb844 59 .cocciconfig
...
040000 tree 70208637ececd5e37e1e42dfdec924315da59a53 - Documentation
100644 blob 464b34a08f51ef9d2ae12e6902c92690b5dfa03b 2573 Kbuild
100644 blob 745bc773f567067a85ce6574fb41ce80833247d9 555 Kconfig
040000 tree 1b67f4f4f889154b0174bd3e531081e44aa60156 - LICENSES
...
但是要注意:tree
对象不会储存文件的大小,因此 git 首次查看一个文件的大小时,必须先将其 blob
对象下载到本地,再统计文件的长度,故而第一次执行上述指令会有很多这样的输出:
remote: Enumerating objects: 1, done.
remote: Counting objects: 100% (1/1), done.
remote: Total 1 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
Receiving objects: 100% (1/1), 4.95 KiB | 2.48 MiB/s, done.
100644 blob fe1aa1a30d40267b71a2c90b23e16f7cfe473eea 22878 .clang-format
remote: Enumerating objects: 1, done.
remote: Counting objects: 100% (1/1), done.
remote: Total 1 (delta 0), reused 1 (delta 0), pack-reused 0 (from 0)
Receiving objects: 100% (1/1), 285 bytes | 142.00 KiB/s, done.
100644 blob e4c4eef10b28c1ff0c48792d095e647c4a8bfb28 335 .clippy.toml
...
下载 blob
对象是我们极力想要避免的,因此非必要情况下应当避免使用 -l
参数。
查看远程仓库内的文件内容
可以使用 cat-files
查看文件的内容,执行
git cat-file blob 43967c6b20151ee126db08e24758e3c789bcb844
可以得到
[spatch]
options = --timeout 200
options = --use-gitgrep
这里 43967c6b20151ee126db08e24758e3c789bcb844
是文件 .cocciconfig
的哈希。
当然,首次使用 cat-file
查看某文件时,需要先下载对应的 blob
对象。
cat-file
还可以用于查看大小(-s
)、类型(-t
)等
检出仓库的一部分
git 有一个叫“稀疏检出”的特性可以检出仓库的一部分,关于该特性的详细内容在此不过多赘述,只谈该特性在本需求中的应用。
初始化稀疏检出特性
git config --local core.sparseCheckout true
git sparse-checkout init
设置检出哪些文件,这里每项格式和 .gitignore
中的一行是一样的,比如放弃检出除 MAINTAINERS
以外的所有文件
git sparse-checkout set --no-cone '!/*' 'MAINTAINERS'
也通过修改 .git/info/sparse-checkout
设置检出哪些文件。
检出
git checkout master
之后就可以看到仓库里只有 MAINTAINERS
一个文件,但是 git status
的如下输出表面我们确实是在仓库的最新版本的
On branch master
Your branch is up to date with 'origin/master'.
You are in a sparse checkout with 1% of tracked files present.
nothing to commit, working tree clean
做出一些修改并推送,对应到 AList 中的上传、重命名、删除等写入操作,这里为了保证成功推送换了仓库地址。
rm MAINTAINERS
git commit -am 'MAINTAINERS: Remove some entries due to various compliance requirements.'
git remote set-url origin https://github.com/KirCute/linux.git
git push origin master
删除本地多余的对象
由于在以上操作中我们下载下来的 blob
对象都是历史版本中被引用的对象,所以我们不能通过 git gc
删除它们,目前我还没有找到合适的方法删除它们,所以只能选择手动删除 .git/objects/pack
下的文件。
在删除前,最好先放弃检出所有文件
git sparse-checkout set --no-cone '!/*'