【问题标题】:Line Endings: Git merge creates duplicates without conflict行尾:Git 合并创建没有冲突的重复项
【发布时间】:2016-08-21 18:09:34
【问题描述】:

Git 自动合并问题:

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

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

【问题讨论】:

  • 我们在这里遗漏了一条关键信息:这个文件的原始合并基础版本中有什么?请参阅stackoverflow.com/q/22054478/1256452(问题和答案)。
  • @torek,我现在更新了信息。
  • 好的。让我感到困惑的另一件事是您说“分支 A 没有换行(行:245)”,但您在此处的列表显示“在分支 A 上提交”,并在第 245 行包含 CR-LF。也许您的意思是“没有前导空行”(由的 CR-LF 组成的行)?无论如何,我会尝试将此作为演示的基础。

标签: git merge newline core.autocrlf


【解决方案1】:

(注意:行尾不是这里的罪魁祸首。)

这个案例很有趣。问题似乎在于,无论如何,在 git 的算法中,这两组添加的行是在两个不同的地方添加的。 Git 对代码一无所知,只是简单地决定,由于这两个(有些不同的)更改将行添加到原始的不同部分,因此只需添加这两个不同的更改即可。

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

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

让我们看看在我们要求 git 合并之前 BranchA 和 BranchB 的状态。关键部分是当我们将合并基础(在此特定设置中的分支 common 的尖端)与两个实际的尖端提交进行比较时,我们得到的 git diff。为了查看这些差异,我使用了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 上的两个最尖端的提交。在这种情况下,它们也是这两个分支上尚未在公共分支上的 only 提交,因为我们只在BranchA 上进行了一次提交,在BranchB 上进行了一次提交。在许多实际情况下,一个分支上可能有 10、20 或更多次提交,以及 2、5 甚至 50 次或更多另一个提交,但 git 只为这一步找到两个最尖端的提交。)

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

  3. 将合并基础与 second 提交进行比较,即BranchA 的提示。

第二个命令git diff BranchA...BranchB 的工作原理非常相似。唯一的变化是两个输入提交是以另一个顺序选择的。 Git 找到相同的合并基础,但现在将提交与 BranchB 的提示提交进行比较。

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

第一个差异表明 git 应该在第 11 行修改以上下文开头的块。上下文有三行“高于”行(第 11、12 和 13 行,它们是 get、set 和右大括号行) ,然后我们添加了注释和函数声明行,然后是三行“下面”上下文。

第二个差异表明 git 应该在第 12 行(而不是第 11 行)开始的块中添加三行文本。上下文的三个“上方”行是集合、大括号和空白行,这些行本身不会被第一个差异更改(尽管它们会在 之间插入一些文本他们)。然后我们添加了三行(注释、函数声明和空白行),然后我们有了尾随上下文。

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

结果是 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

【讨论】:

  • 感谢 torek,解释中有非常翔实的细节。是否可以将此显示为冲突而不自动合并代码?
  • 目前至少没有,没有。
  • 我们观察到在BRanchB commit中,如果我们将CRLF替换为隐藏字符(例如,TAB+CRLF),git不会直接自动合并,但表明它没有冲突,需要解决。如果我们使用带有隐藏字符的 CRLF,听起来会有办法。
  • 使用我编写的重现问题的脚本,总共尝试三件事: (1) 将您刚刚建议的 TAB 字符添加到对 BranchB 所做的更改。 (2) 除了增加那个TAB字符外,在原来的通用代码中包含一个TAB,即将public string riskMktCode之前的现有空行改为由一个TAB组成的行。 (3) 将对 BranchB 所做的更改恢复为仅添加一个空行。做这三个练习的结果应该证明是有益的。
猜你喜欢
  • 1970-01-01
  • 2011-12-16
  • 2020-04-18
  • 2012-10-29
  • 2022-12-18
  • 2017-07-24
  • 1970-01-01
  • 2017-04-28
  • 1970-01-01
相关资源
最近更新 更多