【问题标题】:git: why would a merge commit be required for replacements?git:为什么替换需要合并提交?
【发布时间】:2017-12-15 15:30:18
【问题描述】:

我们的 git repo 是从不同的 VCS (Perforce) 导入的,但标准导入无法恢复分支关系(即分支 1.1 是从分支 1.0 派生的)。为了解决这个问题,我们添加了嫁接:git replace --graft <commit> <parent> 来记录从每个分支的创建到其父提交的缺失关系。

所以我们有一个 git 存储库,它在 .git/refs/replace 中有一些替换。 (这些可能应该通过某种方式重新设置来删除,但现在可能为时已晚)

这些替换必须手动使用:

git pull origin 'refs/replace/*:refs/replace/*'

一位同事不知道这一点,但不知何故能够将更改推送到 repo,以便:

git pull origin 'refs/replace/*:refs/replace/*'

导致合并冲突,例如:

Simple merge did not work, trying automatic merge.
ERROR: /some/file: Not handling case c6aa12b3f446c57921a68c5fc73dae9e086c2bdb ->  -> bb909246180daf894ccdb59cc4a4ff398ac62bad
fatal: merge program failed
Automated merge did not work.
Should not be doing an octopus.
Merge with strategy octopus failed

我不明白这里合并了什么。 如果我使用:

git pull origin 'refs/replace/*:refs/replace/*' -s ours

合并成功,我最终(提交后)收到如下合并消息:

commit 67d7f7cc40826e8a84beed3af9999d39e411e65d
Merge: 718dbe3 c9333d1 b870b22 d3f10f7 77d0835 de79e03 d7f0e7c 97ca1f8 Author: xxxxx
Date:   Tue Jul 11 11:33:55 2017 +0100
Merge commits 'refs/replace/00029d7b3e531215f6ce5afb32862b49d652e896', 'refs/replace/03d715c9890e5cec95ac62d1c9ecc54cb78b9f62', '

git 声称有几个文件被更改,尽管这些不是最近更改的文件。我怀疑它们是两个旧分支之间的区别。

合并/拉取后,我们在 .git/refs/replace 中有替换。

我的同事的报告称,推动的更改包括复制他需要的 2 个缺失的分支关系的移植。这些根本不会出现在 .git/refs/replace 中。

如果它们以某种方式与之前推送的内容混在一起,还有其他一些问题:

他怎么能在不先解决合并冲突的情况下进行推送?

此外,如果您从存储库克隆而不拉取替换,他添加的关系仍然会被拉取。有两个明显的提交,提交消息如下:

Former-commit-id: bc735afc1d8bb842733cb94767afb8b42599eb6a

但是没有描述替换的 .git/refs/replace 目录。

我的同事如何能够将他的移植物作为对回购协议的永久更改,而之前的代码不是自动提取的? 这样根本就没有 .git/refs/replace 目录?

有人可以告诉我这里可能发生什么吗?

还有什么我可以在不重写历史的情况下使其他分支关系永久化吗?我的同事似乎已经这样做了,但我不明白是怎么做到的。

已解决

总结答案:

在 git 中,push 的反面是 fetch 而不是 pull

我的同事 push 不会有合并问题,因为没有合并问题。它是我使用 'pull' 而不是 'fetch' 的产物。

感谢您的帮助。

【问题讨论】:

    标签: git


    【解决方案1】:

    标题问题(“为什么替换需要合并提交?”)的答案是:不会。

    这里有一条使用 Git 的重要规则:永远不要使用git pull。 :-)

    (一旦您精通 Git,您就可以放宽这条规则,只有在您确切知道将要发生的事情时才使用git pull。)

    在这种特殊情况下,您应该运行:

    git fetch origin 'refs/replace/*:refs/replace/*'
    

    然后停在那个点。

    如果您打算继续使用替换,您可能希望将其添加到每个遥控器的fetch = 行集中。 (请注意,一旦你开始使用替换,你往往会被它们卡住,所以这可能是一个合理的做法。另一个选项,如Mark Adelsberger's answer,是连接替换,例如,使用 no- op git filter-branch。)这个“添加fetch设置”必须在每个git clone之后手动完成,因为正常的克隆不会获取替换名称空间名称。 (这也与您之前提出的镜像克隆问题有关:镜像克隆 会盲目地获取 all refs/* 引用,其中包括替换名称空间。)

    说明

    git pull 命令是一种方便快捷的方式。它首先运行git fetch,这是从其他存储库获取提交和其他项目的实际操作。然后它运行另一个 Git 命令。

    大多数情况下,在从另一个 Git 存储库获取项目(例如提交)后,您必须自己采取一些措施来使用这些项目。获取它们只是将它们放入您的存储库中,给它们某种参考名称——通常是一个远程跟踪分支名称,例如refs/remotes/origin/somebranch

    要采取的行动通常是git rebase,有时是git mergegit pull 命令将为您运行 git merge,即采取错误的操作,除非您告诉它为您运行 git rebase。当然,这可能仍然是错误的行为——在这种情况下,它

    您带来的引用位于 refs/replace/ 命名空间,而不是 refs/heads/ 命名空间(包含分支名称)而不是 refs/tags/ 命名空间(包含标记名称)。 refs/replace/ 命名空间包含替换项

    与正常使用分支名称相比,替换和标签有一个奇怪的特性:no 操作是使用它们所必需的。有了分支名称,当您从 Monique 的存储库中获得 refs/heads/master 时,您重命名它为 refs/remotes/monique/master,这样您就可以保留 您的 master——一个分支,在refs/heads/ — 与 master 分开,您仅将其保留为远程跟踪分支。然后,您将采取一些部分操作(合并或变基)将她的工作合并到您的工作中。

    但是,对于标签名称,您可以使用 Monique 的 refs/tags/v2.3 并将其称为您的 refs/tags/v2.3。现在你们共享标签v2.3,并且不需要第二个操作:您的Git 会在您自己的refs/tags/ 命名空间中为您查找标签名称v2.3

    替换也一样。在 Git 中,替换对象由具有非常特殊模式的 refs/replace/ 命名空间名称表示:而不是 refs/replace/master 或类似名称,我们会找到像 refs/replace/b06d3643105c8758ed019125a4399cb7efdcce2c 这样的名称。该名称映射到 替换对象 本身。也就是说,那个又大又长的名字映射到另一个又长又丑的 Git 哈希 ID,Git 可以使用它来访问 不同的 Git 对象。

    为了便于记忆,我将在下面使用blah 而不是b06d3643105c8758ed019125a4399cb7efdcce2c。所以refs/replace/blah 映射到另一个ID,我们可以称之为bazinga

    当您的 Git 即将对哈希 ID 为 blah 的内部对象执行操作时,它会注意到有一个 refs/replace/blah。然后,它不使用普通的blah 对象,而是查找bazinga 对象,并改用那个对象。 (使用--no-replace-objects,Git 会跳过这个“检查refs/replace/”步骤。)

    这就是替换的工作原理,因此,当您使用它们时,您应该获取它们并停止。

    如果您查看您的.git/config 文件,您将看到以下表格的一些行:

    [remote "origin"]
        url = ...
        fetch = +refs/heads/*:refs/remotes/origin/*
    

    在表格中添加一行(保留原来的fetch =):

        fetch = +refs/replace/*:refs/replace/*
    

    将使每个git fetch origin 自动拾取任何新的替换对象。有关此的更多信息,请参阅我对What is the difference between these `git fetch` syntaxes?的回答

    【讨论】:

    • 正如你所说,如果我使用 git fetch 而不是 git pull “它就可以工作”。随后的 git merge 说“已经是最新的”。那么在调用合并之前 git pull 在做什么(或不是)?这是某种竞赛,因为尚未应用替代品吗?我天真的期望是,如果什么都不需要做,它就什么也不做。显然 git pull 认为需要做点什么。
    • 这根本不是一场比赛:这是因为 git pull 专门与您专门提取的任何哈希 ID 合并,即使这样做没有意义。一个单独的git merge 步骤做了一些不同的事情:它查看当前分支的 upstream 设置,并将其用作合并的哈希 ID。 (这与 Git 的历史有关,git pull 实际上早于远程跟踪分支的想法。结果证明这是一个错误——或者至少经验不足——但git pull 仍然表现在一种向后兼容的方式。)
    【解决方案2】:

    我认为 99% 的混淆是因为您使用 pull 而不是 fetch 来复制替换代表。我希望这是试图章鱼将所有替换引用合并到您当前的分支中,这当然是相当荒谬的。

    对提交谱系进行永久性更改的方法是重写历史记录,从长远来看,您可能应该继续这样做,而不是过于依赖替换机制。显然,这需要团队的协调,但这是一次性成本,从那时起,回购将更加无缝地工作。

    【讨论】:

    • 你是对的,但我不明白为什么没有 Torek 的回答帮助。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-16
    • 1970-01-01
    • 1970-01-01
    • 2020-09-17
    • 2015-06-11
    相关资源
    最近更新 更多