6

I've always followed the rule not to modify the git history once it has been pushed to a remote repository.

But I am wondering if interactive rebasing into a push --force-with-lease bypasses this rule?

Is it perfectly safe for other users if the force-with-lease succeeds or are there any caveats to this strategy?

Thank you in advance for any input.

4

4 回答 4

4

使用 Git 2.30(2021 年第一季度)可以使其更安全:“ ( man ) ” 很容易被滥用而丢失提交,除非用户照顾好自己的“ ”。git push --force-with-lease[=<ref>]git fetch

一个新选项“ --force-if-includes”试图确保在检查即将被强制替换的远程 ref 尖端的提交之后创建强制推送的内容。

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

请参阅Srinidhi Kaushik ( )的commit 3b5bf96commit 3b990aacommit 99a1f9a(2020 年 10 月 3 日) 。 请参阅Junio C Hamano ( ) 的提交 aed0800(2020 年 10 月 2 日(由Junio C Hamano 合并 -- --提交 de0a7ef中,2020 年 10 月 27 日)clickyotomy
gitster
gitster

push--force-if-includes: 为“ ”添加 reflog 检查

签字人:Srinidhi Kaushik

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

检查遍历本地 ref 的 reflog 以查看是否有远程跟踪 ref 的条目并将看到的任何提交收集到列表中;如果 reflog 中的条目与远程 ref 匹配,或者如果条目时间戳比远程 ref 的“reflog”的最新条目更旧,则迭代停止。如果没有找到远程 ref 的条目,"in_merge_bases_many()则调用 " 以检查它是否可以从收集的提交列表中访问。

当基于远程 ref 的本地分支已被回绕并被强制推送到远程时,“ --force-if-includes”运行检查以确保对远程跟踪 ref 的任何更新可能已经发生(通过从另一个存储库推送) 在本地分支的最后一次更新时间(git pull例如,通过“”)和推送之前的时间之间,在允许强制更新之前已在本地集成。

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

于 2020-11-01T00:53:20.093 回答
4

我想描述一个似是而非的案例,--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

Bob 机器上的情况

...--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 分支上 rebase Alices 的工作,并执行以下操作

  1. git pull而他在master分支上,基本上就是agit fetch和agit 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。

  2. Bob 签出分支git checkout feature/one

  3. 鲍勃忘记做git pull
  4. Bob 将他的本地分支重新设置在 master 上git rebase -i origin/master

    鲍勃机器上的情况如下所示:

    ...--F--G--H--I--J   <-- master (HEAD)
                      \
                       o--o   <-- feature/one
    
  5. Bob 认为他成功地重新定位了他的分支并强制推feature/one送到origin/feature/one,因为 Bob 是一个好人,他推送git push --force-with-lease origin feature/one并期望该选项 --force-with-lease将阻止他的推送操作,如果他即将覆盖其他人的工作。 但是该选项不会拯救他,如果我正确理解 了这篇博--force-with-lease文, Bob 的机器上的原点/特征/一个和实际的原点/特征/一个之间没有区别,因此假设 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
    

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

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

于 2019-12-19T15:58:55.440 回答
3

不安全

请参阅此 atlassian 博客文章,其中描述了git push --force-with-leasegit push -f. 但是,它会部分覆盖遥控器,使其不安全。

但是 --force 有一个鲜为人知的兄弟,它可以部分防止破坏性的强制更新;这是--force-with-lease。

于 2019-12-12T17:05:52.493 回答
2

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

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

不过,这也不是你正在做的事情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包含其父提交的哈希 ID G,所以H指向G. G包含其 parent 的哈希 ID F,依此类推,一直到第一次提交。

虽然内部箭头都像这样向后指向,但在 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 写出 commit I,让它指向 commit H——我们在做之前一直在使用I那个——然后让 namemaster指向新的 commit I

...--F--G--H--I   <-- master (HEAD)

现在假设我们对其他git push存储库进行了更新。另一个存储库有自己的分支名称,独立于我们的,但是当我们开始时,我们与另一个存储库完全同步:它具有相同的提交,具有相同的哈希 ID,直到. 所以我们把我们的 commit 发给了另一个 Git ,然后问他们:Other Git at ,拜托,如果可以的话,请把你的名字指向 commit 他们 说好的,现在他们的主人也指向了这个新的提交,我们又重新同步了。HIoriginmasterII

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

git commit --amend

尽管有标志的名称,但这不会改变任何现有的 commit。不能!它所做的是进行一个全新的提交J。但它不是J指向I,而是J指向I H级:

             J   <-- master (HEAD)
            /
...--F--G--H--I   [abandoned]

I 在我们的存储库中无法再找到Commit ,因为我们用来查找它的名称——<code>master——已经找不到了。该名称现在找到了 commit J。从J,我们退一步H。好像我们已经改变了 commit I。不过,我们还没有,事实上它还在我们的存储库中,而且——如果我们没有摆弄 Git 中的任何配置旋钮——它会那里至少保留 30 天,因为有一些半-秘密名称2我们可以通过它找到的哈希 ID,然后再次I查看提交。I


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

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

  • 然后 Git 进行新的提交并为其分配一个新的唯一哈希 ID:一个以前从未发生过的哈希 ID。这个新的提交,既然已经做出,就不能改变了。确实,没有任何提交可以改变。因此,每个新提交中的哈希 ID 都是旧提交的哈希 ID。

因此,提交总是向后指向更早的提交。因此,Git 向后工作。

2这些大多在 Git 的reflogs中。对于某些移动分支名称的操作,Git 也会将哈希 ID 临时存储在另一个特殊名称ORIG_HEAD中。此名称会被下一个保存哈希 ID 的操作覆盖ORIG_HEAD,但ORIG_HEAD在失败之后特别有用git rebase,例如。


这是--force进来的地方

我们现在有这个:

             J   <-- master (HEAD)
            /
...--F--G--H--I   [abandoned]

在我们自己的存储库中。我们希望另一个Git 存储库(位于 at 的那个)origin也有这个。但是如果我们运行git push,我们的 Git 会调用他们的 Git,通过 commit 发送J,然后说:如果可以,请让你的master名字指向 commit J 如果他们这样做,他们也将“失去”承诺II他们正在通过他们的名字寻找master;如果他们移动他们master的指向J,他们将无法找到I3

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

 ! [rejected]        master -> master (non-fast forward)

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

为了克服这个问题,你可以发送一个强有力的命令:设置你的master! 他们可能服从也可能不服从,但如果他们不服从,就不再是因为他们会失去提交:“强制”选项表示即使他们因此会失去提交,也要这样做。

这里的缺点是:如果其他在你的 commit 之上构建了另一个新的 commit I,而你正在I用你的替换来修复你的 commitJ怎么办?然后他们的Git——那个在上面的——origin实际上有:

 ...--F--G--H--I--K   <-- master

如果您过去git push --force告诉他们将其设置masterJ,他们最终会得到:

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

被放弃的提交不仅包括你的I(你想要的),也包括其他人的K

进入--force-with-lease

什么--force-with-lease是使用你的Git 对他们Git 的master. 请注意,当您运行以git fetch它们那里获取提交时,您的 Git 将在其自己的存储区域中存储它们的分支名称,修改为在它们前面并成为您的远程跟踪名称。所以在你自己的 Git 中你实际上有这个:origin/

             J   <-- master (HEAD)
            /
...--F--G--H--I   <-- origin/master

origin/master记得他们 master记得提交I

当你使用 时git push --force-with-lease,你的 Git 会调用他们的 Git,J像往常一样发送提交。不过,这一次,不是“如果可以,请设置masterJ”或“将你的设置master为 J”!,您的 Git 会发送以下形式的请求:

      我认为你的master观点I。如果是这样,则强制将其移至指向J

这引入了一种拒绝操作的新方法。如果他们master现在指向K,他们仍然会说不。但如果他们master仍然指向——你希望I他们放弃的提交——他们可能会服从强行推动并指向.masterJ

如果他们确实遵守了,你的 Git 也会更新你自己origin/master的指向Jorigin/*这会尽您的 Git 的能力维护您的名字记住的属性,即他们的Git 分支名称指向的位置。但这可能会过时,因此您可能需要运行git fetch origin(或只是git fetch)来更新您的远程跟踪名称。您需要运行的频率git fetch取决于他们的Git 更新速度。

当然,如果你git fetch,你最好检查一下你是否origin/master仍然指向你想的地方!注意来自的输出git fetch:它会告诉您 Git 是否更新了您自己的origin/master. 如果他们master已经搬家了,其他人已经摆弄了他们的提交,你可能需要知道这一点。


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

于 2019-12-12T18:19:57.413 回答