git push --force-with-lease是否总是安全的?

时间:2019-12-12 16:55:59

标签: git

我一直遵循规则,一旦将git历史记录推送到远程存储库就不要对其进行修改。

但是我想知道是否将交互式基础调整为“推后租用”这一规则?

如果强制租赁成功或对这种策略有任何警告,对其他用户而言是否完全安全?

谢谢您的输入。

4 个答案:

答案 0 :(得分:2)

这不安全。

请参见this atlassian blog post,其中描述了git push --force-with-leasegit push -f更安全。但是,它会部分覆盖遥控器,从而使其不安全。

  

但是--force有一个鲜为人知的同级产品,可以部分保护其免受破坏性的强制更新;这是--force-with-lease。

答案 1 :(得分:1)

我想描述一个合理的情况,其中--force-with-lease不能使您免于覆盖同事的工作。

这一切都始于鲍勃

在检出最新的master分支的同时执行以下操作:

# 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的工作之上提交了一些东西,并推动了她的更改, 一些不相​​关的拉取请求合并到master分支中。 爱丽丝的工作树看起来如何

...--F--G--H--I--J   <-- master (HEAD)
           \
            o--o--x--x   <-- feature/one

鲍勃继续

Bob的任务是在当前master分支上重新整理Alices的工作,并执行以下操作

  1. git pull,而他在master分支上,基本上是git fetchgit merge  此步骤的后果以后很重要。

    鲍勃的机器上的情况:

    ...--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的机器现在包含一个最新的遥控器,但起源/功能/一项的更改尚未合并到  功能/一个。

  2. Bob使用git checkout feature/one

  3. 检出分支
  4. 鲍勃忘了做git pull
  5. Bob使用git rebase -i origin/master

    将其本地分支重新建立在主节点上

    鲍勃机的情况如下:

    ...--F--G--H--I--J   <-- master (HEAD)
                      \
                       o--o   <-- feature/one
    
  6. Bob认为他成功地重新设置了分支的基础,并强行将feature/one推到origin/feature/one,因为  鲍勃是个好人,他推着git push --force-with-lease origin feature/one并期望  如果--force-with-lease要覆盖其他人的工作,则会阻止其推送操作。  但如果我理解,该选项将无法保存他  this blog post正确,--force-with-lease看不到  鲍勃机器上的原点/特征/一个与实际原点/特征/一个之间的差异,因此假定  鲍勃的工作树如果被强行推入遥控器,则不会覆盖遥控器上的任何内容。缺乏的原因  区别在于,在之前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
    

    这是上面链接的博客文章的相关部分:

      

    提取操作将从远程提取对象和引用,但没有匹配的合并不会更新工作   树。这将使其看起来好像遥控器的工作副本是最新的,而实际上没有   包括新工作,并诱骗--force-with-lease覆盖远程分支

答案 2 :(得分:0)

  

我一直遵循规则,不要修改已推送到远程存储库的提交。

无法修改提交。它们是否已发送到另一个存储库都没有关系:您不能更改任何现有的提交。

但这也不是您使用git push -f所做的。这仍然不会修改现有的提交!这样做是告诉另一个接受推送的Git,即使更改 name 会“丢失”某些提交,它也应该更改 name ( s。

这里的关键概念是可达性。请参阅Think Like (a) Git,以全面了解可达性。简短的版本是这样的:每个Git提交都有一个“真实名称”,这是其原始哈希ID。每个Git提交还包含一些更早提交的原始哈希ID。 1 我们说此提交指向较早的提交)。同时,名称(与分支名称一样)指向恰好一个提交(包含其哈希ID):具体来说,被认为“包含在以下位置的 last 提交”分支”。

所以我们可以画这个:

... <-F <-G <-H   <--master

大写字母代表大的丑陋哈希ID。如果H是象master这样的分支中的 last 提交,则名称master指向H。同时,H包含其父提交G的哈希ID,因此H指向GG包含其父项F的哈希ID,依此类推,一直返回到第一次提交。

虽然内部箭头都这样向后指向,但在StackOverflow帖子中将它们绘制为连接线比较容易,所以我现在就开始做。让我们看一下如何向master添加 new 提交。我们运行:

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(我们用完了的{em} 直到提交I为止),然后命名为{ {1}}指向新的提交master

I

现在假设我们将...--F--G--H--I <-- master (HEAD) 更新到某些 other 存储库。另一个存储库具有自己的分支名称,与我们的分支名称无关,但是在启动时,我们与另一个存储库完全同步:它具有相同的提交和相同的哈希ID,直到{{ 1}}。因此,我们向另一个Git发送了提交git push,然后询问他们:其他H上的Git,请确定您的I的名称指向{{ 1}}。他们说确定,现在他们他们的管理员也指向这个新提交origin,我们都在再次同步。

但是现在我们意识到:啊,我们犯了一个错误!我们想停止使用master,而是进行新的改进的提交I!也许错误就像提交消息中的错字一样简单,或者也许我们必须先修复文件并I,但最终我们运行:

I

尽管标志名,但此不会更改任何现有的提交。不能!它所做的是完全 new 提交J。但是,不是使git add指向git commit --amend ,而是使J指向J parent I

J
在我们的存储库中找不到

提交I ,因为我们以前用来查找的名称H)找不到再找到它。现在,该名称可以找到提交 J <-- master (HEAD) / ...--F--G--H--I [abandoned] 。从I起,我们回到master。好像我们已经更改了提交J。不过,我们还没有,实际上它仍在我们的存储库中,而且-如果我们还没有摆弄Git中的任何配置旋钮,它将停留至少30天,因为有一些半秘密名称 2 ,我们可以通过它们 查找J的哈希ID,从而在之后再次查看提交H全部。


1 这些必须是较早/较早的提交:

  • 要将某个提交的哈希ID放入正在执行的某个新提交中,该另一个提交的哈希ID必须存在。 (Git不会让您使用不存在的提交的哈希ID。)因此,这些是现有的提交,您现在建议在此提交中进行。

  • 然后,
  • Git进行新的提交,并为其分配一个新的唯一哈希ID:一个从未发生过的哈希ID。现在完成的新提交无法更改。实际上,任何提交都无法更改。因此,每个新提交中的哈希ID都是旧提交中的ID。

结果,提交始终指向向后,指向较早的提交。因此,Git向后工作。

2 这些主要存在于Git的 reflogs 中。对于某些移动分支名称的操作,Git还将哈希ID临时存储在另一个特殊名称I中。该名称将被 next 操作覆盖,该操作将哈希ID保存在I中,但是IORIG_HEAD失败之后特别有用。 / p>


这是ORIG_HEAD进入的地方

我们现在有了这个:

ORIG_HEAD

在我们自己的存储库中。我们希望 other Git存储库(位于git rebase的那个存储库)也具有此功能。但是,如果我们运行--force,我们的Git会调用他们的Git,发送提交 J <-- master (HEAD) / ...--F--G--H--I [abandoned] ,然后说:请,如果可以,请把您的origin名称指向commit git push如果这样做,他们也会“丢失”提交J!他们通过他们的名字master找到J;如果他们移动I指向I,则将找不到master 3

最后,他们只会说不,我不会那样做。您的Git会向您显示master消息:

J

告诉您他们拒绝以设置I的相同方式设置他们的 rejected,因为他们会丢失一些提交(这是“非快进”部分)。

要解决这个问题,您可以发送强制命令:设置您的 ! [rejected] master -> master (non-fast forward) 他们可能会也可能不会服从,但是如果他们不遵守,那将不再是因为他们会丢失提交:即使他们最终会丢失提交,“强制”选项也要求这样做。

这里的缺点是:如果某人 else 在修复master的同时在您的提交master上构建了另一个新的提交,该怎么办?和您的替代人master?然后,他们的 Git(位于I的那一个)实际上具有:

I

如果您使用J告诉他们将其origin设置为 ...--F--G--H--I--K <-- master ,他们将得到以下结果:

git push --force

,被放弃的提交不仅包括您的master(您想离开),而且还包括其他人的J

输入 J <-- master / ...--F--G--H--I--K [abandoned]

I的用途是使用您的 Git对他们的 Git的K的记忆。请注意,当您运行--force-with-lease从它们中获取提交 时,您的Git在其自己的存储区域中存储了分支名称,将其修改为{{ 1}},然后成为您的远程跟踪名称。因此,在您自己的Git中,您实际上拥有以下功能:

--force-with-lease

您的master记得他们的 git fetch记得提交origin/

当您使用 J <-- master (HEAD) / ...--F--G--H--I <-- origin/master 时,您的Git会调用其Git,并像往常一样发送提交origin/master。不过,这一次,而不是如果可以,请将master设置为I git push --force-with-lease设置为J! ,您的Git发送以下形式的请求:

我认为您的J指向master。如果是这样,请强制将其移至指向J

这引入了拒绝操作的新方法。如果他们的master现在指向master,他们仍然会说 no 。但是,如果他们的I仍然指向J(您希望他们放弃的承诺),他们可能会服从强力推动,并使他们的master指向{ {1}}。

如果他们确实服从,那么您的Git也会更新您自己的K,也指向master。这将保持您的I姓名所记住的属性,并尽您所能地保持Git的能力,即 Git的分支名称所指向的位置。但这可能会过时,因此您可能需要运行master(或仅运行J)来更新您的远程跟踪名称。您需要运行origin/master的频率取决于 Git更新的速度。

当然,如果您运行J,则最好检查一下origin/*是否仍指向您的想法!注意git fetch origin的输出:它会告诉您Git是否更新了自己的git fetch。如果他们的git fetch搬走了,那么其他人已经弄弄了他们的提交,您可能需要知道这一点。


3 服务器Git通常启用reflog,因此它们也比我们自己的本地克隆更快地垃圾收集废弃的提交。

答案 3 :(得分:0)

使用Git 2.30(Q1 2021)可以使其更安全:除非用户小心,否则很容易误用“ git push --force-with-lease[=<ref>] man”自己的“ git fetch”。

新选项“ --force-if-includes”试图确保在检查了将要强制替换的远程引用的尖端处的提交之后,才创建了强制推入的内容。

当分支的远程跟踪引用具有我们本地没有的更新时,它将拒绝强制更新。

请参见commit 3b5bf96commit 3b990aacommit 99a1f9aSrinidhi Kaushik (clickyotomy)(2020年10月3日)。
请参见commit aed0800Junio C Hamano (gitster)(2020年10月2日)。
(由Junio C Hamano -- gitster --commit de0a7ef中合并,2020年10月27日)

push:添加“ --force-if-includes”的引用日志检查

签名人:Srinidhi Kaushik

添加检查以验证是否可以通过其“ reflog”条目之一访问本地分支的远程跟踪引用。

该检查遍历本地引用的引用日志,以查看是否存在用于远程跟踪引用的条目并将收集到的所有提交收集到一个列表中;如果reflog中的条目与远程ref匹配,或者如果条目时间戳较旧,则迭代会停止,而远程ref的“ reflog”是最新条目。如果没有为远程引用找到条目,则调用"in_merge_bases_many()“来检查从收集的提交列表中是否可以访问该条目。

当基于远程引用的本地分支已被倒回并被强行推到远程上时,“ --force-if-includes”将运行检查,以确保对远程跟踪引用进行任何更新在最近一次更新到本地分支(例如,通过“ git pull”)之间和(在通过推送之前)在本地更新之前(已从另一个存储库推送)。强制更新。

如果在未指定“ --force-with-lease”的情况下传递了新选项,或者未与“ --force-with-lease=<refname>:<expect>”一起指定,则为“无操作”。