如何在合并期间覆盖基础?

时间:2016-11-30 12:49:03

标签: git merge cherry-pick

问题

查看图片。我有一个master分支。在提交A时,我从中分支dev分支。在B点,我与dev同步了master,创建了M1。在C点,我们的团队从中分支release分支。将来,我需要将dev合并回release

不幸的是,我意外地将D的提交master合并到我的dev分支,创建了M2。现在我无法将dev合并到release,因为它包含属于C..D的提交master,不应转到release

我的开发还没有结束,我现在不打算将dev合并到release。但是,我希望保持同步并将release合并到我的dev分支。在完成开发并将dev合并到release之前,我希望此类合并将在未来多次发生。

因此,在某些时候我需要从M2恢复dev。我希望尽早完成,因为release的提交可能会与M2中的更改冲突。请记住,release中不存在其中一些更改。

由于我想尽早恢复M2,我希望在从release合并到dev之前执行此操作。这就是问题实际开始的地方。感谢您阅读这一点:)

Problem

我可以在M2中恢复dev,这不是问题。但是,在此之后,当我将release合并到dev时,git将合并库计算为C。虽然我希望合并基数为B,但假装合并M2根本就没有发生过。由于这个不正确的基础,合并实际上会自动丢弃更改B..C。 Git认为,我手动删除了它们,因为这是他在恢复提交M2时看到的内容!

让我澄清一下。想象一下有人在foo.txt中创建了文件C。此文件将dev添加到M2。在我恢复dev后,它将从M2移除。当我将release合并到dev时,git会在foo.txt中看到release,它会在基本提交foo.txt中看到C,但它看不到foo.txt中的dev。因此git认为我删除了foo.txt。但我没有这样做。

如果我将B指定为合并库,则没有问题。有没有办法在git中做到这一点?

我的解决方案

由于我发现无法覆盖合并库,我做了一点点黑客攻击。我在这里发布是因为我有另一个相关的问题。

见第二张图片。我从tmp开始了新的本地分支E,在M2之前提交。除了dev之外,我对M2的所有更改都选择了这个分支。我将release合并到tmp,并将结果M3仅与tmp中的一位父项合并(请注意图片上的虚线)。

我在M2中还原dev,提交G。我创建了从releasedev的“假”合并。此提交不包含任何更改,但有两个父级:来自devrelease。然后,我将M3挑选到dev并用空虚假合并压扁它。因此,我创建了M4,正确的更改和正确的父母。

enter image description here

问题是:我真的需要这种“假”合并吗?也许有办法挑选M3dev并让它有两个父母?

问题

我将在这里总结一些问题:

  • 有没有办法在合并期间手动设置基础?
  • 有没有办法为提交手动指定父项?

如果您能够阅读本文,请感谢您!

更新

我在讨论后意识到,我的解决方案(或所述问题的任何其他解决方案)都存在严重缺陷。正如我所说,在某些时候我会将dev合并到release。正如我没有说过的那样,在此之后的某个时刻我们会将release合并到master。在这一点上,我们将面临完全相同的问题。此合并的基础将解析为D,而不是C。这将导致类似的问题,但规模要大得多。

因此,此问题的最佳解决方案是继续tmp中的开发,并将其设为新dev,从历史记录中有效排除错误合并M2

3 个答案:

答案 0 :(得分:1)

我很确定你的底线问题的简短答案是否定的:

  • 你绝对不能手动设置提交的基础,无论如何,这没有任何意义。 GIT如何知道将“我们”分支中的现有提交与另一个分支中的提交相关联? (例如,假设它在C之后出现在另一个分支上,只是为了使其变硬)。
  • 使用git reset这是可能的,尽管这是一种解决方法。你git reset <wanted base>。您当前提交和新基础之间的所有更改都在工作区中未提交,因此您可以进行所需的新提交(尽管您丢失了一些您可能想要的树信息)。

在任何情况下,您都可以围绕M1进行dev分支以摆脱它:

git checkout dev
git rebase -i <commit prior to M1 or even to A>

-i将允许删除合并提交。

修改

你的变通方法在dev中的最终提交可能看起来像你想要的,但你必须记住在git中,如果下面有一行连接到你,那么这些提交也是你的一部分。您所做的是精心重新定义以删除M2,因此在任何父级中都不会考虑提交C..D。

如果你可以告诉git只是将基于B的dev合并到release,那么以下我认为可以很好地估计会发生什么:

  1. GIT认为B..C是共享的,只是跳过它们。
  2. M1和M2之间的添加合并到dev
  3. 这里还有M2!所以将C..D合并到dev。你从B开始没关系,你会来到这里!
  4. 继续休息。
  5. 如果层次结构中存在M2,则无法绕过C..D。但是,你可以通过还原或重新定位来跳过它,或者做那个疯狂的分支绕过你做的。

答案 1 :(得分:1)

简短的回答是“不,你不能选择自己的合并基础”,你的临时分支方法是正确的。现在让我们来看第二个问题:

  

有没有办法为提交手动指定父项?

这里的答案是响亮的,但你必须使用Git的“管道工具”来完成它,特别是git commit-tree

git commit创建新提交时,它会执行以下操作(当然,在收集提交消息之后等等):

  1. 将索引写入树:git write-tree。这将打印出新树的哈希ID。
  2. 使用结果树创建新提交:git commit-tree args(见下文)。这会打印出新提交的哈希ID。
  3. 更新当前分支以指向新提交:git update-ref -m "commit: subject" HEAD commit-hash
  4. 步骤2是我们可以选择父母进行提交的地方。 commit-tree命令接受一个必需参数,即树ID(由步骤1生成),以及每个父ID设置一个-p参数。每个-p都采用提交父指定符。这可以是原始哈希,也可以是gitrevisions可接受的任何内容。实际上,树ID也可以这种方式编写,如果您只想使用相同的源树创建一个 new 提交,这很方便。

    因此,假设您在分支X下有一个当前源树,指向提交 CX 。您现在希望将X合并到分支dest中,但是您希望合并的行为就像 CX 的父提交是提交 P 一样,以便git将使用git merge-base --all P dest找到合并基础。 (如果您已经知道所需的合并基础 B ,则只需选择 P = B 。)

    我们需要的是一个提交,其树与 CX 相同,但其(单个)父级是 P 。这个提交甚至根本不必在任何分支上,它只需要存在于存储库中。如果您希望它在分支上,请使用配方中的git branch

    X=dev                                        # name of desired branch
    P=<hash ID for commit P>
    git show $P                                  # just to check
    cat > /tmp/msg << END
    temp commit of $X's tree from $(git rev-parse $X)
    
    This is a special commit made with parent $P
    just for doing a merge.
    END
    newcommit=$(git commit-tree -p $P $X^{tree}) < /tmp/msg
    # git branch tmp $newcommit                  # save newcommit as new branch
    git checkout dest
    git merge $newcommit                         # or git merge tmp
    

    (以上都是未经测试的)。

    在这里使用$X^{tree},不需要做很多复杂的挑选。当然,如果你需要省略特定的提交来构建一个新的(不同的)树,你真的需要一个临时分支并做一些挑选。

答案 2 :(得分:0)

我认为您可以在分支dev中执行D并从中移除M2$ git checkout dev $ git rebase -i <commit-sha-of-E>

D

将出现一个新窗口。现在只需删除M2$ git rebase --continue # finish your rebase. 的提交行,然后保存并退出。

        var xdoc = XDocument.Load(theXmlURLpath);

        var testElements = xdoc.Root.Element("tests").Elements("test");

        foreach (var testElement in testElements)
        {
            var lang = testElement.Attribute("language").Value; // en / it / hu / es
            var text = testElement.Value; // hello or ciao ...
            switch (lang)
            {
                case "nl":
                    // do something
                    break;
                case "d":
                    // do something
                    break;
                case "gb":
                    // do something
                    break;
                case "fr":
                    // do something
                    break;
                case "esp":
                    // do something
                    break;
                case "it":
                    // do something
                    break;
            }
        }