git 是否有一个选项可以让我安全地推送和重写历史记录?
不完全是,不。 Git 拥有的是git push --force-with-lease。
请记住,Git finds 最初通过分支名称(或其他名称)提交,但真正重要的是哈希 ID。一个分支名称只存储一个哈希 ID:
66346e97 (HEAD -> dev) Implement bank_statement view form
这里,名称 HEAD 包含名称 dev,名称 dev 包含哈希 ID 66346e97(嗯,完整的 40 个字符版本;Git 显示缩写,例如前 8字符,以帮助我们这些可怜的人处理哈希 ID 的丑陋)。
提交66346e97 本身包含上一个 提交的哈希ID add8964f。因此,在使用名称 dev 找到 66346e97 之后,Git 然后使用 66346e97 找到 add8964f。提交add8964f 然后允许Git 找到c632ef4e。
这里的一般方案是每个提交都包含其直接前任的原始哈希 ID。这是提交的元数据的一部分(相对于数据,它包含所有文件的快照)。如果每个提交都指向它的父级,那么我们只需要找到 last 提交的哈希 ID,就可以找到每个提交,只要它们在漂亮整洁的链中:
... <-F <-G <-H
这里哈希H的提交是last,所以它指向哈希G的提交,它又指向哈希F的提交,以此类推. name master 可能包含提交 H 的实际哈希 ID,我们可以这样绘制:
...--F--G--H <-- master
如果有额外的分支,它们的提交可能最终指向H 本身:
...--F--G--H <-- master
\
I--J <-- dev
或者他们可能有提交指向F,或者两者都有:
K--L <-- rc1
/
...--F--G--H <-- master
\
I--J <-- dev
在每种情况下,name 指向 last 提交,我们从该提交向后工作。当我们选择master 时,最后一次提交是H。当我们选择dev 时,最后一次提交是J。当我们选择rc1(候选发布)时,最后一次提交是L。在每种情况下,最后一次提交都会向后指向某个较早的提交。
当我们执行“历史重写”操作时,例如将提交 K-L 重新定位到提交 H 之后,使用:
git checkout rc1; git rebase master
我们得到具有新的不同哈希 ID 的新提交:
K'-L' <-- rc1
/
...--F--G--H <-- master
\
I--J <-- dev
原来的两次提交并没有消失!它们仍在存储库中;他们只是没有分支名称了:
K--L ???
/
/ K'-L' <-- rc1
/ /
...--F--G--H <-- master
\
I--J <-- dev
但是,如果我们之前执行了 git push origin rc1,则提交 K 和 L(无论这些哈希 ID 实际是什么)现在都存在于 另一个 Git 存储库中,位于我们的任何 URL以我们的名义origin。我们自己的 Git 记住这一点,通过将 他们的 名称 rc1 复制到 我们的 名称 origin/rc1。所以最后一张图看起来像这样:
K--L <-- origin/rc1
/
/ K'-L' <-- rc1
/ /
...--F--G--H <-- master
\
I--J <-- dev
他们的Git 仍然有他们的 rc1 指向提交L。他们还没有提交K'-L':我们只是现在才使用git rebase。
当使用各种查看器(包括您正在使用的查看器)时,我们可以我们的Git 将提交K' 和L' 标记为“等于”提交K 和L由于他们的精挑细选,这是我们刚刚运行git rebase 的结果。这就是您在 git log 和其他查看者中看到的内容。
如果我们现在想通过 origin 调用另一个 Git,我们可以做以下三件事中的任何一件:
如果需要(将需要)发送它提交K'-L',然后礼貌地要求它设置其分支名称rc1指向提交L'。但是如果他们的rc1 仍然指向提交L,他们会拒绝,并告诉我们如果他们这样做,他们将失去提交K-L,可能永远丢失。当然,这正是我们希望他们做的事情,但是如果在我们考虑这个问题时,其他人向该链添加了一个新的提交怎么办?好吧,在这种情况下,它仍然会拒绝。
1234563他们可能会服从。如果他们的rc1 之前指向L,那就是我们想要的。但如果其他人添加了新的提交,我们只是丢失了新的提交:不是我们想要的。
幸运的是,有 --force-with-lease 选项,它可以满足我们的需要。
如果我们使用git push --force-with-lease,我们的 Git 将:
- 使用名称
origin 中的 URL 调用他们的 Git
- 向他们提供提交
L',最终会发送提交 K' 和 L',因为他们还没有提交;
-
向他们发送迄今为止最复杂的命令:
- 我们认为您的名称
rc1 包含哈希ID _____(填写我们origin/rc1 的空白,提交的哈希ID 为L)。
- 如果正确,我们命令您将您的名字
rc1 设置为指向提交_____(用L' 的哈希ID 填写空白)。如果不是,则拒绝此命令。
- 无论如何,请告诉我们发生了什么。
当我们得到回复时,我们可以立即判断他们是否他们的 rc1 指向提交L'。如果他们这样做了,一切都很好。如果他们没有,我们会得到一个提示:为什么:他们的rc1 是否指向其他提交,或者他们只是生我们的气并且无论如何拒绝更新他们的rc1? (如果他们在 GitHub 上并且他们已将 rc1 设置为“受保护”,我们会得到后者。如果我们是管理员,我们当然可以覆盖受保护状态。)
如果他们的rc1 不再指向L,我们应该运行git fetch,以便我们更新自己的origin/rc1。然后我们可以弄清楚该怎么做:我们再次变基吗?我们要如何进行?一旦我们弄清楚并完成了它,我们就可以再做一个git push --force-with-lease。如果他们的rc1 没有移动,这个 可以工作。如果他们的rc1 在解决问题时移动了,我们会再次获取并重新开始——循环重复,直到我们完成工作的速度比其他任何人都快他们的rc1 在我们身上.
所以:
或者我唯一的选择是将我的提交重新设置为 xxxxxx/dev 上游?
您应该决定您希望他们拥有哪些提交,以及您希望他们丢弃哪些提交。
请注意,如果您确实说服 Git 改变事物......好吧,让我们暂时假设 Git 在 GitHub 上,只是为了具体。如果您确实说服 GitHub 上的 Git 更改他们的分支名称,以便他们丢失一些提交,那么您可能刚刚给第二个用户带来了问题 第三个 Git 存储库。也许第二个用户依赖于提交K-L(0ff85c07 和3a361741)。 second 用户很容易意外地重新引入那些原始提交,例如,通过使用 git merge。
因此,无论何时您要使用 git push --force,无论是否使用 -with-lease,您都需要确保其他 Git 存储库的所有用户都同意其他 Git 存储库的分支名称像这样移动。事先得到所有其他用户的同意,他们现在知道发生这种情况时该怎么做。
如果您是其他 Git 存储库的唯一用户,您只需同意自己的意见。如果你不同意自己的观点,你可能应该和自己坐下来好好谈谈。 ?