我们经常会用 git clone 来下载项目,但遇到大项目的时候,clone 就很慢,比如 react:
![a2f1f9a3abc46988d7151d5b491ce2e3.gif](https://i-blog.csdnimg.cn/blog_migrate/422cfcd27a59885f4d69fc114d611504.gif)
要等很久。
当然,还有更慢的项目。
这类项目可以通过 --depth 1 来加速:
git clone --depth 1 https://github.com/facebook/react
![d0e7df19ac2a6b81dcb25e6ec3b5f358.gif](https://i-blog.csdnimg.cn/blog_migrate/499e2bd090b2d15a631be9c6ee59118d.gif)
这速度快了有几十倍吧!越大的项目加速效果越明显。
原因就是下载的内容更少了。
![9a45be7b3e7d7ec0e28b8bde581d100f.png](https://i-blog.csdnimg.cn/blog_migrate/10414b7d1c7b3f64fc30043f1e87d2f4.png)
![dd6fc289711788e6e2ec96ab7a66f92f.png](https://i-blog.csdnimg.cn/blog_migrate/372d961751d435c2480a528f01b5ee9e.png)
那这样代码还是全的么?
当然,代码是最新的完整代码。
那为啥下载的内容少了呢?少了哪一部分呢?
很容易想到,就是历史 commit。
这里要涉及一点 git 的实现原理了:
git 中文件是通过 object 存储不同数据的:
glob 对象存储文件内容
tree 对象存储文件路径
commit 对象存储 commit 信息,关联多个 tree
![b8a6e9a533d9b24444c904c6b8eff877.png](https://i-blog.csdnimg.cn/blog_migrate/6bb7bdbcfeed3f6ed28d6ab97f6abf4c.png)
然后 HEAD、branch、tag 等是指向具体 commit 的指针,可以在 .git/refs 下看到
所以说,每个版本的代码都是从 commit 对象作为入口关联起来的。
指定了 depth 1 的时候,就是只保留了最新的入口,历史入口就没下载了。
![7a0a7355c35399f94f8f2cb56ad34a73.png](https://i-blog.csdnimg.cn/blog_migrate/02b6e23a165d7aececc0a07785adaafb.png)
这样自然快很多,代码也是完整的。
但这么好的事情也是有代价的,它有一些后遗症。
最容易想到的就是切不到历史 commit。
正常下载的项目的 git log 是这样的:
![83aff53d73f916a9c0016f7fdaa22857.png](https://i-blog.csdnimg.cn/blog_migrate/81adbeb33cfb0aa7e07c005332f1fe7a.png)
你可以 git reset 切到任意 commit:
比如:
git reset --hard 4dda96a40
![4d441ddccd41061f8e720bdf08064302.png](https://i-blog.csdnimg.cn/blog_migrate/3a4eea5d4134b4e37966d735f51accc4.png)
但是 depth 1 下载的项目就不可以,因为本地没有这个 commit 可以切:
![75da88e781c36d8da78d6fb0e20b9565.png](https://i-blog.csdnimg.cn/blog_migrate/a4682bdb5cb6215f25a61452a5688459.png)
![12c17dd58d03e067e2ed7ea7e1779509.png](https://i-blog.csdnimg.cn/blog_migrate/ef2a176ee72b07efc1d86ba8ed60a21d.png)
你再 git pull 的时候,也下载不了历史 commit 的代码:
![48cd9406366ff61543657b289513d5ce.png](https://i-blog.csdnimg.cn/blog_migrate/b83890189c9fddc4d953ed914285a088.png)
就很尴尬。
git 团队自然也想到了这点,于是提供了一个 unshallow 的选项:
![d35138c2994001a2ab2e854f5de97456.png](https://i-blog.csdnimg.cn/blog_migrate/1a1d8a01e068c66790783420a9b871c8.png)
加上 --unshallow 再 pull 的时候也会同时拉取历史 commit。(默认没开这个是为了性能)
![1be86bdb9e080be486ba74603c212e23.png](https://i-blog.csdnimg.cn/blog_migrate/75bc29beb97d8a1fd008fad42a0e8616.png)
你完全可以用 depth 1 下载的项目来开发,正常的 pull、push 都没问题,因为都是基于最新 commit 创建的更新的 commit。
当你有一天需要历史 commit 的时候再 pull --unshallow 也不迟。
这样下载项目快,后面也能恢复成完整版代码库,何乐而不为呢?
但 depth 1 还有一个问题,就是切换不了其他 branch。
正常项目是这样的:
git branch -r 可以查看远程分支:
![b972e288379d2d8b6c1ec50675856fe3.png](https://i-blog.csdnimg.cn/blog_migrate/90098678bbf8f024da1abfeab29dd640.png)
git branch -a 可以查看本地和远程的分支:
![5ca66754939a329804a52660d5f805e4.png](https://i-blog.csdnimg.cn/blog_migrate/bd3858c850ab08b4e2ed12026976b2d2.png)
但你 depth 1 下载的项目是没有的:
![ea794dde3872b87549912c318288edc0.png](https://i-blog.csdnimg.cn/blog_migrate/e4f15825ee9b75cd46699814d69ee4bc.png)
只有一个 main。
有的同学说,fetch 一下就好了呀。
太天真了。
git fetch 的作用是把远程分支的新 commit 下载到本地。
默认下载所有远程分支的新 commit。
也可以单独指定某个分支:
![c012a7199932a5245916ccf08e9a8b9d.png](https://i-blog.csdnimg.cn/blog_migrate/7c92ab9a92ab944e0ee0bec19da5a6b4.png)
但你会发现 git fetch 了这个分支的代码,也不能看到和切换到它:
![02125ca1033d13b9dfc206814202a060.png](https://i-blog.csdnimg.cn/blog_migrate/a84dd8c208663c64a727e97c706ae85e.png)
这是因为有个 remote.origin.fetch 的配置。
正常下载的项目的 fetch 配置是这样的:
![88d40137aea72761445d091dba8f1fda.png](https://i-blog.csdnimg.cn/blog_migrate/ad02efe89660ae5346fc3edaadd17270.png)
把 remote 的所有分支下载到本地的所有分支。
而 depth 1 下载的项目的 fetch 配置是这样的:
![467abf3b226fe762e358ec8b3b999305.png](https://i-blog.csdnimg.cn/blog_migrate/4734f5adc5879a3e701566355ea1f8ad.png)
fetch 只会下载 main 分支。
就算你手动 fetch 了其他分支的代码也不会处理。
所以我们可以改下这个配置,我们先指定一个 0.3-stable 分支看看:
git config remote.origin.fetch "+refs/heads/0.3-stable:refs/remotes/origin/0.3-stable"
![7e4e5acae0937e9351ebb71b4ddb59c8.png](https://i-blog.csdnimg.cn/blog_migrate/c3c63e8dc3f11beb7b6e4be78c6d3545.png)
可以看到 pull 的时候就拉取到新分支了,而且 branch -r 可以看到这个分支了。
这里 pull 或者 fetch 都行。pull 就相当于 git fetch + git merge,把代码下载下来,然后 merge 到本地。fetch 是 pull 的第一步:
![61fd9580be9b1c11e61551e8d4d4ef48.png](https://i-blog.csdnimg.cn/blog_migrate/34070f5e18b561cbd6bf058a3eb1cac3.png)
接下来再改为 * 试试:
git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
再执行 git fetch 或者 git pull,就会拉取全部分支的 commit:
![8f8f4f056680aa8ec9211d4767be1c5f.png](https://i-blog.csdnimg.cn/blog_migrate/ac029dc031ff96a582c13d72a8cb3949.png)
这时候就可以切换到这些分支了:
![bd21bed4fe5e9d3c14eda3074b87f38d.png](https://i-blog.csdnimg.cn/blog_migrate/2064ca3f91569d7d2e500c5ca712cabb.png)
![6f490907d5b3b54d0fc7363aef9dd712.png](https://i-blog.csdnimg.cn/blog_migrate/995fe9d95565a515984c87c693c745ec.png)
这样就解决了 --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 时下载代码的时候,都很有意义。它提高下载速度导致的俩后遗症也都可以解决。
- END -
关于奇舞团
奇舞团是 360 集团最大的大前端团队,代表集团参与 W3C 和 ECMA 会员(TC39)工作。奇舞团非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队 Leader 等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。