当我想要强制推送时,我几乎总是使用 --force-with-lease
。今天我升级到 Git 2.30 并发现了一个新选项:--force-if-includes
。
在阅读更新文档后,我仍然不完全清楚在哪些情况下我会使用 --force-if-includes
而不是像我通常那样使用 --force-with-lease
。
Git 2.30中新增的--force-if-includes
参数除非同时使用--force-with-lease
,否则不会起作用。如果您使用了--force-with-lease
,那就足够了。我个人认为--force-if-includes
的效用并不明显,因此会使用多步骤的处理方法:
git fetch <remote> # get commit(s) from remote
git show <remote>/<name> # inspect their most recent commit
# to make sure it's what we think
# it is, so that we're sure of what
# we're tossing *from* their Git repo
然后,如果以上一切顺利:
git push --force-with-lease <remote> <name>
--force-if-includes
表示你的Git在进行强制推送时,会检查你之前使用git fetch
至少一次查看过他们最新的提交。这意味着你必须密切关注git fetch
获取的内容(这就是为什么我只会进行获取和检查,然后不再烦恼新标志的原因)。--force
或--force-with-lease
,你绝对不需要新标志。由于大多数git push
操作一开始就不需要--force
,所以你可以跳过所有花哨的东西,只需使用常规(非强制性)推送,而无需进行仔细的检查。
< h1>长篇大论
--force-if-includes
选项是新功能,如你所述,如果以前从未需要过它,现在也不需要。因此,“何时应该使用此选项”的最短答案是“从不”。推荐的答案是(或将成为)“始终”(一旦证明了?)。(我个人还没有完全相信其中任何一种方法。)--force-with-lease
。因此,如果要使用--force-if-includes
,我们已经实际上启用了--force-with-lease
。1在查看--force-with-includes
之前,我们应该了解一下--force-with-lease
的工作原理。我们要解决什么问题?我们的“使用案例”或“用户故事”或任何其他最新的流行语可能是什么?git fetch
相当简单:我们为他们的Git存储库分配一个名称,例如origin
。然后,当我们从他们那里获取一些新的提交时,我们取得它们的名字——找到这些提交中最后一个的名字——并将其重命名。如果他们使用main
来找到该提示提交,我们将其重命名为origin/main
。我们创建或更新自己的origin/main
以记住他们的提交,而不会干扰我们自己的main
。main
。例如,我们交出提交b789abc
,然后要求他们将他们的main
设置为b789abc
。为了确保他们不会丢失他们的a123456
提交,他们所做的就是确保a123456
是我们提交b789abc
的历史的一部分。 ... <-a123456 <-b789abc <--main
main
指向b789abc
,而b789abc
将a123456
作为其父项,那么让他们更新他们的main
以指向b789abc
是“安全的”。为了确保这真的是安全的,他们必须原子地替换他们的main
,但我们只让他们自己处理。a123456
。我们发现a123456
存在问题或错误。我们不再进行简单的更正,而是使我们的b789abc
跳过了错误的提交,这样它就可以添加到分支上。... <-something <-a123456 <--main
becomes:
... <-something <-b789abc <--main
\
a123456 ??? [no name, hence abandoned]
--force
来告诉他们强制进行替换,并且如果我们有适当的权限3,则他们的Git会遵从。 这有效地从他们的克隆中删除了错误的提交,就像我们从自己的克隆中删除一样。4
1正如您提供的文档所述,--force-if-includes
如果没有--force-with-lease
就会被忽略。也就是说,--force-if-includes
并不能为您打开 --force-with-lease
,您需要同时指定两者。
2这些是哈希 ID,它们需要在所有可能相遇和共享ID的Git之间保持唯一,但不需要在从未相遇的两个Git之间保持唯一。在这种情况下,我们可以安全地拥有我所谓的“替身”:具有相同哈希ID但不同内容的提交或其他内部对象。尽管如此,最好还是使它们真正独特。
3“开箱即用”的Git并没有此类权限检查,但像GitHub和Bitbucket等托管提供商会添加它,作为他们增值服务中的一部分,以说服我们使用他们的托管系统。
4无法找到的提交实际上并没有立即消失。相反,Git会将其留给稍后的清理操作git gc
。此外,从某个名称删除提交可能仍会使该提交可从其他名称访问,或通过Git为每个名称保留的日志条目访问。如果是这样,该提交将更长时间地存在,甚至可能永远存在。
力推的概念就像它所到达的地方一样好,但这还不够。假设我们有一个托管在某个地方(如GitHub等)接收git push请求的存储库。进一步假设我们不是唯一一个进行推送的人/团队。
我们git push了一些新提交,然后发现它是错误的,并想立即用新的和改进的提交替换它,因此我们需要花费几秒钟或几分钟-无论需要多长时间来制作新的改进提交-并将其放置并运行git push --force。具体而言,让我们假设整个过程需要一分钟或60秒。
在这六十秒内,其他人可能会:
因此,此时我们认为托管系统拥有:
...--F--G--H <-- main
需要替换的提交H
存在问题,需要使用我们新而改进的H'
进行替换。但实际上,他们现在有:
...--F--G--H--I <-- main
这里的提交 I
是来自于另一个更快的提交者。同时,我们现在在我们的代码库中有以下序列:
...--F--G--H' <-- main
\
H ???
其中H
是我们要替换的错误提交。现在我们运行git push --force
,由于我们被允许强制推送,主机提供商Git接受我们的新H'
作为它们的main
中的最后一次提交,这样他们现在有了:
...--F--G--H' <-- main
\
H--I ???
这导致我们的git push --force
不仅删除了我们错误的H
,还删除了他们的(可能仍然好的,或者至少是想要的)I
。
5他们可能通过将已经提交的代码进行变基来实现此目的。在原本基于G
的提交被拒绝后,他们会进行变基操作。他们的变基操作会自动将新的提交复制到我们称之为I
的提交中,并且不会出现合并冲突,使他们能够在比我们修复提交H'
所需时间更短的时间内运行git push
。
--force-with-lease
--force-with-lease
选项在 Git 内部称为“比较并交换”,它允许我们将提交发送到其他的 Git,然后让他们检查他们的分支名称(无论是什么)是否包含我们认为它包含的哈希 ID。
让我们将origin/*
名称添加到我们自己的存储库的图示中。由于我们之前向托管提供商发送了提交 H
,并且他们已经采纳了它,因此我们实际上在我们的存储库中拥有 这个:
...--F--G--H' <-- main
\
H <-- origin/main
git push --force-with-lease
时,我们完全可以控制--force-with-lease
的操作。其完整语法如下:{{syntax}}。git push --force-with-lease=refs/heads/main:<hash-of-H> origin <hash-of-H'>:refs/heads/main
git push --force-with-lease origin main
main
提供了我们想要发送的最后一次提交的哈希ID——H'
,以及我们想要更新的引用名称(基于我们的main
是一个分支名称,为refs/heads/main
)。--force-with-lease
没有=
部分,所以Git填写了剩下的内容:引用名称是我们想要更新的那个——refs/heads/main
,期望的提交是我们相应的远程跟踪名称中的那个,即我们自己的refs/remotes/origin/main
中的那个。origin/main
提供了H
哈希,而我们的main
提供了H'
哈希和所有其他名称。它更短,但却起到了作用。
git gc
收集它们,即使它们从未被合并。
--force-if-includes
上面使用--force-with-lease
的示例用例展示了当我们自己发现一个坏提交时,如何替换它并推送。但人们并不总是这样工作。
假设我们像之前一样提交了一个糟糕的提交。我们在本地仓库中陷入了这种情况:
...--F--G--H' <-- main
\
H <-- origin/main
但现在我们运行git fetch origin
。也许我们试图要有意识地做事,也许我们正在压力下犯错误。无论发生了什么,我们现在会得到:
...--F--G--H' <-- main
\
H--I <-- origin/main
在我们自己的代码库中。
如果我们使用git push --force-with-lease=main:<hash-of-H> origin main
,推送将失败,因为我们明确声明我们期望 origin 的main
包含哈希IDH
。但是,从我们的git fetch
中可以看出,它实际上具有哈希IDI
。如果我们使用更简单的方法:
git push --force-with-lease origin main
--force-if-includes
的实际代码,我需要这样做才能弄清楚如何欺骗它。但这似乎是一个可能性。 - torek--force-with-lease
。但我听说有些人在他们的机器上自动定时运行 fetch。对于他们来说,--force-if-includes
比 --force-with-lease
更好。也许正是他们最初推动了这个功能的出现。;) - TTT-f
应该真正成为更安全选项的缩写,但由于向后兼容性而无法实现)。预推钩子也不能保证原子性:这将取决于另一侧实现了什么。 - torek--force-with-lease
允许您提供所有输入:"我期望 refspec <name> 等于 <hash1>;如果是这样,请使用 <hash2> 强制替换它"。这在功能上等同于机器语言的 "比较并交换" 指令,在 Git 内部被称为 "compare and swap"。 - torek避免意外覆盖其他开发者的提交,我的最终安全解决方案是同时使用两个选项。 git config --global alias.pushf 'push --force-with-lease --force-if-includes'
[alias]
pushf = push --force-with-lease --force-if-includes
--force-with-lease[=<refname>]
同时指定--force-if-includes
(即不指定远程端必须指向哪个确切提交或保护哪些引用),在“push”时将验证是否在允许强制更新之前在本地集成了在后台可能隐式更新的远程跟踪引用的更新。保留HTML标记。git push --force-with-lease=refs/heads/main:<hash-of-H> origin <hash-of-H'>:refs/heads/main
- Devin Rhode如果你总是直接使用 --force-with-lease
而不是 --force
,并且想要一些快速信息,那么可以考虑使用 --force-with-lease --force-with-includes
来代替,因为这样会稍微更安全一些。
如果能抽出更多时间听我说的话,请继续往下看。如果在使用 git push --force-with-lease
之前运行 git fetch
,则本质上你只是进行了无保护的强制推送。添加 --force-if-includes
可以同时利用 reflog 和远程跟踪分支来提供保护,因为执行 fetch 操作似乎相当无害,甚至可能发生在后台。
根据文档(我的评论加粗斜体),加粗是重点强调。
--[no-]force-with-lease --force-with-lease=<refname> --force-with-lease=<refname>:<expect>
Usually, "git push" refuses to update a remote ref that is not an ancestor of the local ref used to overwrite it.
This option overrides this restriction if the current value of the remote ref is the expected value. "git push" fails otherwise.
[...]
Alternatively, specifying
--force-if-includes
as an ancillary option along with--force-with-lease[=<refname>]
(this is what we are doing when we do--force-with-lease
) (i.e., without saying what exact commit the ref on the remote side must be pointing at, or which refs on the remote side are being protected) at the time of "push" will verify if updates from the remote-tracking refs that may have been implicitly updated in the background are integrated locally before allowing a forced update.
这意味着,如果你正在使用 --force-with-lease
强制推送你的 blah
分支到 origin
,但是你的 origin/blah
分支(远程跟踪分支)与服务器上的 blah
分支不同,它会阻止你进行推送。 这意味着有人对代码进行了更改,而你不知道。
还有更好的使用 --force-with-lease
的方法,但说实话,我们只是在寻找一些快速且安全的方法。如果你想学习更多,请查看文档。使用 --force-if-includes
除了检查远程跟踪分支外,还会检查你的引用日志,以确保没有错过的更改。
这实际上是强制推送的最安全方式,也是我个人的建议:
git push --force-with-lease=refs/heads/main:<expected-remote-sha> origin main
--force-if-includes
变得无关紧要,因为你直接告诉git你期望替换的远程sha是什么。我不知道还有什么方法可以使其更加安全。git fetch origin && git log -1 origin/<branch>
。 - Devin Rhode--force-with-lease
明确指定预期哈希值的好处。我认为,获取并查看 origin/<branch>
可能是常规操作,而 --force-with-lease
可以防止查看但最近没有获取到。如果您总是首先查看 origin/<branch>
,那么我认为您不需要明确指定提交 SHA,因为它已经在使用该值了。如果出于某种原因您知道您不会获取,并且您将使用远程仓库 UI 确定提交,则明确提供它可能会有所帮助。 - TTT您可能仅通过cli使用git,但是某些编辑器(如vscode)将自动在后台每2分钟运行git fetch
,并向您显示是否有新的提交要下载。
如果您想要更安全的强制推送,则需要在编辑器中禁用自动提取。
首先,通过gui进行强制推送是一个好主意,因为它使用非常明确的语法:
vscode 的底层运行:
git push --force-with-lease origin feature/foo:feature/foo
您可以通过 git config 选项 push.useForceIfIncludes
来影响这个调用:
[push]
useForceIfIncludes = true
你可以将此与在 vscode 中禁用自动提取结合使用:
"git.autofetch": false,
这将导致通过 vscode 的 GUI 菜单进行强制推送失败,除非您在此之前明确运行 git fetch
或 git pull
或 git pull --rebase
。
git push --force-with-lease master:refs/heads/master
。我打算将提交 A 重写为提交 Ax。如果远程提交已不是 A,则我想取消操作。也许整个操作可以进行修改:git push --force --replace-sha=a1s2d3f
。 - Devin Rhodegit push --force-with-lease origin main
已经实现了我所说的应该做的事情... - Devin Rhodegit fetch
- 请阅读下面 torek 的回答。 - Devin Rhode