cra*_*xus 1 git version-control merge pull rebase
现在我通常使用
git pull origin develop
Run Code Online (Sandbox Code Playgroud)
从开发分支获取最新更新。最近,我的团队已经过渡到使用 rebase 而不是合并,所以我对一些东西有点困惑。之前我的工作流程非常简单。我会首先签入开发分支并使用
git checkout -b feature/foo
Run Code Online (Sandbox Code Playgroud)
然后我会进行更改、提交并推送它们。通常开发分支会进行一些更改,因此我会使用
git pull origin develop
Run Code Online (Sandbox Code Playgroud)
获取最新的更改,并且仅当其他人修改同一文件时才会发生冲突。但是,当我使用
git pull origin develop --rebase
Run Code Online (Sandbox Code Playgroud)
我注意到我会与我自己的分支发生冲突,即使我是唯一修改它的人。这有什么特别的原因吗?有没有办法避免我与自己的分支发生的这些合并冲突?
首先,我们要注意这git pull主要包括运行两个 Git 命令。这意味着它是一种方便的操作,让您键入git pull而不是git fetchentergit .....。第一个命令始终是git fetch,第二个命令是您的选择:它默认为git merge,但您可以选择git rebase。当您想要变基时,执行一个命令所需的输入量几乎与执行两个命令的输入量一样多,因此它毕竟不是很方便,我建议使用单独的命令和git fetch第二个命令,至少直到您对 Git 非常熟悉。1
所以你的问题实际上解决了一个更简单的问题: 为什么 rebase 有时会出现 merge 没有的冲突? 这个问题有一个答案,实际上相当简单: Rebase 主要是重复的择优挑选,而择优挑选是合并的一种形式。因此,当您合并时,您会在一处可能发生冲突的地方。如果你变基十个提交,你就有十个可能发生冲突的地方。冲突本身也可能有所不同,但机会的规模才是主要因素。
\n\n1在具有子模块的存储库中,git pull可以递归到子模块,在这种情况下,它的命令超过两个,其便利性变得很重要。您还可以配置 git pull为默认运行git rebase,即使没有子模块也能重新获得便利。我仍然鼓励新用户使用两个单独的命令,尽管 \xe2\x80\x94 的语法git pull有点奇怪,并且与几乎所有其他 Git 东西有点不同,而且很容易混淆。分配给 pull 的魔法太多,而实际上所有魔法都来自第二个命令\xe2\x80\x94,并且您需要学习合并才能理解变基。
尽管实现过程充满了棘手的小曲折,但合并背后的想法很简单。当我们要求 Git 合并时,我们有“我们的工作”和“他们的工作”。Git 需要弄清楚我们改变了什么,他们改变了什么,并将这些改变结合起来。
\n\n为了做到这一点,Git 需要找到一个共同的起点。提交根本不是一组更改:它实际上是一个快照。Git 可以将这些快照之一显示为与其前一个快照的差异,即提取两个快照并查看有何不同。因此,如果我们从具有某个哈希 ID 的某个提交开始B,并且它们也从同一个提交开始:
C--D <-- our-branch (HEAD)\n /\n...--A--B\n \\\n E--F <-- their-branch\nRun Code Online (Sandbox Code Playgroud)\n\n然后 Git 可以将快照与B我们最新的 ,D以及他们最新的 ,进行比较F。B-vs-中的任何不同之D处都是我们改变的。B-vs-中的任何不同之F处都是他们改变的东西。然后,Git 组合更改,将组合的更改应用到合并库中的快照,并提交结果,将其与不是一个而是两个前身B连接起来:
C--D\n / \\\n...--A--B G <-- our-branch (HEAD)\n \\ /\n E--F <-- their-branch\nRun Code Online (Sandbox Code Playgroud)\n\n要到达那里,Git 必须运行:
\n\ngit diff --find-renames hash-of-B hash-of-D (我们改变了什么)git diff --find-renames hash-of-B hash-of-F (他们改变了什么)当 Git 合并这两个差异时,我们和他们可能会在某些地方更改同一文件的相同行。如果我们没有对这些行进行相同的更改,Git 将声明冲突并在中间停止合并,而不进行提交,并强制我们清理混乱并完成合并以创建。G G
cherry-pick 背后的想法是复制提交。要复制提交,我们可以让 Git 将其转换为一组更改:
\n\ngit diff --find-renames hash-of-parent hash-of-commit然后我们可以将这些更改手动应用到其他地方,即其他提交。例如,如果我们有:
\n\n C--D <-- our-branch (HEAD)\n /\n...--A--B\n \\\n E--F <-- their-branch\nRun Code Online (Sandbox Code Playgroud)\n\n我们喜欢他们在 中所做的事情F,但还不想要E它本身,我们可以比较E与F,看看他们做了什么。我们可以使用它来尝试对 中的快照进行相同的更改D。然后我们为自己创建一个新的提交\xe2\x80\x94let\,将其F\'称为副本F:
C--D--F\' <-- our-branch (HEAD)\n /\n...--A--B\n \\\n E--F <-- their-branch\nRun Code Online (Sandbox Code Playgroud)\n\n但是,如果我们对 进行了重大更改C,或者他们对 进行了重大更改E,则可能很难使他们从E到F所做的更改与我们的快照中的更改保持一致D。为了让 Git 帮助我们,并自动进行复制,Git 想知道:和之间有什么不同?ED 也就是说,Git 要运行:
git diff --find-renames hash-of-E hash-of-DC(我们在, vs 中拥有什么E)git diff --find-renames hash-of-E hash-of-F (他们改变了什么F)但是等等,我们刚刚在上面看到了同样的模式git merge!事实上,这正是 Git 在这里所做的:它使用与相同的代码git merge,它只是强制合并基点\xe2\x80\x94(这将用于B常规合并\xe2\x80\x94)提交E,即父级F我们正在挑选的承诺。现在,Git 将我们的更改与他们的更改结合起来,将组合的更改集应用到 \xe2\x80\x94 中的 base\ Exe2\x80\x94 中的快照,并自行进行最终F\'提交,但这一次是常规提交。
新的提交也重复使用来自提交本身的提交消息F,因此新的提交F\'(具有一些新的哈希 ID,与F\'s 不同)F非常相似:git show可能显示相同或非常相似的差异列表每个,当然还有相同的提交日志消息。
与 一样git merge,这个合并过程\xe2\x80\x94(我喜欢将合并称为动词\xe2\x80\x94)可能会出错。如果确实出错,Git 会抱怨合并冲突,并以未完成的合并停止,并让您清理混乱并提交。当您提交时,Git 知道您正在完成 agit cherry-pick并在此时为您复制提交消息,以生成F\'.
要执行, Git:git rebase target
git cherry-pick复制列表中的每个提交。2一旦所有要复制的提交都已成功复制,Git会将分支名称移动到复制列表的末尾。
\n\n假设我们从与之前类似的设置开始,尽管我将在此处列出更多提交:
\n\n C--D--E--F <-- our-branch (HEAD)\n /\n...--A--B\n \\\n G--H <-- their-branch\nRun Code Online (Sandbox Code Playgroud)\n\n我们运行,因此 Git按顺序git rebase their-branch列出要复制的提交: 。C-D-E-F然后 Git 将提交检查H为“分离的 HEAD”:
C--D--E--F <-- our-branch\n /\n...--A--B\n \\\n G--H <-- their-branch, HEAD\nRun Code Online (Sandbox Code Playgroud)\n\n现在,Git 将选择性地C复制它。如果一切顺利的话:
C--D--E--F <-- our-branch\n /\n...--A--B\n \\\n G--H <-- their-branch\n \\\n C\' <-- HEAD\nRun Code Online (Sandbox Code Playgroud)\n\nGit 重复D、E和F。一旦完成D,E我们就处于这种状态:
C--D--E--F <-- our-branch\n /\n...--A--B\n \\\n G--H <-- their-branch\n \\\n C\'-D\'-E\' <-- HEAD\nRun Code Online (Sandbox Code Playgroud)\n\nGit 完成复制到 后F,F\'变基的最后一步是将名称拉到our-branch指向最终复制的提交,并重新附加HEAD到它:
C--D--E--F [abandoned]\n /\n...--A--B\n \\\n G--H <-- their-branch\n \\\n C\'-D\'-E\'-F\' <-- our-branch (HEAD)\nRun Code Online (Sandbox Code Playgroud)\n\n每个cherry-pick都会进行一次三向合并,操作的合并基础是正在复制的提交的父级,而“我们的”提交是分离的\ HEADxe2\x80\x94上的提交,注意最初是他们的承诺H,随着我们的进步,H随着时间的推移,它会变成“他们的承诺加上我们的工作”。每次,“他们的”提交都是我们自己的提交。每个挑选的项目都可能存在所有常见的合并冲突,尽管在大多数情况下,大多数情况下都没有任何冲突。
特别是有两种情况特别糟糕。其中之一(可能是最常见的)是,您自己的任何提交(C-D-E-F例如在列表中)本身就是链中某些内容的精选G-H(通常比只有两个提交更长)\xe2\x80 \x94 或反之亦然,例如,也许H本质上是D\'。
如果您或他们能够更早地轻松挑选,而不会发生冲突,那么您的副本可能看起来几乎与链条之一一模一样,甚至 100% 完全相同G-H。如果是这种情况,Git 可以识别出这是一个副本,并将其从“待复制”列表中删除。在我们的例子中,如果H是 true D\',Git 可以看到,Git 将从D待复制列表中删除,并且只复制C-E-F。但如果不是 \xe2\x80\x94,例如,如果他们必须更改一堆副本D以制作H\xe2\x80\x94,那么 Git将尝试复制D,并且这些更改几乎肯定会与他们修改的H.
如果您合并而不是复制,您将B与H(他们的)和B(F您的)进行比较,并且冲突的可能性可能会减少。即使存在冲突,它们也可能更明显且更容易解决。如果冲突是由于不必要的副本造成的,根据我的经验,它们往往看起来更棘手。
另一个常见的问题情况是,在您的C-D-E-F链中,您最后几次提交是专门为了使合并更容易而所做的事情。也就是说,有人可能会这样说:我们更改了 foo 子系统,现在您需要第三个参数F,并且您在挑选 中的更改后添加了第三个参数E。C复制和时您会遇到冲突D。您可能会跳过复制,E因为它是精挑细选的,然后在F修复了D和中的冲突后就不需要复制了E,但这是两个需要修复的副本,一个会自动删除,另一个需要您的自己的,手动掉落。
因此,最后,git merge进行了一次合并,但git rebase进行了许多次挑选,其中每个都是\xe2\x80\x94内部\xe2\x80\x94a合并,并且每个都可能导致合并冲突。变基引发更多冲突并不奇怪!
2从技术上讲,普通(非交互式)git rebase通常不使用git cherry-pick. 相反,它实际上使用git format-patch ... | git am .... 使用git rebase -i始终使用git cherry-pick,并git rebase -m强制非交互式git rebase使用git cherry-pick。简单的 rebase 避免它的事实主要只是古代(可能是 2008 年左右)Git 的遗留,在cherry-pick 被教导进行正确的三向合并之前。
该git am步骤使用-3,这样如果补丁失败,Git 将“回退”到三向合并。结果通常是相同的,但 format-patch-pipe-to-am 方法永远找不到重命名的文件。这使得格式补丁样式更快,但效果不是那么好。
| 归档时间: |
|
| 查看次数: |
2003 次 |
| 最近记录: |