【问题标题】:Unexpected behaviour Git applying patch (cherry-pick): not only the commit was takenGit应用补丁(cherry-pick)的意外行为:不仅提交了
【发布时间】:2020-04-01 16:38:08
【问题描述】:

我一直在使用cherry-pick,所以我应该对这句话很有信心:

  • cherry-pick 只接受在樱桃挑选提交中所做的更改。停下来。

但碰巧在通常的合并中,它还从另一个提交中进行了不需要的更改。

我简化了我的用例:

  • 我在 master 之上挑选 tocherry 提交,避免 tonotcherry 更改
user@laptop:(master)test$ git log --graph  --oneline --abbrev-commit --decorate --all
* 6fee67e (HEAD -> master) unused
| * 7c9de69 (feature) tocherry
| * fffe063 tonotcherry
|/
* 970888c init
user@laptop:(master)test$ git show fffe063
commit fffe063be9cd11c0fb8406a8e8806bfd2ea71d74

    tonotcherry

diff --git a/Class.cs b/Class.cs
index 8fa6580..ae5f69e 100644
--- a/Class.cs
+++ b/Class.cs
@@ -5,12 +5,14 @@ namespace ciao
        public class Test
        {
                private string attribute1;
+               private string attribute2;

                public Test Test(
-                               string _attribute1
-               )
+                               string _attribute1,
+                               string _attribute2)
                {
                        this.attribute1 = _attribute1;
+                       this.attribute2 = _attribute2;
                }
        }
 }
user@laptop:(master)test$ git log --graph  --oneline --abbrev-commit --decorate --all
* 6fee67e (HEAD -> master) unused
| * 7c9de69 (feature) tocherry
| * fffe063 tonotcherry
|/
* 970888c init
user@laptop:(master)test$ git show fffe063
commit fffe063be9cd11c0fb8406a8e8806bfd2ea71d74

    tonotcherry

diff --git a/Class.cs b/Class.cs
index 8fa6580..ae5f69e 100644
--- a/Class.cs
+++ b/Class.cs
@@ -5,12 +5,14 @@ namespace ciao
        public class Test
        {
                private string attribute1;
+               private string attribute2;

                public Test Test(
-                               string _attribute1
-               )
+                               string _attribute1,
+                               string _attribute2)
                {
                        this.attribute1 = _attribute1;
+                       this.attribute2 = _attribute2;
                }
        }
 }
user@laptop:(master)test$ git show 7c9de69
commit 7c9de69d8461642867b7c5b1395b11abbf918b96 (feature)

    tocherry

diff --git a/Class.cs b/Class.cs
index ae5f69e..f5f88ff 100644
--- a/Class.cs
+++ b/Class.cs
@@ -6,13 +6,19 @@ namespace ciao
        {
                private string attribute1;
                private string attribute2;
+               private string attribute3;
+               private string attribute4;

                public Test Test(
                                string _attribute1,
-                               string _attribute2)
+                               string _attribute2,
+                               string _attribute3,
+                               string _attribute4)
                {
                        this.attribute1 = _attribute1;
                        this.attribute2 = _attribute2;
+                       this.attribute3 = _attribute3;
+                       this.attribute4 = _attribute4;
                }
        }
 }
user@laptop:(master)test$ git show 6fee67e
commit 6fee67e42b675de94b385bca461336326a36c46f (HEAD -> master)

    unused

diff --git a/Class.cs b/Class.cs
index 8fa6580..c189487 100644
--- a/Class.cs
+++ b/Class.cs
@@ -12,5 +12,9 @@ namespace ciao
                {
                        this.attribute1 = _attribute1;
                }
+
+               public string someFunction() {
+                       return "";
+               }
        }
 }
 user@laptop:(master)test$ git cherry-pick -x 7c9de69d8461642867b7c5b1395b11abbf918b96
error: could not apply 7c9de69... tocherry
hint: after resolving the conflicts, mark the corrected paths
hint: with 'git add <paths>' or 'git rm <paths>'
hint: and commit the result with 'git commit'
user@laptop:(master)test$ cat Class.cs
using System;

namespace ciao
{
    public class Test
    {
        private string attribute1;
<<<<<<< HEAD

        public Test Test(
                string _attribute1
        )
        {
            this.attribute1 = _attribute1;
        }

        public string someFunction() {
            return "";
=======
        private string attribute2;
        private string attribute3;
        private string attribute4;

        public Test Test(
                string _attribute1,
                string _attribute2,
                string _attribute3,
                string _attribute4)
        {
            this.attribute1 = _attribute1;
            this.attribute2 = _attribute2;
            this.attribute3 = _attribute3;
            this.attribute4 = _attribute4;
>>>>>>> 7c9de69... tocherry
        }
    }
}

除了构造函数中的输入参数,逗号和括号不明确,我应该在cherry-pick提交期间看不到attribute2,因为那只是fffe063的一部分。

这与 git 如何存储差异和应用合并有关吗?我怎么能预测呢?有什么办法可以避免这种行为?

提前致谢

【问题讨论】:

    标签: git


    【解决方案1】:

    这是否与 git 如何存储差异有关

    Git 不会存储差异(相反,它会在您每次运行 git diffgit showgit log -p计算它们),但事实并非如此这里很重要:

    并应用合并?

    这是重要的部分。

    虽然git cherry-pick 通常被描述为“应用补丁”,但这并不是它真正的作用。这是它真正做的事情的简写。它确实执行了三向合并。

    如果合并顺利,Git 会自动提交结果。这个最终提交是普通提交,而不是合并提交。但是,如果合并没有顺利进行,您会看到中间合并。

    我该如何预测?

    可以预测的,但是只执行cherry-pick操作要容易得多。如果您想进行自己的预测,我们稍后会看到。

    有没有办法避免这种行为?

    不是真的。您应该只修复冲突并继续 rebase 操作以完成它。

    您可能希望将merge.conflictStyle 设置为diff3:我发现这些冲突更具可读性。见下文。

    三路合并

    问题的核心是cherry-pick 需要应用可能实际上并不适用的更改。为了让这项工作更好——但不是 100%——Git 使用了三向合并代码。此合并的 合并基础 是被挑选的提交的父级。 当前提交,或HEAD,是您签出的提交——在本例中,6fee67emaster 的提示。您正在挑选的提交是此三向合并中的第三次提交。

    在更典型的合并中,我们的情况如下所示:

              o--o--L   <-- your-branch (HEAD)
             /
    ...--o--B
             \
              o--o--R   <-- their-branch
    

    三个有趣的提交,它们是合并操作的输入,是 merge base 提交 B(及其快照),当前提交 L(对于左或本地)在你自己分支的顶端,并在他们的分支顶端提交R(对于Right或Remote或其他)。

    这种合并会:

    • 比较 BL 看看 发生了什么变化;和
    • 比较 BR 以了解他们发生了什么变化。

    Git 然后从B 中提取所有文件,将您在B-vs-L 中的工作与他们在B-vs-R 中的工作结合起来,然后应用合并对来自B的文件的更改。

    如果一切顺利,Git 会进行新的合并提交,这将成为您分支的尖端:

              o--o--L
             /       \
    ...--o--B         M   <-- your-branch (HEAD)
             \       /
              o--o--R   <-- their-branch
    

    新提交M 有父LR(两者)以及通过将两个分支提示的组合更改应用到提交B 中看到的文件而生成的快照。这样您就可以应用两组更改。

    不过,我们的图片看起来更像这样:

              o--o--L   <-- your-branch (HEAD)
             /
    ...--o--*
             \
              o--B--R--o--...--o   <-- their-branch
    

    (在您的特定情况下,他们的分支名称直接指向提交R)。虽然提交 * 是您的分支及其分支的实际合并基础,但它不是git cherry-pick 用作合并基础的提交。

    相反,Git 使用 R 的父级(在此图中提交 B)作为合并基础。因此,它再次比较 B-vs-L 以查看 发生了什么变化,并比较 B-vs-R 以查看 他们 发生了什么变化.如果这些更改发生冲突(就像您的情况一样),合并将在中间停止。

    如果一切顺利——你的情况并非如此——Git 会像往常一样将 combined 更改应用于B 中的快照。这样就可以保留BL 之间的差异,同时增加BR 之间的差异。换句话说,它将应用从提交 B 到提交 R 所需的补丁。

    预测是否会发生冲突

    要进行此预测,您可以自行比较 B-vs-LB-vs-R。首先,获取您计划挑选的提交的父哈希 ID。在这种情况下,它是fffe063。您可以根据需要剪切和粘贴它,或者将其存储在变量中:

    $ B=fffe063
    

    然后,在提交 B 和提交 L 时运行 git diff --find-renames。您可以使用名称 HEAD 轻松命名提交 L

    $ git diff --find-renames $B HEAD
    

    无论此处显示什么,请记下原始文件中每个更改的源代码行。

    然后,在提交 B 时运行 git diff --find-renames 并提交 R

    $ git diff --find-renames $B 7c9de69
    

    无论此处显示什么,请记下原始文件中每个更改的源代码行。

    如果两组更改触及 same 文件的 same 行,则会发生冲突。如果两组更改总是触及 不同的文件,或 不同的,足够远的1 行,则不会有冲突:Git将能够合并更改。


    1“足够远”基本上是指变化既不重叠,也不头尾相触。如果两个更改都插入文件的开头或结尾,它们将重叠。这些 head/tail 或 both-at-same-end-of-file“接触”案例的英文单词是 abut;参见,例如,https://www.dictionary.com/browse/abut


    如何解读冲突

    &lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD======= 之间的行来自提交 B 和您当前 (HEAD) 提交 L 之间的差异。您可能希望保留这些更改。

    =======&gt;&gt;&gt;&gt;&gt;&gt;&gt; ... 之间的行来自提交 B 和他们的提交 R 之间的差异——在这种情况下,你正在挑选的那个。您可能也希望保留这些更改!

    您可以合理地结合这两组更改。查看此文件的合并基础版本中的内容通常非常有帮助 - 即,在您的情况下,提交 R 之前的文件中的内容。但是 Git 默认不会显示这些行。默认为merge 使用merge.conflictStyle

    如果您将 merge.conflictStyle 设置为 diff3,Git 会添加 第三 组行,以 ||||||| 作为标记。这第三组行是提交 B 中的原始行。因此,您可以将它们与 &lt;&lt;&lt;&lt;&lt;&lt;&lt;&gt;&gt;&gt;&gt;&gt;&gt;&gt; 部分中的提交 LR 进行比较。

    您可以改用合并工具

    如果您不喜欢这些方法,可以使用git mergetool。我自己不使用这些各种合并工具,只能给出非常笼统的指导。搜索有关使用各种合并工具的现有 StackOverflow 帖子。

    【讨论】:

      【解决方案2】:

      这里存在冲突,因为您选择的提交 7c9de69 与您的主分支有不同的起点(提交时 6fee67e)。这是因为提交 fffe063 修改了同一段代码,所以 git 要求您解决这个问题,因为它无法决定应该如何完成......

      user@laptop:(master)test$ git show 7c9de69
      commit 7c9de69d8461642867b7c5b1395b11abbf918b96 (feature)
      
          tocherry
      
      diff --git a/Class.cs b/Class.cs
      index ae5f69e..f5f88ff 100644
      --- a/Class.cs
      +++ b/Class.cs
      @@ -6,13 +6,19 @@ namespace ciao
              {
                      private string attribute1;
                      private string attribute2;
      +               private string attribute3;
      +               private string attribute4;
      
                      public Test Test(
                                      string _attribute1,
      -                               string _attribute2)   <----- NOT POSSIBLE
      +                               string _attribute2,
      +                               string _attribute3,
      +                               string _attribute4)
                      {
                              this.attribute1 = _attribute1;
                              this.attribute2 = _attribute2;
      +                       this.attribute3 = _attribute3;
      +                       this.attribute4 = _attribute4;
                      }
              }
       }
      

      【讨论】:

      • 我明白了,但我希望樱桃采摘不会考虑 fffe063 或者起点:我虽然合并是在实际文件和“差异”(如 + 或 - ) 的7c9de69 。我还发现我误解了 git 应用补丁的方式。您知道任何解释该过程的资源吗?
      • @RiccardoBucchi git show 7c9de69 commit 说它需要删除这一行:string _attribute2) 不存在,因为fffe063 没有发生,所以 git 无法解决这个问题。这显示在您在问题中显示的 git show 命令中。这只是一个合并冲突,仅此而已:)
      • 更新了我的帖子以突出提交中不可能的部分
      • 你突出显示的行我预计会发生冲突,我在说 ``` this.attribute1 = _attribute1; this.attribute2 = _attribute2;
      • 都是同一段代码,所以会出现同样的冲突
      猜你喜欢
      • 2011-07-06
      • 2021-01-18
      • 2016-01-08
      • 2012-11-05
      • 1970-01-01
      • 2018-04-17
      • 2012-05-21
      • 2013-03-26
      • 2016-10-24
      相关资源
      最近更新 更多