New*_*ser 0 git github git-merge
我完全不习惯使用git作为开发的版本控制系统,我仍然在学习它.我对拉取请求期间的合并冲突感到困惑.根据我的理解,当文件的同一行在两个不同的分支中不同时,通常会发生合并冲突.我有一个具有以下树结构的git存储库.所以我从"master"创建了一个"dev"分支,然后从"dev"创建了"D1"和"D2"分支.master-> dev - >(D1,D2)在"dev"分支中,我有一个文件名"file1.txt",第一行为1111111111111,在"D1"分支中我有相同的文件"file1.txt"第一行为AAAAAAAAAAAAAAA.我创建了一个拉动请求,将"D1"分支合并到"dev",并预期会出现合并冲突,因为两个分支中的第一行"file1.txt"不同.
但是git显示Able合并消息表示没有合并冲突,"dev"分支更改被"D1"分支更改覆盖.知道我错过了什么.
根据我的理解,当文件的同一行在两个不同的分支中不同时,通常会发生合并冲突
这不是那样的.当您在两个不同的分支中更改文件的同一行然后尝试将它们合并在一起时,Git会检测到冲突.
在您的示例中,您已更改分支D1中文件的第一行但未在dev分支中触摸它,因此合并过程看起来像"从分支D1进行第一行更改并将其应用于分支开发中的第一行".
相反,如果您(或其他人)在创建D1后更改分支dev中文件的第一行,然后在D1中更改该行,则合并过程将如下所示:"第一行发生了更改在分支D1和分支开发 - 我应该考虑哪些变化是主要的变化?" - 这就是合并冲突的样子.
这里有很多东西需要学习。有些\xe2\x80\x94,至少有一个\xe2\x80\x94将Git的学习曲线称为“学习墙”。在我看来,在 Git 之上添加 GitHub 之类的 Web 服务实际上会让事情变得更加困难:不再可能判断某些内容是否在Git中,或者由GitHub提供。Pull 请求实际上属于 GitHub 提供的类别。但它们建立在 Git 的合并动词之上(结合 GitHub 存储您的存储库和其他人的)。
\n\n让我们暂时把所有这些都放在一边,从 Git 本身开始。理解\xe2\x80\x94是否以及何时\xe2\x80\x94某个文件\xe2\x80\x94会出现合并冲突的关键隐藏在一些图论背后背后。
\n\n首先,让我们提到提交保存文件。更准确地说,每次提交都会保存截至您进行该提交时所有文件的快照。但每个提交还有额外的信息该提交的额外信息,例如谁进行了提交、何时进行以及为何进行(日志消息)。
\n\n在 Git 中,每个提交都由其哈希 ID 唯一标识。这是一个大的、丑陋的、几乎不可读的、对人类完全无用的字符串,例如5d826e972970a784bd7a7bdf587512510097b8c7(Git 的 Git 存储库中的实际提交)。这些是 Git 查找提交的方式,但除了复制粘贴或链接目的之外,我不建议您这样做过多使用它们。:-)
尽管如此,了解 Git 的做法还是很重要的,因为几乎每次提交都会列出至少一个父提交的原始哈希 ID。父级是此提交之前的提交。肯定不会列出父级的一次提交是有史以来的第一个提交,当然它之前没有提交。
\n\n这一切意味着,从任何分支上的最后一次提交开始,Git 可以向后工作到之前的提交。然后,Git 有一个表,从人类可读的分支名称(例如master原始提交哈希 ID)开始。添加新的提交添加到分支:
master。当某个东西拥有提交哈希 ID 时,我们说该东西指向该提交。这样,分支名称总是指向最后一个分支上的最后一次提交指向其前一个提交,前一个提交又指向另一个步骤,依此类推,因此 Git 可以从那里向后工作。这种后退一步,一次提交一个,就是 Git 保存你\xe2\x80\x94 和其他人\xe2\x80\x94 曾经做过的所有事情的历史记录的方式:每次提交都会及时记录一个快照,并且每次提交(除了首先)有一个“以前,事情看起来像......”历史链接。
\n\n当你创建一个新分支时,Git 会创建一个新的表条目,以便两个分支都指向同一个提交。出于绘图目的,让我对每个提交使用单个大写字母,而不是一个又大又难看的哈希 ID。那么我们可能会得到这样的结果:
\n\n... <-F <-G <-H <-- master, develop (HEAD)\nRun Code Online (Sandbox Code Playgroud)\n\n一旦提交,其中的任何内容都无法更改。所以H 总是指向G,等等。(分支名称可以并且确实一直在变化,通常是为了适应新的提交。)因此,为了在 StackOverflow 文本中绘制目的,我将省略内部箭头,只保留分支名称箭头:
...--F--G--H <-- master, develop (HEAD)\nRun Code Online (Sandbox Code Playgroud)\n\n该名称HEAD附加到分支名称之一。这就是 Git 知道我们正在使用哪个分支的方式,因为当我们进行新的提交时,Git 必须更新此分支名称。现在让我们进行一个新的提交,并调用它的哈希值I:
...--F--G--H <-- master\n \\\n I <-- develop (HEAD)\nRun Code Online (Sandbox Code Playgroud)\n\n现在让我们切换回master并在那里进行新的提交,我们可以称之为J:
J <-- master (HEAD)\n /\n...--F--G--H\n \\\n I <-- develop\nRun Code Online (Sandbox Code Playgroud)\n\n请注意,提交H和更早的内容都在两者上分支上。
如果我们现在要求 Git 将(即 commit )合并 回(即 commit ),Git 将找到最佳的共同祖先 commit。宽松地说,这是Git 通过从两个分支提示向后工作可以找到的第一个共享提交。在这种情况下,这显然是提交developImasterJH。
找到共同祖先提交\xe2\x80\x94(Git 将其称为合并基础\xe2\x80\x94)后,Git 现在必须弄清楚我们更改了什么,以及它们更改了什么。也就是说,Git 必须针对两个分支提示diff进行 commit (合并基础): ,即 commit ,以及,即 commit 。这实际上需要两个单独的H--oursJ--theirsIgit diff命令:
git diff --find-renames <hash-of-H> <hash-of-J> # what we changed\ngit diff --find-renames <hash-of-H> <hash-of-I> # what they changed\nRun Code Online (Sandbox Code Playgroud)\n\nGit 现在可以结合这两组更改。我们触及了一些文件,他们也触及了一些文件。当我们和他们触及相同的文件时,我们将一些行从合并基础更改为我们的提交,他们将一些行从合并基础更改为他们的提交。
\n\n如果 Git 认为我们触及了相同的行,但对这些行进行了不同的更改,则会发生冲突。 这里同样很明显\xe2\x80\x94如果我更改了第17行并且他们更改了第17行,那么我们显然更改了相同的行。然而,同样可能有点奇怪:例如,如果我们都在文件末尾添加了不同的文本,Git 不知道将它们放入哪个顺序,所以这也是一个冲突。
\n\n但只要我们触及不同的行或不同的文件,就不会发生冲突:Git 将两组更改应用于每个合并基础文件,以获得合并结果。然后,Git 可以根据合并的更改进行新的提交。
\n\n合并过程称为三向合并。 另请参阅为什么 3 路合并比 2 路合并更有优势?
\n\n这个新的提交有一个特殊的属性;让我们通过绘制结果来展示它:
\n\n J\n / \\\n...--F--G--H K <-- master (HEAD)\n \\ /\n I <-- develop\nRun Code Online (Sandbox Code Playgroud)\n\n这里发生的事情是新的提交K记住了之前的两次提交:首先,它自己的之前的提交是J。那是第一任K父母。但也有第二个父母:它也记得它来自。这使得 Git 在未来的合并中可以减少很多工作,因为它会更改稍后将成为合并基础的提交。(这是图论中最复杂的部分,我们将跳过它。)KI
组合更改并进行新提交的过程是单词merge的动词形式,即合并。此过程所做的提交使用与形容词相同的词:K是合并提交。Git 和 Git 的用户经常将其缩写K为“合并” ,其中使用了“合并”一词一词作为名词。
合并提交只是具有至少两个父级的任何提交。该git merge命令经常进行此类提交。(但并非总是如此!这是 Git 陡峭学习曲线的另一部分。现在,让我们忽略它,因为 GitHub 尝试\xe2\x80\x94半成功地\xe2\x80\x94隐藏这一点你。) 最后,当你有一个合并(名词)时,它是通过合并(动词)的过程完成的。 该过程使用三个输入\xe2\x80\x94(合并基础)和两个分支提示\xe2\x80\x94 来产生组合更改。然后该git merge命令产生名词形式的结果。
理解这个合并过程非常重要,这主要需要时间和练习。原因是它不仅仅是git merge将合并作为动词操作:许多其他 Git 命令也这样做,只是在完成后不进行合并提交。每次运行git rebase、git cherry-pick或 时git revert,您都将使用 Git 的合并机制。即使是看似简单的(我认为是具有欺骗性的)也git stash使用 Git 的合并机制。当 Git 自己正确地\xe2\x80\x94(大多数情况下\xe2\x80\x94)你不必考虑它,但是当它出错时,你需要知道该怎么做。
merge.conflictStyle(对于 Git,我发现将配置选项设置为也很有帮助diff3,这样当 Git 留下混乱的冲突合并需要您修复时,它会显示输入合并基础以及两个分支提示。其他人喜欢使用基于窗口的精美合并工具,所以这是一个品味问题。)
现在我们已经有了上述背景,让我们看看 GitHub 的“发出拉取请求”按钮是如何真正工作的。为了告诉你你的分支是否可以与别人的分支合并,GitHub 所做的是实际进行合并,作为测试合并。1 GitHub 所做的测试合并根本不在任何分支上,但它仍然遵循相同的总体思路。如果没有合并冲突,则测试成功,并且 GitHub 表示能够合并。如果存在冲突,GitHub 会丢弃测试合并\xe2\x80\x94,它无法完成它,就像 Git 本身无法\xe2\x80\x94 一样,并告诉您存在冲突。
\n\n当然,如果存在冲突,则由您决定如何处理。现在您需要了解上述三向合并过程,这需要了解图\xe2\x80\x94,或者,如果没有真正理解所有图论,至少要清楚什么是合并基础。你可以找到基础,将其与两个提示进行比较,看看冲突是如何产生的。(或者,设置merge.conflictStyle为diff3,Git 会将冲突源保留在工作树中,您可以在其中直接编辑它。)
1我在这里跳过了 Git 如何将提交从一个存储库传输到另一个存储库的一些重要方面。正如我所提到的,GitHub 在同一个网站上拥有您和他们的存储库,因此他们可以并且确实在这里作弊\xe2\x80\x94,他们不必在任何地方传输任何内容,他们拥有一切。同样,它们实际上并不运行git merge,因为这需要工作树:GitHub 上的存储库都是所谓的裸存储库,没有工作树。但所有这些都是挑剔的细节,破坏了“GitHub 运行测试合并”的良好概述想法:它们实际上运行了测试合并。他们只是有很多需要使用的棘手部分来实现它。
还值得一提的是,在这个脚注中,GitHub 做了与git merge --no-ff此处相同的操作。没有选项可以执行相当于git merge --ff-only或git merge没有任何快进控制旋钮的操作。
对于拉取请求N,拉取请求的提交本身可以在引用下获取。如果测试合并成功,则位于参考下refs/pull/N/headrefs/pull/N/merge。
| 归档时间: |
|
| 查看次数: |
698 次 |
| 最近记录: |