[英]Is git push --force-with-lease always safe?
我一直遵循的规则是,一旦将 git 历史推送到远程存储库,就不要修改它。
但我想知道是否交互式变基到 push --force-with-lease 绕过了这个规则?
如果强制租赁成功,其他用户是否完全安全,或者此策略是否有任何警告?
预先感谢您提供任何意见。
我想描述一个合理的案例,其中--force-with-lease
不会让您免于覆盖同事的工作。
在签出最新的主分支时执行以下操作:
# Creating a new branch called feature/one
$ git checkout -b feature/one
# Do some changes and git add ...
$ git commit
# Push for the first time
$ git push --set-upstream origin feature/one
# Checkout another branch to work on something else
鲍勃机器上的情况
...--F--G--H <-- master (HEAD)
\
o--o <-- feature/one
Alice 拿起 feature/one 上的工作,并在 Bob 的工作之上提交内容并推送她的更改,同时一些不相关的拉取请求被合并到主分支。 Alice 的工作树是什么样子的
...--F--G--H--I--J <-- master (HEAD)
\
o--o--x--x <-- feature/one
Bob 的任务是在当前的 master 分支上重新调整 Alices 的工作,并执行以下操作
git pull
而他在 master 分支上,基本上就是一个git fetch
和一个git merge
这一步的结果在后面很重要。
Bob 机器上的情况:
...--F--G--H--I--J <-- master (HEAD) \\ o--o <-- feature/one
...--F--G--H--I--J <-- origin/master (HEAD) \\ o--o--x--x <-- origin/feature/one
Bob 的机器现在包含一个最新的遥控器,但 origin/feature/one 中的更改尚未合并到 feature/one。
Bob 使用git checkout feature/one
出分支git checkout feature/one
git pull
Bob 使用git rebase -i origin/master
在 master 上重新建立他的本地分支
bob 机器上的情况是这样的:
...--F--G--H--I--J <-- master (HEAD) \\ o--o <-- feature/one
Bob 认为他成功地重新定位了他的分支并强制将feature/one
推送到origin/feature/one
,因为 Bob 是个好人,他使用git push --force-with-lease origin feature/one
并期望选项--force-with-lease
将阻止他的推送操作,如果他要覆盖其他人的工作。 但是该选项不会拯救他,如果我正确理解这篇博文,-- --force-with-lease
认为 Bob 机器上的 origin/feature/one 与实际的 origin/feature/one 之间没有区别,因此假设 Bob 正在工作如果强制推送到遥控器上,树将不会覆盖遥控器上的任何内容。 缺乏差异的原因在于在不同的分支上执行了隐式git fetch
作为git pull
一部分(在本节的第 1 步中)。
推送后,遥控器看起来像这样
...--F--G--H--I--J <-- master (HEAD) \\ o--o <-- feature/one
代替
...--F--G--H--I--J <-- master (HEAD) \\ o--o--x--x <-- feature/one
这是上面链接的博客文章的相关部分:
fetch 将从远程拉取对象和引用,但没有匹配的合并不会更新工作树。 这将使远程的工作副本看起来好像远程的工作副本是最新的,而不实际包含新工作,并欺骗
--force-with-lease
覆盖远程分支
这是不安全的。
请参阅此 atlassian 博客文章,其中描述了git push --force-with-lease
比git push -f
更安全。 但是,它会部分覆盖遥控器,使其不安全。
但是 --force 有一个鲜为人知的兄弟,可以部分防止破坏性的强制更新; 这是--force-with-lease。
使用 Git 2.30(2021 年第一季度)可以使其更安全:“ git push --force-with-lease[=<ref>]
( man ) ”很容易被误用而丢失提交,除非用户照顾好自己” git fetch
”。
一个新选项“ --force-if-includes
”试图确保在检查即将被强制替换的远程引用尖端的提交后创建被强制推送的内容。
当其远程跟踪引用具有我们在本地没有的更新时,它会拒绝分支的强制更新。
请参阅Srinidhi Kaushik ( clickyotomy
) 的commit 3b5bf96 、 commit 3b990aa 、 commit 99a1f9a (2020 年 10 月 3 日) 。
请参阅Junio C gitster
( gitster
) 提交的 aed0800 (2020 年 10 月 2 日) 。
(由Junio C gitster
-- gitster
--在提交 de0a7ef 中合并,2020 年 10 月 27 日)
push
:为“--force-if-includes
”添加引用日志检查签字人:Srinidhi Kaushik
添加检查以验证本地分支的远程跟踪引用是否可从其“引用日志”条目之一访问。
检查遍历本地引用的引用日志,以查看是否有远程跟踪引用的条目,并将所有看到的提交收集到列表中; 如果引用日志中的条目与远程引用匹配,或者条目时间戳比远程引用的“引用日志”的最新条目旧,则迭代停止。 如果没有找到远程引用的条目,则调用
"in_merge_bases_many()
”以检查它是否可以从收集的提交列表中访问。当一个基于远程引用的本地分支被倒带并被强制推送到远程时,“
--force-if-includes
”运行检查以确保对远程跟踪引用的任何更新可能有在上次更新到本地分支的时间之间(例如通过“git pull
”)和推送时间之前发生的(通过从另一个存储库推送)在允许强制更新之前已在本地集成。如果在没有指定“
--force-with-lease
”的情况下传递新选项,或者与“--force-with-lease=<refname>:<expect>
”一起指定,则它是“无操作”。
我一直遵循不修改已推送到远程存储库的提交的规则。
无法修改提交。 它们是否已发送到另一个存储库并不重要:您不能更改任何现有提交。
不过,这也不是您使用git push -f
所做的。 这仍然不会修改现有的提交! 这样做是告诉另一个 Git——接收推送的那个——它应该更改name ,即使对name的更改会“丢失”一些提交。
这里的关键概念是可达性。 请参阅Think Like (a) Git以了解有关可达性的所有信息。 然而,简短的版本是这样的:每个 Git 提交都有一个“真实名称”,即它的原始哈希 ID。 每个 Git 提交还包含一些早期提交的原始哈希 ID。 1我们说这个提交指向较早的提交。 同时,一个名称——如分支名称——指向(包含)恰好一个提交的哈希 ID:特别是最后一个被视为“包含在分支中”的提交。
所以我们可以这样画:
... <-F <-G <-H <--master
其中大写字母代表丑陋的大哈希 ID。 如果H
是像master
这样的分支中的最后一次提交,则名称master
指向H
。 同时H
包含其父提交G
的哈希 ID,因此H
指向G
。 G
包含其父F
的哈希 ID,依此类推,一直回到第一次提交。
虽然内部箭头都像这样向后指向,但在 StackOverflow 帖子中将它们绘制为连接线更容易,所以我现在要这样做。 让我们看看我们如何向master
添加一个新的提交。 我们跑:
git checkout master
# ... do some work, run `git add` ...
git commit
git checkout
步骤将特殊名称HEAD
附加到分支名称,以便 Git 知道要更新哪个分支名称,以防我们有多个分支名称:
...--F--G--H <-- master (HEAD)
\
o--o <-- develop
例如。 我们完成工作并进行新的提交,我们将其称为I
。 Git 写出提交I
,让它指向提交H
我们在创建I
之前一直在使用的那个——然后让名称master
指向新的提交I
:
...--F--G--H--I <-- master (HEAD)
现在假设我们git push
这个更新到其他一些存储库。 另一个存储库有自己的分支名称,独立于我们的分支名称,但我们在开始时与另一个存储库完全同步:它具有相同的提交,具有相同的哈希 ID,直到H
。 所以我们发送的其他的Git我们犯I
,然后问他们:其他的Git在origin
,请,如果是OK,让你的master
的名字点犯I
。 他们说OK,现在他们有他们的主人指向这个新犯I
过了,我们都在同步一次。
但现在我们意识到:啊,我们犯了一个错误! 我们想停止使用I
并改用新的和改进的提交J
! 也许错误就像提交消息中的错字一样简单,或者也许我们必须先修复一个文件并git add
它,但最终我们运行:
git commit --amend
尽管有标志的名称,但这不会改变任何现有的 commit 。 不能! 它所做的是进行一个全新的提交J
。 但不是让J
指向I
,而是让J
指向I
的父H
:
J <-- master (HEAD)
/
...--F--G--H--I [abandoned]
凯明I
再也不能在我们的资料库中找到,因为这个名字我们用来找到它- master
-doesn't找到它了。 该名称现在找到 commit J
。 从J
,我们回到H
。 好像我们已经改变了 commit I
。 不过,我们还没有,事实上它仍然存在于我们的存储库中,而且——如果我们没有摆弄 Git 中的任何配置旋钮——它将在那里停留至少 30 天,因为有一些半秘密名称2我们可以通过它找到I
的哈希 ID,因此毕竟再次查看提交I
1这些必须是更早/更旧的提交:
要将某个提交的哈希 ID 放入您正在制作的某个新提交中,该其他提交的哈希 ID 必须存在。 (Git 不会让你使用不存在的提交的哈希 ID。)所以这些是现有的提交,在这个提交中你建议现在进行。
然后 Git 进行新的提交并为其分配一个新的唯一哈希 ID:以前从未发生过的哈希 ID。 这个新的提交,既然已经做出,就不能更改。 事实上,任何提交都不能改变。 因此,每个新提交中的哈希 ID 都是旧提交的哈希 ID。
因此,提交总是向后指向更早的提交。 因此,Git 向后工作。
2这些主要在 Git 的reflogs 中。 对于某些移动分支名称的操作,Git 也会将哈希 ID 临时存储在另一个特殊名称ORIG_HEAD
中。 这个名字会被下一个在ORIG_HEAD
中保存哈希 ID 的操作覆盖,但是ORIG_HEAD
在一个失败的git rebase
之后特别有用,例如。
--force
用武之地我们现在有这个:
J <-- master (HEAD)
/
...--F--G--H--I [abandoned]
在我们自己的存储库中。 我们希望另一个Git 存储库 - origin
存储库 - 也有这个。 但是如果我们运行git push
,我们的 Git 调用他们的 Git,发送提交J
,然后说:请,如果可以的话,让你的master
名称指向提交J
。 如果他们这样做,他们也会“失去”承诺I
! 他们是通过他们的名字master
找到I
; 如果他们移动他们的master
指向J
,他们将无法找到I
。 3
最后,他们只会说不,我不会那样做。 你的 Git 向你展示了rejected
消息:
! [rejected] master -> master (non-fast forward)
告诉你,他们拒绝设定他们的master
同样的方式,你有你的master
组,因为他们会失去一些提交(这是“非快进”的一部分)。
为了克服这个问题,你可以发送一个强有力的命令:设置你的master
! 他们可能服从也可能不服从,但如果他们不服从,那不再是因为他们会失去提交:“强制”选项表示即使他们会因此失去提交,也要这样做。
这里的缺点是:如果其他人在您的提交I
之上构建了另一个新提交,而您正在用您的替代品J
修复您的I
怎么办? 然后他们的Git—— origin
的那个——实际上有:
...--F--G--H--I--K <-- master
如果你使用git push --force
告诉他们将他们的master
设置为J
,他们最终会得到:
J <-- master
/
...--F--G--H--I--K [abandoned]
被放弃的提交不仅包括你的I
(你想要的),还包括其他人的K
--force-with-lease
--force-with-lease
所做的是使用您的Git对其Git 的master
的记忆。 请注意,当您运行git fetch
以获取来自它们的提交时,您的 Git 在其自己的存储区域中存储它们的分支名称,它们的分支名称被修改为在它们之前具有origin/
并成为您的远程跟踪名称。 所以在你自己的 Git 中,你实际上有这个:
J <-- master (HEAD)
/
...--F--G--H--I <-- origin/master
你的origin/master
记得他们的master
记得提交I
。
当你使用git push --force-with-lease
,你的 Git 调用他们的 Git,像往常一样发送提交J
不过,这一次,不是请把你的master
设置为J
如果可以的话,或者把你的master
设置为 J! ,您的 Git 发送以下形式的请求:
我想你的master
指的是I
。 如果是这样,请用力移动它以指向J
这引入了一种拒绝操作的新方法。 如果他们的master
现在指向K
,他们仍然会说没有。 但是如果他们的master
仍然指向I
- 你想让他们放弃的承诺 - 他们可能会服从强力推动并使他们的master
指向J
。
如果他们确实服从,您的 Git 也会更新您自己的origin/master
以指向J
这将尽您的 Git 能力维护您的origin/*
名称记住的属性,即其Git 的分支名称所指向的位置。 但这可能会过时,因此您可能需要运行git fetch origin
(或仅运行git fetch
)来更新您的远程跟踪名称。 您需要运行git fetch
频率取决于他们的Git 更新速度。
当然,如果你确实运行了git fetch
,你最好检查一下你的origin/master
仍然指向你认为的地方! 注意git fetch
的输出:它会告诉你你的 Git 是否更新了你自己的origin/master
。 如果他们的master
移动了,其他人已经摆弄了他们的提交,你可能需要知道这一点。
3服务器 Git 通常没有启用 reflog,因此它们也会比我们自己的本地克隆更快地收集废弃的提交。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.