您的担心是对的:Git不懂任何语言,它的内置合并算法严格基于时线比较。 您不必使用此内置的合并算法
,但是大多数人都可以这样做,因为(a)它大部分都可以工作,并且(b)没有太多选择。
注意,这取决于您的 合并策略 (
-s参数);以下文字是默认
recursive策略。该
resolve策略与
recursive;
该
octopus策略不仅适用于两次提交;而
ours策略则完全不同(并且与完全不同
-Xours)。您还可以使用
.gitattributes和“合并驱动程序”
为特定文件选择替代策略或算法。而且,这都不适用于Git决定认为是“二进制”的文件:对于这些文件,它甚至不尝试合并。(我不会在这里讨论任何内容,而是默认
recursive策略如何处理文件。)如何
git merge工作(使用默认时
-s recursive)
- 合并以两次提交开始:当前一次(也称为“我们的”,“本地”和
HEAD
),以及一些“其他”一次(也称为“其”和“远程”) - 合并查找这些提交之间 的 合并基础
- 通常,这只是另一个提交:隐含分支1联接的第一点上的提交
- 在某些特殊情况下(多个合并基础候选对象),Git必须发明一个“虚拟合并基础”(但在这里我们将忽略这些情况)
- 合并运行两个差异:
git diff base local
和git diff base other
- 这些已打开重命名检测
- 您可以自己运行这些diff来查看合并内容
您可以将这两个差异视为“我们做了什么”和“他们做了什么”。 合并的 目的 是 将 “我们所做的”和“他们所做的” 结合起来 。
差异是基于行的,来自最小编辑距离算法2,实际上只是Git 对我们做了什么以及他们做了什么的 猜测 。
第 一个 diff(base-vs-
local)的输出告诉Git哪些基础文件与哪些本地文件相对应,即,如何从当前提交到基础遵循名称。然后,Git可以使用基本名称在其他提交中发现重命名或删除。在大多数情况下,我们可以忽略重命名和删除问题以及新文件创建问题。请注意,默认情况下,Git2.9版会为 所有
差异(不仅是合并差异)打开重命名检测。(您可以通过配置
diff.renames为来在Git早期版本中启用此功能
true;另请参见的
gitconfig设置
diff.renameLimit。)
如果 仅在一侧 (基础到本地或基础到另一方)更改了文件,则Git会简单地进行这些更改。混帐只有当一个文件时,改变做了三路合并 两个 方面。
为了执行三向合并 ,Git本质上遍历了两个差异(基础到本地和基础到另一个),一次比较一个“差异块”,比较更改的区域。如果每个块都影响原始基础文件的
不同部分 ,则Git会接受该块。如果某些块影响基本文件的 同 一部分,则Git会尝试获取该更改的一份副本。
例如,如果本地更改显示为“添加一条右括号”,而远程更改显示为“添加(在同一位置,相同的缩进)封闭括号”,则Git将仅获取该副本的一个副本。如果两个都说“删除右括号”,Git只会删除该行一次。
仅当两个差异 发生冲突时
,例如,一个说“添加一个缩进的括号内缩进12个空格”,另一个说“添加一个闭合的括号内缩进11个空格”,Git才会声明冲突。默认情况下,Git将冲突写入文件中,显示两组更改-
如果设置
merge.conflictstyle为
diff3, 还 显示 文件的基于合并基础的版本中的代码 。
任何不冲突的差异大块,Git均适用。如果存在冲突,Git通常会使文件处于“冲突合并”状态。但是,两个
-X参数(
-X ours和
-Xtheirs)对此进行了修改:使用
-X oursGit在冲突中选择“我们的” diff大块,然后将该更改放入,而忽略“其”更改。使用
-XtheirsGit选择“他们的”差异块,然后将更改放入,而不考虑“我们的”更改。这两个
-X参数保证Git最终不会声明冲突。
如果Git能够自行解决此文件的所有问题,它就可以做到:您将在工作树和索引/临时区域中获取基本文件,以及本地更改以及其他更改。
如果Git不能自行解决所有问题,它将使用三个特殊的非零索引插槽将文件的基础版本,其他版本和本地版本放入索引/临时区域。工作树版本始终是“
Git能够解决的问题,以及各种可配置项指示的冲突标记”。
像这样的文件
foo.java通常在插槽0中暂存。这意味着现在就可以进行新提交了。根据定义,其他三个插槽为空,因为存在零插槽条目。
在发生冲突的合并期间,插槽零保留为空,并且插槽1-3用于保存合并的基本版本,“本地”或
--ours版本以及另一个或
--theirs版本。工作树保存正在进行的合并。
您可以
git checkout用来提取任何这些版本,或
git checkout -m重新创建合并冲突。所有成功的
gitcheckout命令都会更新文件的工作树版本。
一些
git checkout命令使各个插槽不受干扰。一些
gitcheckout命令写入插槽0,清除插槽1-3中的条目,以便文件可以提交。(要知道哪些人在做什么,您只需要记住它们即可。在很长一段时间内,我就把它们弄错了。)
git commit在清除所有未合并的插槽之前,您无法运行。您可以
git ls-files--unmerged用来查看未合并的插槽,也可以查看
git status更人性化的版本。(提示:使用
git status。请经常使用!)成功合并并不意味着好的代码
即使git merge
成功将所有内容自动合并,也不意味着结果正确!
当然,当它因冲突而停止时,这也意味着Git无法自动合并所有内容,而不是它自己自动合并的内容是正确的。我喜欢设置
merge.conflictstyle为,
diff3以便在合并之前将“基本”代码替换为合并之前,可以看到Git认为
基本 是什么。经常发生冲突是因为diff选择了错误的基数(例如某些匹配的大括号和/或空行),而不是因为必须存在实际的冲突。
至少在理论上,使用“耐心”差异可能会导致基本选择不佳。我自己还没有尝试过。 Git
2.9中新的“压缩启发式”很有希望,但是我也没有尝试过。
您必须始终检查和/或测试合并的结果。 如果已经提交了合并,则可以编辑文件,构建和测试
git add更正的版本,并用于
git commit--amend推开先前的(错误的)合并提交,并使用相同的父项进行不同的提交。(该
--amend部分
git commit--amend是虚假广告它不会改变当前的承诺本身,因为它。 能 不能,而是它与同一个新的提交 的父类标识
为当前犯,而不是使用目前的常规方法提交的ID作为新提交的父级。)
您也可以禁止自动提交与的合并
--no-commit。在实践中,我对此几乎没有什么需要:大多数合并大多只是工作
git show-m而已,并且快速眼球和/或“它可以编译并通过单元测试”就可以发现问题。但是,在发生冲突或
--no-commit合并时,简单的方法
gitdiff会为您提供组合的差异(提交合并后,
gitshow如果不带
-m,则为相同的排序),这可能会有所帮助,也可能会更加令人困惑。您可以运行更特定的
gitdiff命令和/或检查三个(基本,本地,其他)插槽条目看看Git会看到什么
除了
diff3用作之外
merge.conflictstyle,您还可以看到将要看到的差异
git merge。您所需要做的就是运行两个
gitdiff命令-将要运行的两个命令
git merge。
为此,您必须找到(或至少告诉
git diff要找到) 合并基础 。您可以使用
git merge-base,从字面上找到(或所有)合并基础并打印出来:
$ git merge-base --all HEAD foo4fb3b9e0570d2fb875a24a037e39bdb2df6c1114
这表示在当前分支和分支之间
foo,合并基础是提交
4fb3b9e...(并且只有一个这样的合并基础)。然后
git diff 4fb3b9eHEAD,我可以跑步和
git diff 4fb3b9e foo。但是有一种更简单的方法,只要我可以 假设 只有一个合并基础:
$ git diff foo...HEAD # note: three dots
这将告诉
git diff(且 仅
gitdiff)在
foo和之间找到合并基础
HEAD,然后比较该提交(该合并基础)以进行提交
HEAD。和:
$ git diff HEAD...foo # again, three dots
做同样的事情,发现
HEAD和之间的合并基数
foo-““合并基数””是可交换的,因此它们应该与其他方法相同,例如7 + 2和2 +
7均为9,但这次将合并基数与提交
foo。1个
(对于其他命令(不是这样
git diff的命令),三点语法会产生 对称的差异
:位于两个分支但不在两个分支上的所有提交的集合。对于具有单个合并基础提交的分支,这是“每个提交 后,
合并的基础上,对每个分支“:换句话说,这两个分支的联合,不包括合并基础本身和任何早期提交对于具有多个合并基地分支,这个减去了。 所有
的合并基准站的。
git diff我们只是假设只有一个合并基数,我们将其用作差异的左侧或“之前”,而不是将其及其祖先相减。
1在Git中,分支 名称 标识一个特定的提交,即分支的 尖端
。实际上,这就是分支的实际工作方式:一个分支名称命名一个特定的提交,然后为了向该分支添加另一个提交- 分支 在这里意味着 提交链
-Git做出了一个新的提交,其父作为当前的分支提示,然后将分支名称指向新提交。“分支”一词可以指分支名称,也可以指整个提交链。我们应该根据上下文找出哪一个。
在任何时候,我们都可以命名一个特定的提交,并通过将该提交 及其所有祖先
:其父代,其父代的父代等等,将其视为分支。在此过程中,当我们执行合并提交时(与两个或多个父项进行的提交),我们将接受 所有
父项提交,以及他们父母的父母,依此类推。
2该算法实际上是可选的。默认
myers值基于EugeneMyers的算法,但是Git还有其他一些选择。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)