行结尾:Git合并创建重复项而不会发生冲突

时间:2016-04-27 12:31:44

标签: git merge newline core.autocrlf

Git自动合并问题:

当在两个不同的分支文件中提交相同的代码时,其中一个分支代码在启动时具有额外的CRLF / LF。合并它时自动合并文件会创建重复项而不会产生任何冲突。请最早通知。

下图显示了文本文件中的所有可能符号。注意:分支A没有换行(行:245)。下面的自动合并会产生重复,而不会显示冲突。

Image describing Auto Merge issue

1 个答案:

答案 0 :(得分:2)

(注意:行结尾不是罪魁祸首。)

这个案子很有意思。问题似乎是,无论如何,在git的算法中,两组添加的行在两个不同的地方添加。 Git不了解代码,只是决定由于两个(稍有不同)的更改在原始的不同部分添加了行,所以只需添加这两个不同的更改即可。

你应该从中汲取的教训之一就是git 不聪明。它只是遵循通常工作的一堆简单规则,但只是因为它认为它成功合并了两组差异,并不意味着结果是正确的。这例如,自动化测试是个好主意的又一个原因。

注意:通过脚本完成重新创建问题的步骤显示在此答案的底部。

在我们要求git合并之前,让我们看一下BranchA和BranchB的状态。当我们将合并基础(在此特定设置中的分支git diff的尖端)与两个实际的提示提交进行比较时,关键部分是我们使用common得到的结果。要查看这些差异,我使用git diff

的三点形式
$ git diff BranchB...BranchA
diff --git a/demo-file b/demo-file
index 1d822d4..d222dc7 100644
--- a/demo-file
+++ b/demo-file
@@ -11,6 +11,8 @@ Note that CR-LF is not an issue
             get { return valueForKey<int?>("realPortNum") ; }
             set { takeValueForKey("realPortNum", value); }
         }
+        // ADSO-3530
+        public Decimal? tradeItemDryPhyWetQty

         public string riskMktCode
         {
$ git diff BranchA...BranchB
diff --git a/demo-file b/demo-file
index 1d822d4..52802fa 100644
--- a/demo-file
+++ b/demo-file
@@ -12,6 +12,9 @@ Note that CR-LF is not an issue
             set { takeValueForKey("realPortNum", value); }
         }

+        //ADSO-3530
+        public Decimal? tradeItemDryPhyWetQty
+
         public string riskMktCode
         {
             get { return valueForKey<string>("riskMktCode") ; }
$ 

第一个命令git diff BranchB...BranchA告诉git:

  1. 查找BranchBBranchA标识的提交。 (这些是BranchBBranchA上最常见的两个提交。在这种情况下,它们也是提交在那两个尚未在common branch,因为我们只在BranchA上提交了一个提交,而在BranchB上只提交了一个提交。在许多实际情况中,一个分支上可能有10个,20个或更多提交,2个另一方提交,5,甚至50或更多提交,但是git只是找到了这两步提交的两个提交。)

  2. 为这两个提交找到合并基础。合并基地是两个分支在历史中重新加入的地方。在这种情况下,合并基础非常明显:它是分支common顶端的提交,这是两个分支BranchABranchB作为单独的分支出现的位置。 common提示处的提交位于所有三个分支(以及任何其他分支,例如默认的master分支)。

  3. 将合并基础与第二次提交区分开来,即BranchA的提示。

  4. 第二个命令git diff BranchA...BranchB的工作方式非常相似。唯一的变化是以另一个顺序选择两个输入提交。 Git找到相同的合并基础,但现在针对BranchB的提示提交区分该提交。

    再看一下上面引用的差异。 有两个不同的差异结果

    第一个差异显示git应该修改一个以第11行的上下文开头的块。上面有三个&#34;&#34;上下文行(第11,12和13行,这些是get,set和close括号行),然后我们添加了注释和函数声明行,然后有三行&#34;下面&#34;上下文。

    第二个差异显示git应该在第12行(不是第11行)开始的块中添加三行文本。三个&#34;以上&#34;上下文行是set,close-brace和空行,并且这些行本身不会被第一个diff更改(尽管它们将在之间插入一些文本)。然后我们添加了三行(注释,函数声明和空行),然后我们有了尾随上下文。

    请注意,git已经确定我们新添加的初始空白行已经存在,而我们添加了一个后续空行,我们的添加发生在第14行,而不是第13行。这解释为什么这两个添加不冲突:就git而言,BranchA更改是&#34;在第11 + 3行和第34行添加两行。并且BranchB更改为&#34;在旧行12 + 3处添加三行(现在添加两行后现在为行14 + 3)&#34;。

    结果是git 添加了两个文本块,即使它们非常相似。

    重现问题的脚本如下。

    #! /bin/sh
    
    tdir=/tmp/mergetest
    
    die() {
        echo "fatal: $@" 1>&2
        exit 1
    }
    
    set -e
    [ -d $tdir ] && die "$tdir: already exists -- hint: rm -rf $tdir"
    
    mkdir $tdir
    cd $tdir
    git init
    echo "This repository is for demonstrating git merge." > README
    git add README
    git commit -m initial
    
    # Create common file on common branch.
    git checkout -b common
    cat << END > demo-file
    This is a demo file,
    meant to illustrate how git merge works,
    why git merge is not very bright,
    and why it is therefore necessary to INSPECT THE MERGE RESULTS
    (automated tests are good).
    The next few lines are not line 241 through 250 here,
    but do match the original sample input.
    Note that CR-LF is not an issue
    (this host Unix-ish system uses simple newlines).
            {
                get { return valueForKey<int?>("realPortNum") ; }
                set { takeValueForKey("realPortNum", value); }
            }
    
            public string riskMktCode
            {
                get { return valueForKey<string>("riskMktCode") ; }
                set { 
                    takeValueForKey("riskMktCode", value);
    Finally, we have some
    trailing text so as to provide
    plenty of context area for git,
    when it is doing its comparisons of the
    merge-base version of the file
    against the two branch versions.
    END
    git add demo-file
    git commit -m 'create common base'
    
    # Set variable to two-line form that we will add to both files.
    samepart="        // ADSO-3530
            public Decimal? tradeItemDryPhyWetQty"
    
    # Make version on BranchA with two added lines.
    git checkout -b BranchA
    ed - demo-file << END
    13a
    $samepart
    .
    w
    q
    END
    git add demo-file
    git commit -m 'branch A: add declaration for tradeItemDryPhyWetQty'
    
    # Make alternate version on BranchB with three added lines;
    # note that we start from the common base.
    git checkout -b BranchB common
    ed - demo-file << END
    13a
    
    $samepart
    .
    w
    q
    END
    git add demo-file
    git commit -m 'branch B: add declaration for tradeItemDryPhyWetQty'
    
    # Show which commit is the merge-base.
    mergebase=$(git merge-base BranchA BranchB)
    echo "The merge base is commit $(git rev-parse --short $mergebase)".
    
    # View diffs.  Could use "git diff $mergebase BranchA" here.
    echo "Here is what we added in BranchA, vs the common base:"
    git diff BranchB...BranchA
    
    # Could use "git diff $mergebase BranchB" here.
    echo "And, here is what we added in BranchB, vs the common base:"
    git diff BranchA...BranchB
    
    echo "Now we merge the two (on BranchA in this case)"
    git checkout BranchA
    git merge --no-edit BranchB
    
    echo "Comparing the result to the merge base, we get:"
    git diff $mergebase HEAD