首页 > 解决方案 > 不小心创建了不同大小写的重复分支(Master),如何将其移回master?

问题描述

在将数据从子分支移回master分支时,我不小心创建了一个Master分支(大写 M)。
Windows 上的工具集似乎没有注意到大小写的差异,在我的本地仓库中,我只有两个分支:

 Directory of C:\Users\Me\source\repos\Me\MyProject\.git\logs\refs\remotes\origin

[.]                 [..]                64ThreadsPerBlock   HEAD                master

但是在远程我有三个分支:

64threadsPerBlock
Master
master

我该如何解决问题,以便遥控器没有 aMaster并且它与本地同步master

标签: gitbranch

解决方案


如果你在 Linux 系统上,这很容易,因为 Git总是区分大小写:readmeandREADME是两个不同的文件masterandMaster是两个不同的分支。

如果您使用的是典型的 Windows 或 macOS 文件系统,并且本机文件名不区分大小写,1它会变得混乱:Git认为masterMaster两个不同的名称,但有时 这些名称用作文件系统中的普通文件。然后它期望这两个不同的名称保持不同,但操作系统坚持不这样做。由于 Git 有时只这样做但有时它会按照 Git 认为的方式工作。

您可以做的是利用两个事实:

  1. 另一个系统显然将这两个名称视为不同。
  2. Git 实际上并不关心分支名称是什么——它的拼写方式等——它只关心每个名称中存储的哈希 ID

因此,解决问题的第一步是运行:

git ls-remote origin

这将吐出几行或多行输出,例如:

e2850a27a95c6f5b141dd88398b1702d2e524a81    HEAD
898f80736c75878acc02dc55672317fcc0e0a5a6    refs/heads/maint
e2850a27a95c6f5b141dd88398b1702d2e524a81    refs/heads/master
5d2a92d10f831493d4b0b258ee3cd18d4a7ae995    refs/heads/next
4141ae219919d2e29f3939983be8501770f247a3    refs/heads/seen
1637c5d31a0b0df8e3dcef7072720fe0381520ac    refs/heads/todo
d5aef6e4d58cfe1549adef5b436f3ace984e8c86    refs/tags/gitgui-0.10.0
3d654be48f65545c4d3e35f5d3bbed5489820930    refs/tags/gitgui-0.10.0^{}
[... tons of tags snipped here]

左边的东西,在每一列中,是原始哈希 ID。 这个原始哈希 ID 是 Git 关心的。 右边是另一个 Git 存储库存储原始哈希 ID 的名称refs/heads/*名称是它们的分支名称。

您可能希望将所有这些输出保存在一个文件中。还要注意,我们指望另一个 Git 存储库在我们工作时不会对其进行任何更改。如果它是一个高度活跃的存储库,那么您最好启动一个 Linux 系统(或 Linux VM)并在其中执行普通的 Git 操作。

(旁注:git ls-remote -h origin将输出修剪为分支名称,如果有很多标签,这会非常方便。)


1从技术上讲,每个文件系统都区分大小写。在 macOS 上,您可以轻松地设置区分大小写的磁盘映像、挂载、克隆并在那里修复所有内容,甚至无需使用 VM。可能也有适用于 Windows 系统的区分大小写的文件系统,但我不知道,也不知道是否有任何像 macOS “.dmg” 技巧这样相对简单的东西。


第 1 步之后要做什么

如果他们masterMaster都存储相同的原始哈希 ID,您现在可以要求他们删除两个名称之一:

git push --delete origin Master

(假设您要删除的那个是带有一个大写字母的那个M)。你现在完成了。

如果它们masterMaster存储不同的原始哈希 ID,您现在必须弄清楚该怎么做。您删除这两个名称之一。如果他们master在他们之前Master,你可以做同样的事情,如果他们是平等的:只需删除他们的Master. 但是,如果他们master落后他们Master,或者两者都领先和落后,你就不能完全做到这一点,至少不安全。

理解这一点需要了解“领先”和“落后”的实际含义。在 Git 中,提交哈希 ID 是有向无环图DAG的一部分。我们需要简要介绍一下图论和偏序集。

偏序集

大多数人都熟悉我们得到的全序,例如整数:1 在 2 之前,2 在 3 之前。任何大于 3 的数字都在所有这些之后。

然而,在图表中,我们可以有一条简单的线:

A--B--C--D--...

叉子:

     C   <-- last
    /
A--B
    \
     D   <-- also-last

这里,A 在 B 之前,B 在 C 和 D 之前,但我们不能说 C 在 D 之前或之后。从某种意义上说,它们都在同一个位置,就在 B 之后。

这也是 Git 提交的情况。如果每个大写字母都代表一些原始提交哈希,并且lastandalso-last实际上是masterand Master,那么我们就会遇到这样一种情况,即删除两个名称中的一个将使 Git 无法找到两个“最后”提交。Git 实际上通过使用标识每个“最后一次提交”的名称来工作,并从那里向后工作,因此 Git需要这两个名称来查找两个提交CD此处。

将部分顺序的东西应用到 Git

用 Git 实用的术语来说,这意味着如果你能确定这master真的是“之后” Master——如果他们有:

...--G--H   <-- Master
         \
          I   <-- master

那么你就可以Master安全地删除了。他们现在将拥有:

...--G--H--I   <-- master

他们仍然可以找到所有的提交。

但如果这不安全,您应该让他们为以后的提交创建一个新名称。也就是说,如果他们有这个:

...--G--H   <-- master
         \
          I   <-- Master

或这个:

       H   <-- master
      /
...--G
      \
       I   <-- Master

那么你应该做的是在他们的 Git 存储库中创建一个新名称,该名称标识他们的Master. 为此,您可以运行:

git push origin <hash>:<new-name>

例如,如果他们的哈希 IDmasterdeadbeef,并且您选择 name 作为新分支名称save-for-now,您将运行:

git push origin deadbeef:save-for-now

这将在他们的Git 存储库中创建这种情况:

       H   <-- master
      /
...--G
      \
       I   <-- Master, save-for-now

现在可以安全运行了:

git push origin --delete Master

要求他们删除他们的名字Master

       H   <-- master
      /
...--G
      \
       I   <-- save-for-now

请注意,如果您处于“安全”情况,例如当两个名称都标识相同的最后一次提交时,您仍然可以执行以下安全操作:

git push origin <hash>:save-for-now

创建:

...--G--H   <-- Master, master, save-for-now

然后删除Master给你:

...--G--H   <-- master, save-for-now

(当然都在他们的Git 存储库中)。

现在你已经解决了问题,只需运行git fetch --prune origin

您现在可以让您的 Git 将您的存储库与他们的 Git 存储库重新同步。您可能需要运行git fetch --prune origin两次,因为您的本地 Git 可能会Master在更新后删除该名称master,并且您的 Git 将该名称存储在一个文件中,您的 Git 可能会删除这两个名称,但这是安全的,因为第二个 git fetch会恢复它。(一般来说,Git 似乎以 ASCII 顺序执行这些操作,以便一次提取可以正常工作,但我不知道这是否能保证过去和未来的所有 Git 版本。)

如果之前所有的提交都是安全的,那么您就可以安全地删除它们Master,那么您现在就有了一个正常的设置,可以正常解决所有问题。如果它们以前不安全,那么您现在在自己的 Git 存储库中拥有一个origin/safe-for-now 远程跟踪名称。此名称与您的名称不冲突origin/master,因此正常解决所有问题都没有问题。

换句话说,无论哪种方式,现在都可以安全地回去工作了。


推荐阅读