为什么要用
git clone 默认会把整个历史的所有对象都拉下来。有些场景只需要最新的一份代码,甚至只需要其中一个文件,例如 CI 里构建镜像、流水线里改一行 GitOps manifest 的镜像 tag。这时完整克隆既慢又浪费,网络差的时候还可能超时。
Git 提供了三种互相独立、可以叠加的裁剪方式:
| 方式 | 参数 | 裁掉什么 |
|---|---|---|
| 浅克隆 shallow clone | --depth N |
历史:只要最近 N 个 commit |
| 部分克隆 partial clone | --filter=blob:none 等 |
文件内容:blob 用到时再按需下载 |
| 稀疏检出 sparse checkout | --sparse + git sparse-checkout |
工作区:只检出指定路径 |
浅克隆 –depth
# only the latest commit
git clone --depth 1 https://github.com/foo/bar.git
# --depth implies --single-branch; name the branch explicitly
git clone --depth 1 --branch main https://github.com/foo/bar.git
# by time instead of count
git clone --shallow-since=2026-01-01 https://github.com/foo/bar.git
--depth 默认带上 --single-branch,只拉一个分支。需要其它分支时,加 --no-single-branch。
浅克隆之后的常用操作:
# deepen history by 50 more commits
git fetch --deepen=50
# turn it into a full clone
git fetch --unshallow
# check whether the repo is shallow
git rev-parse --is-shallow-repository
--depth 1 只裁掉了历史。如果仓库当前版本本身就有大文件,比如提交进仓库的二进制或数据文件,体积还是下不来。
部分克隆 –filter
部分克隆会保留完整的 commit 和 tree,但是不预先下载文件内容(blob)。checkout、diff 等命令用到哪个 blob,再去服务器取哪个。服务端需要支持,GitHub、GitLab 都支持。
# no blobs up front; fetched on demand
git clone --filter=blob:none https://github.com/foo/bar.git
# skip blobs larger than 1 MB
git clone --filter=blob:limit=1m https://github.com/foo/bar.git
# no trees either (smallest, but many later commands trigger fetches)
git clone --filter=tree:0 https://github.com/foo/bar.git
和浅克隆不同,部分克隆后 git log 能看到完整历史,只是看文件内容时要联网。
稀疏检出 sparse checkout
只把需要的路径检出到工作区。和 --filter=blob:none 一起用时,没检出的文件不会下载内容。
git clone --filter=blob:none --sparse https://github.com/foo/bar.git
cd bar
# cone mode: whole directories
git sparse-checkout set docs src/api
# non-cone mode: exact file patterns
git sparse-checkout set --no-cone /k8s/app/deployment.yaml
# back to a full working tree
git sparse-checkout disable
三者叠加:只改一个文件
CI 里改 GitOps 仓库里某个 manifest 的镜像 tag,只需要一个文件,下面这条命令拉到的东西最少:
git clone --depth 1 --single-branch --branch main \
--filter=blob:none --sparse git@github.com:foo/gitops.git gitops
cd gitops
git sparse-checkout set --no-cone /k8s/app/deployment.yaml
我在一个约 32 MB pack 的配置仓库上实测的 .git 大小(这个仓库当前版本里有几个 10–20 MB 的数据文件):
| 方式 | .git 大小 |
|---|---|
| 完整克隆 | 约 32 MB |
--depth 1 |
25 MB |
--filter=blob:none --no-checkout |
876 KB |
--depth 1 --filter=blob:none --sparse + 一个文件 |
332 KB |
可以看到,只用 --depth 1 效果有限,体积主要来自当前版本的大文件,要靠 --filter 才能省掉。
在浅克隆里提交和推送
在浅克隆里 commit 和 push 都可以正常用,push 不需要完整历史。
有个坑:push 被拒(! [rejected] ... (fetch first))后,不能像平时那样 fetch 再 rebase。
git fetch --depth 1 origin main
git rebase origin/main # conflicts
原因是 --depth 1 拉下来的新远端提交没有父提交,本地也就找不到它和自己分支的共同祖先。rebase 会把本地浅边界上那个旧提交也当成"自己的提交"重放一遍,结果产生冲突。先 git fetch --unshallow 再 rebase 可以解决,但这样又把完整历史拉回来了。
如果改动是幂等的(比如用 sed 把镜像 tag 替换成固定值),更简单的做法是丢掉本地提交,在远端最新版本上重做一遍:
update_and_commit() {
sed -i "s|image:.*myapp.*|image: ${IMAGE}|" k8s/app/deployment.yaml
git add k8s/app/deployment.yaml
git commit -m "Update image to ${IMAGE}" || echo "No changes to commit"
}
update_and_commit
for attempt in 1 2 3 4 5; do
git push origin HEAD:main && break
[ "$attempt" -eq 5 ] && { echo "push failed" >&2; exit 1; }
# rejected by a concurrent push: redo the edit on the new tip
git fetch --depth 1 origin main
git reset --hard FETCH_HEAD
update_and_commit
sleep $((attempt * 2))
done