HOWTO · Git
Git Push --force-with-lease 与 Git Push --force
在本文中,我们将讨论 git push --force-with-lease 和 git push --force 命令之间的区别。
本页内容
执行 rebase 或 amend 后,已发布的提交会发生变化;如果远端分支无法从本地分支快进更新,普通 push 可能会被拒绝。git push --force 和 git push --force-with-lease 都能替换远端分支的顶端提交。两者的区别在于,Git 是否会在更新前检查预期的远端值。若确实要改写历史,优先使用带 lease 的形式;如果 Git 拒绝了 push,再检查并协调远端状态。
git push --force 的工作方式
普通 push 要求分支向前更新,以此保护远端提交。git push --force 会对正在推送的引用取消这项保护:远端分支会被设为你指定的提交,即使这会让一些提交从分支可见历史中消失。如果队友推送了你尚未纳入本地历史的工作,这些提交可能会无法再从该分支访问。
该命令的作用范围由目标决定。例如,git push --force origin feature 会更新 origin 上的 feature 分支;如果没有明确写出分支名称,就不容易看清哪些引用会受影响。只有在你确实打算替换分支历史,并且已与所有使用该分支的人协调好时,才使用 force push。它不会产生合并冲突,也不会替你合并远端提交。
git push --force-with-lease 的工作方式
git push --force-with-lease origin feature 同样允许非快进更新,但会先检查远端分支是否仍指向预期的值。常见情况下,Git 会根据本地的远端跟踪引用来确定这个预期值,例如 origin/feature。如果服务器上的分支值与之不同,Git 会拒绝更新,而不是替换它。
这项检查很有用,但不能证明没有人推送你尚未看到的工作。如果后台 fetch 在你上次检查后更新了 origin/feature,默认 lease 可能会基于这个刚获取的值接受历史改写。明确指定预期对象 ID,可以让比较更有针对性:git push --force-with-lease=refs/heads/feature:<expected-object-id> origin HEAD:refs/heads/feature。应将预期值的获取和检查纳入团队流程;不要在没确认对象 ID 所代表内容的情况下直接复制使用。
改写历史后使用 --force-with-lease
假设共享分支原本是 A---B---C,协作者在 C 之后添加了 D,而你将本地工作 rebase 后得到 A---B---C'。此时,本地的 C' 和远端的 D 分别代表不同的分支顶端。如果你预期的远端状态仍是 C,lease 的作用是避免你悄悄覆盖 D。
先检查分支并获取远端的当前状态,再决定是否要把协作者的提交纳入改写后的历史。你可以将自己的工作 rebase 到刚获取的分支上,也可以与团队协商其他整合方案。解决并检查好历史后,再用 lease 推送预期结果。如果 lease 拒绝了 push,就重新 fetch 并检查;不要立刻改用 --force 重试。
运行下面这个独立示例,可以创建本地分支、将其推送到临时裸仓库、修改分支顶端的提交,再使用 lease 更新远端:
set -e
root="$(mktemp -d)"
trap 'rm -rf "$root"' EXIT
git init --bare "$root/remote.git" >/dev/null 2>&1
git init -b feature "$root/work" >/dev/null 2>&1
cd "$root/work"
git config user.name "Example Developer"
git config user.email "developer@example.com"
printf 'first version\n' > README.md
git add README.md
git commit -m "Add first version" >/dev/null 2>&1
git remote add origin "$root/remote.git"
git push -u origin feature >/dev/null 2>&1
git commit --amend -m "Rewrite first version" >/dev/null 2>&1
git push --force-with-lease origin feature >/dev/null 2>&1
示例中的路径都是临时路径,脚本退出时会删除其测试环境。在真实仓库中替换共享历史之前,请先检查相关提交,并与协作者协调。
安全选择强制推送方式
如果个人分支没有协作者,而且你确实打算替换远端分支顶端,直接 force push 可能是可接受的。对于共享分支,应先检查预期的远端状态并协调好历史改写,再选择 --force-with-lease。如果可以通过 pull request 或其他审查流程避免改写共享分支,优先考虑这些做法。
明确指定远端和分支,做决定前先 fetch,并在推送前检查提交图。lease 拒绝更新是一个安全信号:将本地分支与刚获取的远端分支进行比较,保留需要的提交,并在确认它们应如何纳入历史后再重试。定期同步可以减少意外,但不能取代对即将发布的提交历史的检查。
恢复或预防提交丢失
如果 force push 让某个提交从分支顶端消失,请先停止清理操作,并查找是否还有保留该提交的引用。在本地克隆中,git reflog 可能会显示近期的分支位置;其他协作者的克隆、远端备份或代码托管服务的恢复功能也可能保留着该提交。如果找到它,先在该提交处创建一个分支,再决定如何恢复或整合。reflog 条目会过期,而且仅保存在本地,因此无法保证一定能恢复。
为避免丢失提交,应事先约定谁可以改写共享分支,在更新前通知协作者,并在需要审查改写时保留一个已知引用。lease 可以减少一类意外覆盖;明确协调并确认有可用的恢复点,则能应对它无法检测的风险。
常见问题
git push --force 和 git push --force-with-lease 有什么区别? 前者会在不检查远端当前值的情况下替换远端引用。后者会检查预期值;如果远端与之不符,就会拒绝更新。
什么时候可以使用 git push --force? 只有在你确实打算替换目标分支的顶端、清楚哪些提交可能会从该分支消失,并且已与受影响的协作者协调好时,才使用它。对于共享工作,优先使用 lease,并检查其预期状态。
在共享分支上强制推送有危险吗? 这可能会让提交从分支历史中消失,并影响协作者的工作。如果远端与预期值不同,lease 可以帮助避免覆盖;但如果默认预期值被你没有检查过的 fetch 更新,它也可能无法发现问题。
如何减少冲突或意外? 先 fetch 并检查远端,在适当情况下将协作者的提交整合到改写后的历史中,提前沟通计划,并对共享变更使用审查流程。
强制推送后可以撤销吗? 没有自动撤销的方法。你可能可以通过 reflog、其他克隆或代码托管服务的恢复功能找到先前的提交,然后创建一个引用并协调恢复。能否恢复取决于该提交是否仍有引用保留。