Git强制推送:如何防止覆盖其他用户的更改

4
我经常需要使用git resetgit rebase来更改最近的一些提交(重新排列,将多个提交合并为一个等)。这需要在推送时使用强制选项。不幸的是,这也可能会覆盖远程存储库中其他用户的更改。
为什么我需要重写历史记录?因为我们正在一个功能分支上进行大量工作(具体来说是从使用Swing转换为SWT),并且必须尽可能地保持它的短小。为了实现这一点,我们经常发现,在功能分支被分叉之前,某些提交应该进入主分支。
如何使强制git push失败,如果我还没有从远程存储库获取最新更改?
2个回答

5
如果其他人在存储库中看到了历史提交记录,而您随后对其进行了重写,那么您将会遇到问题。这是git设计的一部分。
git无法真正区分您是否已经重写了提交记录,但有一个最新的存储库,或者您是否没有重写提交记录,但必须合并提交记录才能推送。检测最新的推送方式是通过检查目标引用中的每个提交是否在要推送的引用的历史记录中; 这被称为快进。当您进行变基时,会从本地分支中删除提交,并创建新的、类似的提交。git不能确定这些新提交是否与历史记录中的旧提交相等,因此将其视为待处理的合并,并需要使用强制选项来覆盖已经存在于目标存储库中的内容。
因此,如果您使用强制选项,则会覆盖目标中的所有内容。这是按设计所做的。此外,其他人从您的存储库拉取也会因此出现问题。假设您翻转了存储库中的提交顺序:
A-------B-------C oldmaster
 \
  ------C'------B' master

如果其他用户在本地拥有一个oldmaster的副本,并从主分支中拉取,git将尝试合并这两个分支。这将会使C'B'中的补丁被重复应用。不好。Git有时可以在合并时检测到这些问题,但即使合并正确,你也会留下:
A-------B-------C------D master
 \                    /
  ------C'------B'----

现在,您的变基提交已经“死而复生”了。

使用变基也无济于事;您最终可能会得到:

A------B------C------C'------B'

在这种简单的情况下,Git 可能能够检测到双重应用的补丁并将其删除,但不能保证。
因此,总之:不要合并已经对其他人可见的提交。当它仍然是你本地保留的私有分支或者是带有大量警告的公共分支“开发分支 - 可能会被合并 - 不要基于此分支进行工作”时,可以随意进行所有的合并操作。但是一旦有其他人在其上进行构建,则停止更改历史记录。
如果你绝对必须经常与多个用户混淆历史记录,那么你可能会发现最好使用功能分支作为一个补丁包,使用 StGITguilttopgit 或类似工具。然后,您可以将它们视为补丁来处理,按照需要重新排序 patches(但不能更改提交本身!),然后在推送变更到上游时,在原始上游分支的顶部线性化补丁。

顺便说一句,我们不使用合并而是使用变基。因此在你的例子中,D 提交将不会发生。Git 是否可以验证我的 origin/mybranch 指向与远程仓库的 mybranch 相同的提交,并在它们不同时拒绝强制推送? - Mot
@Mike,底层协议的设计并不是作为原子比较和交换的;远程端直接验证提交是否快进。git在推送操作中根本不使用远程引用。 - bdonlan

2

我不知道有没有简单的方法解决这个问题,但是在多用户环境中通常强制更新是一个不好的想法。尽管如此,你可以通过操作仓库上的钩子来实现一些神奇的效果。我猜更好的办法是改变你使用git的方式。

首先,你真的需要修改历史记录吗?大部分时间正确的做法是保留历史记录并添加新的提交。

如果你真的认为必须重新排列提交(比如为了符合编码标准?),而且你不能教育那些提交者使他们一次性提交正确的内容,你考虑过不同的设置吗?有一个中央“授权”仓库,但只允许发布工程师(或集成工程师或团队或其他你想称呼他们的人)推送到它。普通用户将仓库发布到其他地方(通常是他们自己的工作站),而不是推送,他们会要求发布工程师从他们那里拉取。发布工程师然后审查更改,必要时重新排列和合并它们,运行任何测试或其他所需步骤,并将结果推送到授权仓库。


1
我现在已经解释了为什么我需要重写历史。 - Mot

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接