加速几十倍 git clone 速度的 --depth 1,它的后遗症怎么解决?

lxf2023-03-19 19:50:01

我们经常会用 git clone 来下载项目,但遇到大项目的时候,clone 就很慢,比如 react:

加速几十倍 git clone 速度的 --depth 1,它的后遗症怎么解决?

要等很久。

当然,还有更慢的项目。

这类项目可以通过 --depth 1 来加速:

git clone --depth 1 https://github.com/facebook/react

加速几十倍 git clone 速度的 --depth 1,它的后遗症怎么解决?

这速度快了有几十倍吧!越大的项目加速效果越明显。

原因就是下载的内容更少了。

加速几十倍 git clone 速度的 --depth 1,它的后遗症怎么解决?

加速几十倍 git clone 速度的 --depth 1,它的后遗症怎么解决?

那这样代码还是全的么?

当然,代码是最新的完整代码。

那为啥下载的内容少了呢?少了哪一部分呢?

很容易想到,就是历史 commit。

这里要涉及一点 git 的实现原理了:

git 中文件是通过 object 存储不同数据的:

  • blob 对象存储文件内容
  • tree 对象存储文件路径
  • commit 对象存储 commit 信息,关联多个 tree

加速几十倍 git clone 速度的 --depth 1,它的后遗症怎么解决?

然后 HEAD、branch、tag 等是指向具体 commit 的指针,可以在 .git/refs 下看到

所以说,每个版本的代码都是从 commit 对象作为入口关联起来的。

指定了 depth 1 的时候,就是只保留了最新的入口,历史入口就没下载了。

加速几十倍 git clone 速度的 --depth 1,它的后遗症怎么解决?

这样自然快很多,代码也是完整的。

但这么好的事情也是有代价的,它有一些后遗症。

最容易想到的就是切不到历史 commit。

正常下载的项目的 git log 是这样的:

加速几十倍 git clone 速度的 --depth 1,它的后遗症怎么解决?

你可以 git reset 切到任意 commit:

比如:

git reset --hard 4dda96a40

加速几十倍 git clone 速度的 --depth 1,它的后遗症怎么解决?

但是 depth 1 下载的项目就不可以,因为本地没有这个 commit 可以切:

加速几十倍 git clone 速度的 --depth 1,它的后遗症怎么解决?

加速几十倍 git clone 速度的 --depth 1,它的后遗症怎么解决?

你再 git pull 的时候,也下载不了历史 commit 的代码:

加速几十倍 git clone 速度的 --depth 1,它的后遗症怎么解决?

就很尴尬。

git 团队自然也想到了这点,于是提供了一个 unshallow 的选项:

加速几十倍 git clone 速度的 --depth 1,它的后遗症怎么解决?

加上 --unshallow 再 pull 的时候也会同时拉取历史 commit。(默认没开这个是为了性能

加速几十倍 git clone 速度的 --depth 1,它的后遗症怎么解决?

你完全可以用 depth 1 下载的项目来开发,正常的 pull、push 都没问题,因为都是基于最新 commit 创建的更新的 commit。

当你有一天需要历史 commit 的时候再 pull --unshallow 也不迟。

这样下载项目快,后面也能恢复成完整版代码库,何乐而不为呢?

但 depth 1 还有一个问题,就是切换不了其他 branch。

正常项目是这样的:

git branch -r 可以查看远程分支:

加速几十倍 git clone 速度的 --depth 1,它的后遗症怎么解决?

git branch -a 可以查看本地和远程的分支:

加速几十倍 git clone 速度的 --depth 1,它的后遗症怎么解决?

但你 depth 1 下载的项目是没有的:

加速几十倍 git clone 速度的 --depth 1,它的后遗症怎么解决?

只有一个 main。

有的同学说,fetch 一下就好了呀。

太天真了。

git fetch 的作用是把远程分支的新 commit 下载到本地。

默认下载所有远程分支的新 commit。

也可以单独指定某个分支:

加速几十倍 git clone 速度的 --depth 1,它的后遗症怎么解决?

但你会发现 git fetch 了这个分支的代码,也不能看到和切换到它:

加速几十倍 git clone 速度的 --depth 1,它的后遗症怎么解决?

这是因为有个 remote.origin.fetch 的配置

正常下载的项目的 fetch 配置是这样的:

加速几十倍 git clone 速度的 --depth 1,它的后遗症怎么解决?

把 remote 的所有分支下载到本地的所有分支。

而 depth 1 下载的项目的 fetch 配置是这样的:

加速几十倍 git clone 速度的 --depth 1,它的后遗症怎么解决?

fetch 只会下载 main 分支。

就算你手动 fetch 了其他分支的代码也不会处理。

所以我们可以改下这个配置,我们先指定一个 0.3-stable 分支看看:

git config remote.origin.fetch "+refs/heads/0.3-stable:refs/remotes/origin/0.3-stable"

加速几十倍 git clone 速度的 --depth 1,它的后遗症怎么解决?

可以看到 pull 的时候就拉取到新分支了,而且 branch -r 可以看到这个分支了。

这里 pull 或者 fetch 都行。pull 就相当于 git fetch + git merge,把代码下载下来,然后 merge 到本地。fetch 是 pull 的第一步:

加速几十倍 git clone 速度的 --depth 1,它的后遗症怎么解决?

接下来再改为 * 试试:

git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"

再执行 git fetch 或者 git pull,就会拉取全部分支的 commit:

加速几十倍 git clone 速度的 --depth 1,它的后遗症怎么解决?

这时候就可以切换到这些分支了:

加速几十倍 git clone 速度的 --depth 1,它的后遗症怎么解决?

加速几十倍 git clone 速度的 --depth 1,它的后遗症怎么解决?

这样就解决了 --depth 1 的第二个问题。

总结

当 git clone 下载大项目的时候,加个 --depth 1 可以提速几十倍。

下载下来的项目也可以正常的 pull 和 push。

这是因为 git 是通过 commit、tree、blob 的对象存储的,每个 commit 是关联这些对象的入口。

depth 1 只会下载最后一个 commit 关联的 object,下载内容更少,所以速度快很多。

但这种方式有两个问题:

  • 切换不到历史 commit
  • 切换不到别的分支

没有历史 commit 可以通过 git pull --unshallow 解决。

切不到别的分支是因为 fetch 配置导致的,配置成 +refs/heads/*:refs/remotes/origin/* 也就可以了,也就是拉取远程所有分支代码到本地。

这样再 fetch 和 pull 就会拉取所有分支的新 commit,也可以正常的切分支。

--depth 1 在下载大项目的时候,或者 build 时下载代码的时候,都很有意义。它提高下载速度导致的俩后遗症也都可以解决。