为何Git会重新发送远程仓库已存在的对象?
准备仓库
$ git init repo $ cd repo $ git config user.name username $ git config user.email username@mail $ head --bytes=100000000 < /dev/urandom > file
创建远程master分支
$ git add file $ git commit --message=initial\ commit [master (root-commit) 6068e0d] initial commit 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 file $ git rev-parse master 6068e0d0071bc76f31065ddb1ddbad0d46c635b8 $ git cat-file -p master tree f9862a0f570f7a910a01ab1fa743d66407452fdd author username <username@mail> 1719130146 +0300 committer username <username@mail> 1719130146 +0300 initial commit $ git cat-file -p f9862a0f570f7a910a01ab1fa743d66407452fdd 100644 blob bbb3bfdf995b0d2eea02b1fed8688e886da134de file $ git cat-file -s bbb3bfdf995b0d2eea02b1fed8688e886da134de 100000000 $ time git push git@hostname:username/reponame.git master:master Enumerating objects: 3, done. Counting objects: 100% (3/3), done. Delta compression using up to 20 threads Compressing objects: 100% (2/2), done. Writing objects: 100% (3/3), 95.40 MiB | 4.57 MiB/s, done. Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0) To hostname:username/reponame.git * [new branch] master -> master real 0m22.503s user 0m3.233s sys 0m0.483s
准备远程dev分支
$ git checkout -b dev $ git rev-parse dev 6068e0d0071bc76f31065ddb1ddbad0d46c635b8 $ git cat-file -p dev tree f9862a0f570f7a910a01ab1fa743d66407452fdd author username <username@mail> 1719130146 +0300 committer username <username@mail> 1719130146 +0300 initial commit $ time git push git@hostname:username/reponame.git dev:dev Total 0 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0) To hostname:username/reponame.git * [new branch] dev -> dev real 0m1.671s user 0m0.035s sys 0m0.015s
此时完全可以理解为何增量为0,以及推送dev分支耗时极短的原因。
在本地和远程修改初始dev提交
$ git commit --amend --no-edit [dev b15f0a6] initial commit Date: Sun Jun 23 11:09:06 2024 +0300 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 file $ git rev-parse dev b15f0a6c36463e6e8b28b63473c464fb2fbcc326 $ git cat-file -p dev tree f9862a0f570f7a910a01ab1fa743d66407452fdd author username <username@mail> 1719130146 +0300 committer username <username@mail> 1719131177 +0300 initial commit # it still points to the f9862a0f570f7a910a01ab1fa743d66407452fdd tree as expected $ time git push --force git@hostname:username/reponame.git dev:dev Enumerating objects: 3, done. Counting objects: 100% (3/3), done. Delta compression using up to 20 threads Compressing objects: 100% (2/2), done. Writing objects: 100% (3/3), 95.40 MiB | 4.44 MiB/s, done. Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0) To hostname:username/reponame.git + 6068e0d...b15f0a6 dev -> dev (forced update) real 0m23.326s user 0m3.202s sys 0m0.507s
问题解答
虽然远程仓库确实已经拥有该tree和blob对象,但推送耗时久的原因主要有两点:
- Git打包传输的机制限制:
git commit --amend生成的新提交哈希值和原提交完全不同,Git推送时会默认遍历新提交关联的所有对象(提交本身、tree、blob)并生成pack文件。即使远程已有这些tree和blob,本地打包过程依然会把它们包含进去——Git不会主动去远程校验对象是否存在,而是直接传输整个pack包,远程收到后才会自动忽略已存在的对象,但传输的时间和带宽消耗已经产生。 - 强制推送的特殊逻辑:使用
--force推送时,Git的历史校验逻辑会简化,不会像普通推送那样对比远程分支历史来筛选需要传输的对象,而是直接发送新分支顶端关联的所有可达对象,这就导致原本不需要传输的大体积blob被重复打包发送。
内容的提问来源于stack exchange,提问作者terrorrussia-keeps-killing
相关产品推荐
相关产品推荐

