How do I solve "error: Merging is not possible because you have unmerged files."?
How do I solve "error: Merging is not possible because you have unmerged files."?
当我输入 git merge "my-most-up-to-date-branch"
时,出现以下错误:
问题是我根本不知道从哪里开始。我可以手动编辑的 .py 文件存在差异。但是,还有一些无法手动修复的文件,例如 .db 、 .pyc 文件。
这是我输入 git mergetool
:
时得到的结果
另外,我不确定我是否理解如果我在第二张图片中输入“m”或“d”会发生什么。项目的最新版本位于分支“重置密码”中。我该如何解决这个问题?
ps:我确信有一种方法可以在不使用合并并将“重置密码”设为我的新“主”分支的情况下处理这个问题。但是,我真的很想能够通过合并来处理这个问题,这样我就可以在以后的职业生涯中处理类似的问题。
当你运行git合并<em>name</em>
时,有多种可能的结果:
Git 找不到适合合并的内容并抱怨,甚至从未 开始 合并。这里不是这种情况。
Git 开始操作,并且能够自行完成,因为以下两个条件之一为真:
- 操作发现不需要真正的合并:快进将代替服务,您允许快进,Git执行快进而不是合并;或
- 该操作发现需要真正的合并,或者不允许快进,但能够自己进行真正的合并,因此它会这样做并进行合并提交。
在这些情况下,合并已完成,您可以在 Git 中继续做更多事情。这里也不是这种情况。
Git 开始操作,但无法完成。你一团糟。
案例 3 已经在某个时刻发生了,在 你运行 git merge reset-password
之前。情况 3 发生后,您 必须清理混乱 才能继续。 运行 git merge
再次获取您在第一张图片中显示的输出。
(注意:使用 git cherry-pick
和 git revert
或任何调用它们的命令,您可能会遇到同样的情况。因为 git rebase
是由重复的 cherry-pick 执行的操作,这些也可能会给您带来合并冲突。我只是猜测您之前有一个从未完成的合并,基于 shell 提示中的 >M<
。似乎大多数设置都使用 >R<
或 >R
表示不完整的 rebase。)
Problem is that I don't know where to start, at all ...
好吧,你有点做,因为你已经尝试过 git mergetool
。但这是跳入 Git 的深渊。不幸的是,几乎所有的方法都涉及到这里的深层次。 Git 有点强迫你了解它留下的烂摊子,这并不简单。
事前须知
您可能已经了解其中的一些内容,但至少值得快速浏览一下以防万一。首先,Git 实际上就是 提交 。提交已编号,但 Git(和您)通常会通过 b运行ch names 找到这些编号,因为这些关于提交的事实:
提交已编号,但数字又大又难看。例如,它们看起来像 e1cfff676549cdcd702cbac105468723ef2722f4
。这些数字可能 看起来 运行dom,但实际上,它们是某些内部 Git object 内容的加密校验和。每个 Git 提交都是一个唯一的内部 object,因此得到一个唯一的校验和。1
提交包含两件事:Git 在您(或任何人)提交时知道的每个源文件的完整快照,以及一些元数据。元数据包括提交者的姓名和电子邮件地址等内容。对于 Git 至关重要,元数据还包括 先前 提交的原始哈希 ID,或者对于合并提交,两个或更多先前提交。 Git 调用之前的提交 parent (因此,暗示提交本身是 child parent).
哈希 ID 是校验和这一事实意味着 任何 提交都无法更改。所有提交都是完全 read-only。每次提交里面的文件也是read-only;为了保存 space,它们被压缩并以 Git-only 格式存储,其中 de-duplication.
这三点有一些重要的后果,我们将在这里非常快速地讨论:
提交形式链。因为 child 提交保存了 parent 的哈希 ID——它必须是;当我们创建 parent 时,child 的哈希 ID 不可预测——这些链指向 向后 .
A b运行ch name 仅保存 last 提交的哈希 ID链。然而,作为某个链中的最后一次提交并不意味着在此之后不能有更多的提交:另一个 b运行ch 名称可以指向稍后的提交。
许多提交在多个 b运行ches 上。存储库中的第一个提交,没有 parent,因为在 之前没有提交 ,通常在 every b运行通道。 (让它不出现在每个 b运行ch 上的唯一方法是让这些“第一”或 root 提交中有多个。我们不会看如何这可以在这里发生。)
因为提交中的文件是 read-only,您 处理 (或使用) 的文件不是在一次提交中。在一个重要的意义上,他们根本不在存储库本身中。
下面的部分根本不是关于合并的。稍后我们将在更大的标题中进行介绍。
1不关注pigeonhole principle here, or see How does the newly found SHA-1 collision affect Git?
提取提交:您的work-tree
让我们对最后一个要点进行一些扩展。要完成任何实际工作,您需要从某些提交中获取文件 out。 Git 将通过提取冻结、压缩和 de-duplicated 文件(有时根本不是正常的 OS 文件,并且在内部都有 hash-ID 名称)来完成此操作放入常规的日常文件中,将它们放入工作区。此工作区不在存储库内。2
Git 将此工作区称为您的 工作树 或 work-tree。由于此区域是您的,您可以根据需要在此处创建其他文件和directories/folders。 Git 知道的文件至少在最初是 Git 刚刚从某个现有提交中提取的文件。如果您使用 OS 创建其他文件,Git 不知道它们,尽管 Git 通常会注意不要 破坏 它们一不小心。3
几乎所有的版本控制系统都是这样工作的:有提交的文件,它们会一直保存,4 和一些 more-temporary 你可以实际工作 on/with。这部分,大多数人根本不会感到困惑。大多数其他版本控制系统到此为止,但 Git 是 Git,它不会。
2 存储库本身通常存储在工作区顶层的隐藏 .git
目录中。也就是说,存储库位于 work-tree,而不是相反!这并不总是一个明智的arge运行gement,子模块存储库通常被移开,在现代Git,以免你的work-tree的这一部分被删除。
3在 .gitignore
中列出文件有时会给 Git 破坏它的权限,以及一些 Git 命令,例如 git clean
,应该 销毁此类文件。所以这不是完全的安全保障运行。但一般来说,您可以在 work-tree 中创建文件,而不是让 Git 破坏它们。你会时不时地看到来自 Git 的抱怨,说一些 work-tree 文件妨碍了 git checkout
或 git merge
操作:Git 只是告诉你嘿,我找到了你的这个文件,如果我现在从提交的文件覆盖它,我会破坏 你的 数据,所以也许你应该移动它先让开。
4或者只要您不告诉系统忘记该提交或其他任何内容,就可以保存。从一个版本控制系统到另一个版本控制系统,其细节变化很大。
进行新提交:Git 的索引
在其他版本控制系统 (VCS) 中,您检查了一些提交,现在您有一堆有用的文件。您对这些文件进行更改,准备就绪后,您告诉 VCS:提交这些文件。它会找到你所做的,然后提交。其中一些 VCS 在这里可能慢得令人难以忍受。 Git 倾向于 blazing-fast。它以一定的价格获得这种速度。这个价格有(对您)有用的附带好处,但它肯定令人困惑,是时候了解它的全部内容了。
每个文件只有两个个副本,Git存储三个。三者之一是 当前提交 中的冻结(和 de-duplicated)文件。您选择了一些要处理的提交,因此具有丑陋的大哈希 ID 的提交是 当前提交,并且该提交具有所有文件的快照。
在另一端,可以说,Git 已将提交的所有这些文件 复制到 到你的 work-tree 中。这些是您可以用来做任何事情的普通日常文件。
在这两个副本之间,不过,Git保留了第三个“副本”。 “复制”一词在这里用引号引起来,因为第三个是冻结的 形式 ,并且是 de-duplicated 之前的形式。最初,所有这些都与提交中的副本匹配。这个额外的副本位于 Git 称为 index 或 暂存区 的地方,或者有时——现在很少见——缓存。所有这三个名称都是为了同一件事。它有三个名字可能是因为 index 没有任何意义,而且 cache 太具体了:名字 staging area体现了它的作用。
当您进行新提交时,Git 使用 Git 索引中的 ready-to-go 文件。由于它们采用正确的 格式 ,Git 可以非常快速地进行新的提交。但这意味着,如果您 更改 work-tree 中的副本 ,您你必须告诉 Git 替换 索引副本。索引中的副本采用冻结 格式 但不在提交中,因此实际上并未冻结。
git add
命令是您执行所有这些操作的方式。 git 添加 <em>file</em>
的真正意思是 使 file
的索引副本与 work-tree复制。 Git 将用新索引副本替换旧索引副本,在 git add
时压缩和 de-duplicating 文件,使其准备好提交。这意味着不是 git commit
变慢,而是 git add
变慢了——但你只需要对你 更改过的 的文件执行此操作,所以事实并非如此慢。
反过来,所有这些都意味着索引(或暂存区)中的内容实际上是您提议的下一次提交。当 Git 提取该提交时,Git 从 当前提交 中填充了它。 Git 将提交复制到 Git 的索引 和 您的 work-tree。现在您已经更改了内容,或者可能添加甚至删除了一些文件,您必须更新 Git 的索引以匹配。如果您只想删除内容,可以使用 git add
或 git rm
来执行此操作。这会更新 Git 的索引,因此您建议的下一次提交。
进行新提交:更新 b运行ch 名称
在我们继续讨论合并的工作原理之前,让我们花点时间观察每天定期 non-merge 提交的过程——换句话说,只提交一次 parent。我们从一个简单的线性提交链开始,以某个特定的具有哈希 ID 的最后一次提交结束:
... <-F <-G <-H <-- somebranch (HEAD)
这里 H
代表链中最后一次提交的实际哈希 ID。 Commit H
包含快照和元数据。 Git 可以通过哈希 ID 找到提交,哈希 ID 在 name somebranch
中。在提交H
的元数据中,Git可以找到更早的哈希ID——parent——提交G
,所以使用somebranch
找到 H
让 Git 找到 G
。 Commit G
当然有一个快照和元数据,元数据包括其 parent F
的哈希 ID。这又有一个快照和一个哈希 ID。所以只给定 b运行ch 名称,Git 可以找到 all 提交。
让我们创建第二个指向同一提交的 b运行ch 名称:
...--F--G--H <-- somebranch (HEAD), anotherbranch
我们仍在使用提交 H
。这里的 (HEAD)
告诉我们我们正在使用 name somebranch
来查找提交 H
。如果你 git checkout anotherbranch
,我们将开始使用 name anotherbranch
,但仍然会找到提交 H
:
...--F--G--H <-- somebranch, anotherbranch (HEAD)
如果您现在修改一些文件并 git add
它们将更新后的文件放入 Git 的索引中,您现在可以 运行 git commit
创建一个新的犯罪。 Git 将:
- 收集元数据,例如您的姓名和电子邮件地址以及当前日期和时间(以及您的提交消息,有时还有更多内容);
- 使用 当前 提交的哈希 ID 作为新提交的 parent;
- 使用 Git 的索引 / staging-area 中的任何内容作为新提交的快照;
- 写出新提交,为其分配新的哈希 ID,但我们只称其为
I
。
还有一步,但让我们现在绘制提交 I
:
...--F--G--H
\
I
现在让我们添加 b运行ch 名称,在我们注意到 git commit
的 last 步骤是 Git 写入之后current b运行ch name 中的新哈希 ID — 带有附加 HEAD
:
的那个
...--F--G--H <-- somebranch
\
I <-- anotherbranch (HEAD)
现在 H
之前的提交都在两个 b运行 上,而新提交 I
仅在 anotherbranch
.
上
正在合并
我们现在准备处理 Git 的合并操作。让我们首先考虑这些事实:
- 每个提交都包含一个快照——不是更改,只是一个快照。
- 如果我们选择一些其他的——通常是更早的——快照,我们可以将快照变成变化,并且比较两者。这只是一个简单的 spot the difference 游戏,由计算机完成所有定位。
- 合并的目标是合并更改。
我们从这样的情况开始:
I--J <-- branch1 (HEAD)
/
...--G--H
\
K--L <-- branch2
也就是说,name branch1
选择了一些提交——我们称之为 J
——并且 name branch2
选择其他一些我们称之为 L
的提交。我们现在使用的是 J
:这就是 Git 的索引和我们的 work-tree.
中的内容
当我们运行git merge branch2
时,Git使用HEAD
定位我们的提交J
,并且使用我们作为参数提供的名称 branch2
来定位 他们的 提交 L
。但是现在 Git 需要弄清楚 我们改变了什么 和 他们改变了什么 。这意味着 Git 必须找到一些更早的提交。
正确 较早的提交并不总是很明显,但是 Git neds 是在 both b运行ches 上的提交。提交 H
在两个 b运行 上;提交 G
以及更早的任何内容也是如此。不过,按理说,best 提交可能是“最接近终点”的提交:也就是说,提交 H
比提交 G
,因为将 H
中的快照与以后的提交进行比较可能会发现 比 G
中的快照或更早的任何内容进行比较 的更改更少。
我们称此“正确提交”为合并基础,无论如何,Git 在这里自行找到合并基础。在这个简单的例子中,很容易看出 Git 将选择提交 H
。在更复杂的图表中,使用 git merge-base --all
可能是查看 Git 正在挑选的唯一明智的方法。5
找到我们改变的,Git现在运行s,实际上:
git diff --find-renames <hash-of-H> <hash-of-J> # what we changed
一个非常相似的命令发现他们改变了什么:
git diff --find-renames <hash-of-H> <hash-of-L> # what they changed
Git 同样,在这一点上,实际上 将所有三个提交读入索引 .
5Git这里使用了lowest common ancestor算法。当应用于有向无环图时,可能会有不止一个 LCA。 --all
到 git merge-base --all
告诉此命令打印出所有 LCA。不同的合并策略可能只使用一个合并基础,也可能使用所有合并基础;此处不赘述。
合并真正发生在索引中
早些时候我们看到索引有每个文件的副本。这是索引的正常状态,但在合并期间,索引实际上会扩展。而不是一个副本,它包含三个:
- Git 将合并基础提交读入“插槽 1”处的索引。
- Git 将我们的提交读入“插槽 2”处的索引。
- Git 将他们的提交读入“插槽 3”处的索引。
也就是说,如果合并基础、我们的提交和他们的提交都有一个 README.md
个文件,那么索引现在有 三个 README.md
个文件在里面。我们可以 name 这些使用 digit 和一些冒号,用一些 Git 命令,6 例如:
git show :1:README.md # view the merge base copy
git show :2:README.md # view the `--ours` copy
这对三个提交中的每个文件重复。有些提交可能 没有 所有文件名,上面的 git diff --find-renames
可能会发现,从提交 H
到 L
,它们 重命名 一些文件,例如;在这种情况下,索引条目有点棘手。或者,也许我们或他们删除了一个文件,或者添加了一个全新的文件,在这种情况下,没有 slot-1 条目但有 slot-2 或 slot-3 条目。 你有这些情况,所以我们不能忽略它们。但是它们有点复杂,所以现在,我们将忽略它们.剩下的就很简单了:
- 如果所有三个索引条目都匹配,则根本没有人动过该文件。三个副本中的任何一个都将作为合并文件。
- 如果三个副本中的两个匹配,那么我们更改了文件(基础和他们的匹配)并且 Git 应该使用我们的,或者他们更改了文件(基础和我们的匹配)并且 Git 应该使用他们的,或者我们和他们对文件进行了 相同的更改 (他们的和我们的匹配)并且 Git 应该使用这些版本中的任何一个。所以 Git 将使用我们的或他们的,以 与 不匹配的基数为准。
- 如果所有三个副本都不同,Git必须做一些实际工作。
如果 Git 能够从上面找出要使用的版本,Git 只需将该版本从这些编号为非零的插槽移动到编号为零的插槽,并擦除 higher-numbered 插槽。 slot-zero 条目是正常的“这是文件,准备提交”副本。所以 那个 文件现在已经解决了。 Git 将选定的副本也放入您的 work-tree。
如果没有,Git 继续尝试 low-level 合并 文件。7
6大多数可以使用散列 ID 的 Git 命令可以使用 Git 解析为散列 ID 的名称。此解决方案是通过 the gitrevisions documentation 中概述的规则完成的。因此 git rev-parse :1:README.md
打印出该文件的内部 blob 哈希 ID。当使用 git show
或 git cat-file -p
时,您可以给它哈希 ID 或名称;他们将根据需要通过内部 rev-parse 运行 名称。
7您可以指定 merge driver 而不是让 Git 使用其内置的。这也变得有些复杂。
Low-level 合并正常完成 diff-hunk-by-diff-hunk
假设我们在索引中有 run.py
的三个不同版本,从 base 到我们的差异表示对第 42 行进行更改,而从 base 到他们的差异表示进行不同的更改到第 54 行。Git 将简单地进行 both 更改和一个将它们添加到文件的合并基础副本。
如果我们和他们更改了相同的线路,Git 将比较我们都使用的作为他们的新替代品的线路。如果我们的替换匹配,Git 将复制一份此更改。
如果我们和他们更改了相同的行但更改为不同的文本,Git将在此声明合并冲突文件,并将 ar运行ge 合并在中间停止。扩展的 (-X
) 选项可以告诉 Git 毕竟不要停止(通过告诉它有利于我们或他们的),但我们将跳过这些。
如果合并我们的更改和他们的更改后没有合并冲突,Git 将像往常一样将结果放入索引槽零和您的 work-tree。此文件也已解决。
如果Git无法解决冲突,低级合并代码将在将三个文件合并到您的文件时写入其best-effort work-tree。 (Git 的索引发生了什么,好吧,我们将把它留到下一节。)work-tree 文件将在 没有[=的地方使用合并的更改599=] 冲突,以及它们发生的地方,将包含来自合并“两侧”的行。如果将 merge.conflictStyle
设置为 diff3
,冲突区域将包含文件 merge-base 版本中的相应行。我喜欢总是设置这个选项;我发现由此产生的冲突更容易阅读。
高层冲突,也称为树冲突
在上一节中,我谈到了 Git 如何处理某些文件的三个版本中的冲突,其中有合并基础副本、--ours
副本和 --theirs
复制,这三个都不同。但是让我们看看这些情况会发生什么:
假设他们删除了一个文件而我们没有对它做任何事情。 Git 应该用这个做什么? Git的答案是进行删除: Git通过清空所有索引槽(包括槽零)并使文件在合并结果中被删除确保该文件不在您的 work-tree.
中
假设我们删除了一个文件,而他们没有对它做任何事情。 Git 以同样的方式处理。
假设我们或他们删除了一个文件,而他们或我们——另一方——修改该文件。 Git 的答案是声明一个 合并冲突 并且只在索引中保留三个副本中的两个。 Git 称之为 modify/delete 冲突 .
假设我们重命名一个文件(不改变其内容),他们没有改变它,或者改变了它但没有重命名. Git 的答案是结合这两个更改:如果有的话,接受他们的更改,并使用我们的新文件名。这同样适用于他们重命名它而我们没有。如果我们都修改文件,并且低级代码可以合并 content 更改,Git 通过重命名和合并的内容更改来解析文件。
如果我们都重命名文件,但使用不同的新名称,Git 将此称为 rename/rename 冲突.
如果我们都创建了 all-new 文件,内容不同但名称相同,Git 称之为 add/add 冲突.
这些涉及文件名或整个文件的冲突creation/deletion都是高级别或树冲突,因为它们不' 涉及 low-level 内容冲突。我们甚至可以获得高级和低级冲突,例如,rename/rename 冲突 加 低级冲突;但这里的要点是,如果我们确实遇到这些高级冲突之一,则扩展(-X ours
和 -X theirs
)选项无效:这些选项仅由 low-level 处理合并代码。8
在任何情况下,如果 Git 确实 因合并冲突而停止,它 在其索引 [=599] 中留下非零槽号条目=].这使得两个或三个输入文件可用于 git mergetool
等命令,并为 git mergetool
留下足够的痕迹来诊断高级冲突,例如 modify/delete 冲突。
8将来可能会有一些更高级的高级冲突处理程序允许某些 -X
选项。但是今天没有了。
你的工作:收拾残局
我们现在知道 Git 留下了什么样的烂摊子:
- 某些 work-tree 文件中可能存在合并冲突。
- 如果是这种情况,索引会为所有这些条目保留一些非零槽号条目。
- 对于没有低级合并冲突的文件,索引可能包含非零槽号条目。 All 合并冲突通过这些非零槽号显示在索引中; low-level 也显示为带有冲突标记的部分合并 work-tree 文件。
你的工作是完成合并。您可以按照自己喜欢的方式执行此操作。
您不必使用 higher-numbered 索引条目,但如果您愿意,git mergetool
为您提供了一种方便的方式来访问它们,这不需要摸索 git show :1:file.ext
、git show :2:file.ext
和 git show :3:file.ext
以及许多临时文件:git mergetool
为您完成。
您不必使用文件的 work-tree 副本及其部分合并。
你做必须运行git add
或git rm
,但git mergetool
可以做到对你也是。要标记冲突已解决,您将完全删除索引副本——这意味着最终提交将根本没有文件——或者将正确的合并结果写入索引槽零。
您的具体情况
在您的特定情况下,您列出了 __pycache/*.pyc
个文件(其中四个)和另外两个文件,app.db
和 run.py
。
__pycache__
文件几乎不应该在 Git 存储库中。您对其中一个的合并冲突表明合并的一侧 - --ours
侧,即合并基础与 HEAD
- 修改了文件,而合并的另一侧删除了文件, 在 git merge
运行.
的两个 git diff
中
此处正确的解决办法是接受他们的更改,即完全删除文件。对于 git mergetool
,那么,答案将是 d
:使用删除,而不是保留修改后的文件。
对于app.db
,正确的结果可能不是你的文件,但也可能不是他们的文件。正确的结果可能是这两个文件的某种组合。如果数据库是二进制的,Git 的简单 newline-based 文本替换规则,用于组合两个 git diff
并将组合的更改应用到合并基础副本,根本不起作用.如何生成正确的最终 app.db
副本取决于您,但我们假设有一个魔术命令可以读取两个 app.db
输入文件并生成正确的结果。你可能 运行:
git show :2:app.db > app.db.mine
git show :3:app.db > app.db.theirs
magic-combiner -o app.db app.db.mine app.db.theirs
组合它们并将正确的组合数据写入 app.db
。现在您的 work-tree 副本是您要提交的内容,您只需 运行:
git add app.db
这会擦除三个编号的插槽(:1:app.db
、:2:app.db
和 :3:app.db
都消失了)并复制(压缩和冻结以及 de-duplicates)当前app.db
进入索引-slot-zero.
对于 run.py
,也许您应该查看他们的文件和您的文件,也许还应该查看合并基础版本,在编辑器或合并工具或您将使用的任何工具中找出正确的合并结果是。或者 work-tree 副本与 Git 的合并尝试足以让您弄清楚该文件中应该包含什么。 git mergetool
命令可能会为您提供一种 运行 合并所有三个输入的工具的方法。在大多数情况下,我更喜欢在编辑器中编辑 run.py
并弄清楚(使用 merge.conflictStyle
的 diff3
设置中的三个部分)。
如果您有 git mergetool
运行 工具,那么:
- 要么
git mergetool
对这个工具了解很多,并且可以相信它会退出,状态代码显示“全部合并,使用结果”或“未合并,不使用”和 git mergetool
是否会 运行 git add
是否正确;或
git mergetool
对这个工具了解不够,但是会运行然后问你是否应该使用结果。
如果 git mergetool
使用结果,它会自己做 git add run.py
。如果没有,您仍然在索引中拥有三个副本;您可以在您最喜欢的编辑器中打开 run.py
,查看它,然后决定它是否全部正确或需要更多更改。您可以 运行 测试等等。
即使git mergetool
确实添加了文件,您仍然可以查看并运行测试。 解析文件只是意味着设置索引,以便Git认为合并完成。
提交最终合并
如果 Git 认为合并是自己完成的,Git 将进行新的合并提交:
I--J
/ \
...--G--H M <-- branch1 (HEAD)
\ /
K--L <-- branch2
这个合并提交有一个哈希 ID,就像任何提交一样。它有一个快照,就像任何提交一样。它有元数据,就像任何提交一样——有一个区别:它将提交 J
列为其 第一个 parent,因此 M
指向 J
,但随后它还将提交 L
列为其 第二个 parent,因此 M
也指向 L
.现在提交 H-I-J-M
在 branch1
上(加上更早的提交)但 H-K-L-M
也是如此(加上更早的提交)。所以现在所有以前只在 branch2
上的提交,都在两个 b运行 上。新提交 M
仅在 branch1
上,和往常一样,是 b运行ch 的新提示:Git 写入了 M
的哈希 ID进入 name branch1
.
如果 Git 由于合并冲突而没有自行进行合并提交,您:
- 收拾残局,然后
[=516=运行 git merge --continue
或 git commit
,9
和 Git 现在将像以前一样使用两个 parent 进行合并提交 M
。或者,您可以 运行:
git merge --abort
擦除索引(好吧,将其重置为匹配 J
,真的)并将您的 work-tree 放回匹配的提交 J
,您将回到您在开始合并之前所处的情况。 (你为解决合并所做的任何工作都消失了,所以在这里要小心一点!)
9所有 git merge --continue
真正做的是确保您处于合并的中间,然后 运行 git commit
。所以它有点安全,因为如果您认为您处于冲突合并中,但您不知何故提前中止或完成了它,它不会做任何事情。通常在那种情况下 git commit
也会告诉您没有什么可提交的,所以这很少是重要的。
当我输入 git merge "my-most-up-to-date-branch"
时,出现以下错误:
问题是我根本不知道从哪里开始。我可以手动编辑的 .py 文件存在差异。但是,还有一些无法手动修复的文件,例如 .db 、 .pyc 文件。
这是我输入 git mergetool
:
另外,我不确定我是否理解如果我在第二张图片中输入“m”或“d”会发生什么。项目的最新版本位于分支“重置密码”中。我该如何解决这个问题?
ps:我确信有一种方法可以在不使用合并并将“重置密码”设为我的新“主”分支的情况下处理这个问题。但是,我真的很想能够通过合并来处理这个问题,这样我就可以在以后的职业生涯中处理类似的问题。
当你运行git合并<em>name</em>
时,有多种可能的结果:
Git 找不到适合合并的内容并抱怨,甚至从未 开始 合并。这里不是这种情况。
Git 开始操作,并且能够自行完成,因为以下两个条件之一为真:
- 操作发现不需要真正的合并:快进将代替服务,您允许快进,Git执行快进而不是合并;或
- 该操作发现需要真正的合并,或者不允许快进,但能够自己进行真正的合并,因此它会这样做并进行合并提交。
在这些情况下,合并已完成,您可以在 Git 中继续做更多事情。这里也不是这种情况。
Git 开始操作,但无法完成。你一团糟。
案例 3 已经在某个时刻发生了,在 你运行 git merge reset-password
之前。情况 3 发生后,您 必须清理混乱 才能继续。 运行 git merge
再次获取您在第一张图片中显示的输出。
(注意:使用 git cherry-pick
和 git revert
或任何调用它们的命令,您可能会遇到同样的情况。因为 git rebase
是由重复的 cherry-pick 执行的操作,这些也可能会给您带来合并冲突。我只是猜测您之前有一个从未完成的合并,基于 shell 提示中的 >M<
。似乎大多数设置都使用 >R<
或 >R
表示不完整的 rebase。)
Problem is that I don't know where to start, at all ...
好吧,你有点做,因为你已经尝试过 git mergetool
。但这是跳入 Git 的深渊。不幸的是,几乎所有的方法都涉及到这里的深层次。 Git 有点强迫你了解它留下的烂摊子,这并不简单。
事前须知
您可能已经了解其中的一些内容,但至少值得快速浏览一下以防万一。首先,Git 实际上就是 提交 。提交已编号,但 Git(和您)通常会通过 b运行ch names 找到这些编号,因为这些关于提交的事实:
提交已编号,但数字又大又难看。例如,它们看起来像
e1cfff676549cdcd702cbac105468723ef2722f4
。这些数字可能 看起来 运行dom,但实际上,它们是某些内部 Git object 内容的加密校验和。每个 Git 提交都是一个唯一的内部 object,因此得到一个唯一的校验和。1提交包含两件事:Git 在您(或任何人)提交时知道的每个源文件的完整快照,以及一些元数据。元数据包括提交者的姓名和电子邮件地址等内容。对于 Git 至关重要,元数据还包括 先前 提交的原始哈希 ID,或者对于合并提交,两个或更多先前提交。 Git 调用之前的提交 parent (因此,暗示提交本身是 child parent).
哈希 ID 是校验和这一事实意味着 任何 提交都无法更改。所有提交都是完全 read-only。每次提交里面的文件也是read-only;为了保存 space,它们被压缩并以 Git-only 格式存储,其中 de-duplication.
这三点有一些重要的后果,我们将在这里非常快速地讨论:
提交形式链。因为 child 提交保存了 parent 的哈希 ID——它必须是;当我们创建 parent 时,child 的哈希 ID 不可预测——这些链指向 向后 .
A b运行ch name 仅保存 last 提交的哈希 ID链。然而,作为某个链中的最后一次提交并不意味着在此之后不能有更多的提交:另一个 b运行ch 名称可以指向稍后的提交。
许多提交在多个 b运行ches 上。存储库中的第一个提交,没有 parent,因为在 之前没有提交 ,通常在 every b运行通道。 (让它不出现在每个 b运行ch 上的唯一方法是让这些“第一”或 root 提交中有多个。我们不会看如何这可以在这里发生。)
因为提交中的文件是 read-only,您 处理 (或使用) 的文件不是在一次提交中。在一个重要的意义上,他们根本不在存储库本身中。
下面的部分根本不是关于合并的。稍后我们将在更大的标题中进行介绍。
1不关注pigeonhole principle here, or see How does the newly found SHA-1 collision affect Git?
提取提交:您的work-tree
让我们对最后一个要点进行一些扩展。要完成任何实际工作,您需要从某些提交中获取文件 out。 Git 将通过提取冻结、压缩和 de-duplicated 文件(有时根本不是正常的 OS 文件,并且在内部都有 hash-ID 名称)来完成此操作放入常规的日常文件中,将它们放入工作区。此工作区不在存储库内。2
Git 将此工作区称为您的 工作树 或 work-tree。由于此区域是您的,您可以根据需要在此处创建其他文件和directories/folders。 Git 知道的文件至少在最初是 Git 刚刚从某个现有提交中提取的文件。如果您使用 OS 创建其他文件,Git 不知道它们,尽管 Git 通常会注意不要 破坏 它们一不小心。3
几乎所有的版本控制系统都是这样工作的:有提交的文件,它们会一直保存,4 和一些 more-temporary 你可以实际工作 on/with。这部分,大多数人根本不会感到困惑。大多数其他版本控制系统到此为止,但 Git 是 Git,它不会。
2 存储库本身通常存储在工作区顶层的隐藏 .git
目录中。也就是说,存储库位于 work-tree,而不是相反!这并不总是一个明智的arge运行gement,子模块存储库通常被移开,在现代Git,以免你的work-tree的这一部分被删除。
3在 .gitignore
中列出文件有时会给 Git 破坏它的权限,以及一些 Git 命令,例如 git clean
,应该 销毁此类文件。所以这不是完全的安全保障运行。但一般来说,您可以在 work-tree 中创建文件,而不是让 Git 破坏它们。你会时不时地看到来自 Git 的抱怨,说一些 work-tree 文件妨碍了 git checkout
或 git merge
操作:Git 只是告诉你嘿,我找到了你的这个文件,如果我现在从提交的文件覆盖它,我会破坏 你的 数据,所以也许你应该移动它先让开。
4或者只要您不告诉系统忘记该提交或其他任何内容,就可以保存。从一个版本控制系统到另一个版本控制系统,其细节变化很大。
进行新提交:Git 的索引
在其他版本控制系统 (VCS) 中,您检查了一些提交,现在您有一堆有用的文件。您对这些文件进行更改,准备就绪后,您告诉 VCS:提交这些文件。它会找到你所做的,然后提交。其中一些 VCS 在这里可能慢得令人难以忍受。 Git 倾向于 blazing-fast。它以一定的价格获得这种速度。这个价格有(对您)有用的附带好处,但它肯定令人困惑,是时候了解它的全部内容了。
每个文件只有两个个副本,Git存储三个。三者之一是 当前提交 中的冻结(和 de-duplicated)文件。您选择了一些要处理的提交,因此具有丑陋的大哈希 ID 的提交是 当前提交,并且该提交具有所有文件的快照。
在另一端,可以说,Git 已将提交的所有这些文件 复制到 到你的 work-tree 中。这些是您可以用来做任何事情的普通日常文件。
在这两个副本之间,不过,Git保留了第三个“副本”。 “复制”一词在这里用引号引起来,因为第三个是冻结的 形式 ,并且是 de-duplicated 之前的形式。最初,所有这些都与提交中的副本匹配。这个额外的副本位于 Git 称为 index 或 暂存区 的地方,或者有时——现在很少见——缓存。所有这三个名称都是为了同一件事。它有三个名字可能是因为 index 没有任何意义,而且 cache 太具体了:名字 staging area体现了它的作用。
当您进行新提交时,Git 使用 Git 索引中的 ready-to-go 文件。由于它们采用正确的 格式 ,Git 可以非常快速地进行新的提交。但这意味着,如果您 更改 work-tree 中的副本 ,您你必须告诉 Git 替换 索引副本。索引中的副本采用冻结 格式 但不在提交中,因此实际上并未冻结。
git add
命令是您执行所有这些操作的方式。 git 添加 <em>file</em>
的真正意思是 使 file
的索引副本与 work-tree复制。 Git 将用新索引副本替换旧索引副本,在 git add
时压缩和 de-duplicating 文件,使其准备好提交。这意味着不是 git commit
变慢,而是 git add
变慢了——但你只需要对你 更改过的 的文件执行此操作,所以事实并非如此慢。
反过来,所有这些都意味着索引(或暂存区)中的内容实际上是您提议的下一次提交。当 Git 提取该提交时,Git 从 当前提交 中填充了它。 Git 将提交复制到 Git 的索引 和 您的 work-tree。现在您已经更改了内容,或者可能添加甚至删除了一些文件,您必须更新 Git 的索引以匹配。如果您只想删除内容,可以使用 git add
或 git rm
来执行此操作。这会更新 Git 的索引,因此您建议的下一次提交。
进行新提交:更新 b运行ch 名称
在我们继续讨论合并的工作原理之前,让我们花点时间观察每天定期 non-merge 提交的过程——换句话说,只提交一次 parent。我们从一个简单的线性提交链开始,以某个特定的具有哈希 ID 的最后一次提交结束:
... <-F <-G <-H <-- somebranch (HEAD)
这里 H
代表链中最后一次提交的实际哈希 ID。 Commit H
包含快照和元数据。 Git 可以通过哈希 ID 找到提交,哈希 ID 在 name somebranch
中。在提交H
的元数据中,Git可以找到更早的哈希ID——parent——提交G
,所以使用somebranch
找到 H
让 Git 找到 G
。 Commit G
当然有一个快照和元数据,元数据包括其 parent F
的哈希 ID。这又有一个快照和一个哈希 ID。所以只给定 b运行ch 名称,Git 可以找到 all 提交。
让我们创建第二个指向同一提交的 b运行ch 名称:
...--F--G--H <-- somebranch (HEAD), anotherbranch
我们仍在使用提交 H
。这里的 (HEAD)
告诉我们我们正在使用 name somebranch
来查找提交 H
。如果你 git checkout anotherbranch
,我们将开始使用 name anotherbranch
,但仍然会找到提交 H
:
...--F--G--H <-- somebranch, anotherbranch (HEAD)
如果您现在修改一些文件并 git add
它们将更新后的文件放入 Git 的索引中,您现在可以 运行 git commit
创建一个新的犯罪。 Git 将:
- 收集元数据,例如您的姓名和电子邮件地址以及当前日期和时间(以及您的提交消息,有时还有更多内容);
- 使用 当前 提交的哈希 ID 作为新提交的 parent;
- 使用 Git 的索引 / staging-area 中的任何内容作为新提交的快照;
- 写出新提交,为其分配新的哈希 ID,但我们只称其为
I
。
还有一步,但让我们现在绘制提交 I
:
...--F--G--H
\
I
现在让我们添加 b运行ch 名称,在我们注意到 git commit
的 last 步骤是 Git 写入之后current b运行ch name 中的新哈希 ID — 带有附加 HEAD
:
...--F--G--H <-- somebranch
\
I <-- anotherbranch (HEAD)
现在 H
之前的提交都在两个 b运行 上,而新提交 I
仅在 anotherbranch
.
正在合并
我们现在准备处理 Git 的合并操作。让我们首先考虑这些事实:
- 每个提交都包含一个快照——不是更改,只是一个快照。
- 如果我们选择一些其他的——通常是更早的——快照,我们可以将快照变成变化,并且比较两者。这只是一个简单的 spot the difference 游戏,由计算机完成所有定位。
- 合并的目标是合并更改。
我们从这样的情况开始:
I--J <-- branch1 (HEAD)
/
...--G--H
\
K--L <-- branch2
也就是说,name branch1
选择了一些提交——我们称之为 J
——并且 name branch2
选择其他一些我们称之为 L
的提交。我们现在使用的是 J
:这就是 Git 的索引和我们的 work-tree.
当我们运行git merge branch2
时,Git使用HEAD
定位我们的提交J
,并且使用我们作为参数提供的名称 branch2
来定位 他们的 提交 L
。但是现在 Git 需要弄清楚 我们改变了什么 和 他们改变了什么 。这意味着 Git 必须找到一些更早的提交。
正确 较早的提交并不总是很明显,但是 Git neds 是在 both b运行ches 上的提交。提交 H
在两个 b运行 上;提交 G
以及更早的任何内容也是如此。不过,按理说,best 提交可能是“最接近终点”的提交:也就是说,提交 H
比提交 G
,因为将 H
中的快照与以后的提交进行比较可能会发现 比 G
中的快照或更早的任何内容进行比较 的更改更少。
我们称此“正确提交”为合并基础,无论如何,Git 在这里自行找到合并基础。在这个简单的例子中,很容易看出 Git 将选择提交 H
。在更复杂的图表中,使用 git merge-base --all
可能是查看 Git 正在挑选的唯一明智的方法。5
找到我们改变的,Git现在运行s,实际上:
git diff --find-renames <hash-of-H> <hash-of-J> # what we changed
一个非常相似的命令发现他们改变了什么:
git diff --find-renames <hash-of-H> <hash-of-L> # what they changed
Git 同样,在这一点上,实际上 将所有三个提交读入索引 .
5Git这里使用了lowest common ancestor算法。当应用于有向无环图时,可能会有不止一个 LCA。 --all
到 git merge-base --all
告诉此命令打印出所有 LCA。不同的合并策略可能只使用一个合并基础,也可能使用所有合并基础;此处不赘述。
合并真正发生在索引中
早些时候我们看到索引有每个文件的副本。这是索引的正常状态,但在合并期间,索引实际上会扩展。而不是一个副本,它包含三个:
- Git 将合并基础提交读入“插槽 1”处的索引。
- Git 将我们的提交读入“插槽 2”处的索引。
- Git 将他们的提交读入“插槽 3”处的索引。
也就是说,如果合并基础、我们的提交和他们的提交都有一个 README.md
个文件,那么索引现在有 三个 README.md
个文件在里面。我们可以 name 这些使用 digit 和一些冒号,用一些 Git 命令,6 例如:
git show :1:README.md # view the merge base copy
git show :2:README.md # view the `--ours` copy
这对三个提交中的每个文件重复。有些提交可能 没有 所有文件名,上面的 git diff --find-renames
可能会发现,从提交 H
到 L
,它们 重命名 一些文件,例如;在这种情况下,索引条目有点棘手。或者,也许我们或他们删除了一个文件,或者添加了一个全新的文件,在这种情况下,没有 slot-1 条目但有 slot-2 或 slot-3 条目。 你有这些情况,所以我们不能忽略它们。但是它们有点复杂,所以现在,我们将忽略它们.剩下的就很简单了:
- 如果所有三个索引条目都匹配,则根本没有人动过该文件。三个副本中的任何一个都将作为合并文件。
- 如果三个副本中的两个匹配,那么我们更改了文件(基础和他们的匹配)并且 Git 应该使用我们的,或者他们更改了文件(基础和我们的匹配)并且 Git 应该使用他们的,或者我们和他们对文件进行了 相同的更改 (他们的和我们的匹配)并且 Git 应该使用这些版本中的任何一个。所以 Git 将使用我们的或他们的,以 与 不匹配的基数为准。
- 如果所有三个副本都不同,Git必须做一些实际工作。
如果 Git 能够从上面找出要使用的版本,Git 只需将该版本从这些编号为非零的插槽移动到编号为零的插槽,并擦除 higher-numbered 插槽。 slot-zero 条目是正常的“这是文件,准备提交”副本。所以 那个 文件现在已经解决了。 Git 将选定的副本也放入您的 work-tree。
如果没有,Git 继续尝试 low-level 合并 文件。7
6大多数可以使用散列 ID 的 Git 命令可以使用 Git 解析为散列 ID 的名称。此解决方案是通过 the gitrevisions documentation 中概述的规则完成的。因此 git rev-parse :1:README.md
打印出该文件的内部 blob 哈希 ID。当使用 git show
或 git cat-file -p
时,您可以给它哈希 ID 或名称;他们将根据需要通过内部 rev-parse 运行 名称。
7您可以指定 merge driver 而不是让 Git 使用其内置的。这也变得有些复杂。
Low-level 合并正常完成 diff-hunk-by-diff-hunk
假设我们在索引中有 run.py
的三个不同版本,从 base 到我们的差异表示对第 42 行进行更改,而从 base 到他们的差异表示进行不同的更改到第 54 行。Git 将简单地进行 both 更改和一个将它们添加到文件的合并基础副本。
如果我们和他们更改了相同的线路,Git 将比较我们都使用的作为他们的新替代品的线路。如果我们的替换匹配,Git 将复制一份此更改。
如果我们和他们更改了相同的行但更改为不同的文本,Git将在此声明合并冲突文件,并将 ar运行ge 合并在中间停止。扩展的 (-X
) 选项可以告诉 Git 毕竟不要停止(通过告诉它有利于我们或他们的),但我们将跳过这些。
如果合并我们的更改和他们的更改后没有合并冲突,Git 将像往常一样将结果放入索引槽零和您的 work-tree。此文件也已解决。
如果Git无法解决冲突,低级合并代码将在将三个文件合并到您的文件时写入其best-effort work-tree。 (Git 的索引发生了什么,好吧,我们将把它留到下一节。)work-tree 文件将在 没有[=的地方使用合并的更改599=] 冲突,以及它们发生的地方,将包含来自合并“两侧”的行。如果将 merge.conflictStyle
设置为 diff3
,冲突区域将包含文件 merge-base 版本中的相应行。我喜欢总是设置这个选项;我发现由此产生的冲突更容易阅读。
高层冲突,也称为树冲突
在上一节中,我谈到了 Git 如何处理某些文件的三个版本中的冲突,其中有合并基础副本、--ours
副本和 --theirs
复制,这三个都不同。但是让我们看看这些情况会发生什么:
假设他们删除了一个文件而我们没有对它做任何事情。 Git 应该用这个做什么? Git的答案是进行删除: Git通过清空所有索引槽(包括槽零)并使文件在合并结果中被删除确保该文件不在您的 work-tree.
中假设我们删除了一个文件,而他们没有对它做任何事情。 Git 以同样的方式处理。
假设我们或他们删除了一个文件,而他们或我们——另一方——修改该文件。 Git 的答案是声明一个 合并冲突 并且只在索引中保留三个副本中的两个。 Git 称之为 modify/delete 冲突 .
假设我们重命名一个文件(不改变其内容),他们没有改变它,或者改变了它但没有重命名. Git 的答案是结合这两个更改:如果有的话,接受他们的更改,并使用我们的新文件名。这同样适用于他们重命名它而我们没有。如果我们都修改文件,并且低级代码可以合并 content 更改,Git 通过重命名和合并的内容更改来解析文件。
如果我们都重命名文件,但使用不同的新名称,Git 将此称为 rename/rename 冲突.
如果我们都创建了 all-new 文件,内容不同但名称相同,Git 称之为 add/add 冲突.
这些涉及文件名或整个文件的冲突creation/deletion都是高级别或树冲突,因为它们不' 涉及 low-level 内容冲突。我们甚至可以获得高级和低级冲突,例如,rename/rename 冲突 加 低级冲突;但这里的要点是,如果我们确实遇到这些高级冲突之一,则扩展(-X ours
和 -X theirs
)选项无效:这些选项仅由 low-level 处理合并代码。8
在任何情况下,如果 Git 确实 因合并冲突而停止,它 在其索引 [=599] 中留下非零槽号条目=].这使得两个或三个输入文件可用于 git mergetool
等命令,并为 git mergetool
留下足够的痕迹来诊断高级冲突,例如 modify/delete 冲突。
8将来可能会有一些更高级的高级冲突处理程序允许某些 -X
选项。但是今天没有了。
你的工作:收拾残局
我们现在知道 Git 留下了什么样的烂摊子:
- 某些 work-tree 文件中可能存在合并冲突。
- 如果是这种情况,索引会为所有这些条目保留一些非零槽号条目。
- 对于没有低级合并冲突的文件,索引可能包含非零槽号条目。 All 合并冲突通过这些非零槽号显示在索引中; low-level 也显示为带有冲突标记的部分合并 work-tree 文件。
你的工作是完成合并。您可以按照自己喜欢的方式执行此操作。
您不必使用 higher-numbered 索引条目,但如果您愿意,
git mergetool
为您提供了一种方便的方式来访问它们,这不需要摸索git show :1:file.ext
、git show :2:file.ext
和git show :3:file.ext
以及许多临时文件:git mergetool
为您完成。您不必使用文件的 work-tree 副本及其部分合并。
你做必须运行
git add
或git rm
,但git mergetool
可以做到对你也是。要标记冲突已解决,您将完全删除索引副本——这意味着最终提交将根本没有文件——或者将正确的合并结果写入索引槽零。
您的具体情况
在您的特定情况下,您列出了 __pycache/*.pyc
个文件(其中四个)和另外两个文件,app.db
和 run.py
。
__pycache__
文件几乎不应该在 Git 存储库中。您对其中一个的合并冲突表明合并的一侧 - --ours
侧,即合并基础与 HEAD
- 修改了文件,而合并的另一侧删除了文件, 在 git merge
运行.
git diff
中
此处正确的解决办法是接受他们的更改,即完全删除文件。对于 git mergetool
,那么,答案将是 d
:使用删除,而不是保留修改后的文件。
对于app.db
,正确的结果可能不是你的文件,但也可能不是他们的文件。正确的结果可能是这两个文件的某种组合。如果数据库是二进制的,Git 的简单 newline-based 文本替换规则,用于组合两个 git diff
并将组合的更改应用到合并基础副本,根本不起作用.如何生成正确的最终 app.db
副本取决于您,但我们假设有一个魔术命令可以读取两个 app.db
输入文件并生成正确的结果。你可能 运行:
git show :2:app.db > app.db.mine
git show :3:app.db > app.db.theirs
magic-combiner -o app.db app.db.mine app.db.theirs
组合它们并将正确的组合数据写入 app.db
。现在您的 work-tree 副本是您要提交的内容,您只需 运行:
git add app.db
这会擦除三个编号的插槽(:1:app.db
、:2:app.db
和 :3:app.db
都消失了)并复制(压缩和冻结以及 de-duplicates)当前app.db
进入索引-slot-zero.
对于 run.py
,也许您应该查看他们的文件和您的文件,也许还应该查看合并基础版本,在编辑器或合并工具或您将使用的任何工具中找出正确的合并结果是。或者 work-tree 副本与 Git 的合并尝试足以让您弄清楚该文件中应该包含什么。 git mergetool
命令可能会为您提供一种 运行 合并所有三个输入的工具的方法。在大多数情况下,我更喜欢在编辑器中编辑 run.py
并弄清楚(使用 merge.conflictStyle
的 diff3
设置中的三个部分)。
如果您有 git mergetool
运行 工具,那么:
- 要么
git mergetool
对这个工具了解很多,并且可以相信它会退出,状态代码显示“全部合并,使用结果”或“未合并,不使用”和git mergetool
是否会 运行git add
是否正确;或 git mergetool
对这个工具了解不够,但是会运行然后问你是否应该使用结果。
如果 git mergetool
使用结果,它会自己做 git add run.py
。如果没有,您仍然在索引中拥有三个副本;您可以在您最喜欢的编辑器中打开 run.py
,查看它,然后决定它是否全部正确或需要更多更改。您可以 运行 测试等等。
即使git mergetool
确实添加了文件,您仍然可以查看并运行测试。 解析文件只是意味着设置索引,以便Git认为合并完成。
提交最终合并
如果 Git 认为合并是自己完成的,Git 将进行新的合并提交:
I--J
/ \
...--G--H M <-- branch1 (HEAD)
\ /
K--L <-- branch2
这个合并提交有一个哈希 ID,就像任何提交一样。它有一个快照,就像任何提交一样。它有元数据,就像任何提交一样——有一个区别:它将提交 J
列为其 第一个 parent,因此 M
指向 J
,但随后它还将提交 L
列为其 第二个 parent,因此 M
也指向 L
.现在提交 H-I-J-M
在 branch1
上(加上更早的提交)但 H-K-L-M
也是如此(加上更早的提交)。所以现在所有以前只在 branch2
上的提交,都在两个 b运行 上。新提交 M
仅在 branch1
上,和往常一样,是 b运行ch 的新提示:Git 写入了 M
的哈希 ID进入 name branch1
.
如果 Git 由于合并冲突而没有自行进行合并提交,您:
- 收拾残局,然后 [=516=运行
git merge --continue
或 git commit
,9
和 Git 现在将像以前一样使用两个 parent 进行合并提交 M
。或者,您可以 运行:
git merge --abort
擦除索引(好吧,将其重置为匹配 J
,真的)并将您的 work-tree 放回匹配的提交 J
,您将回到您在开始合并之前所处的情况。 (你为解决合并所做的任何工作都消失了,所以在这里要小心一点!)
9所有 git merge --continue
真正做的是确保您处于合并的中间,然后 运行 git commit
。所以它有点安全,因为如果您认为您处于冲突合并中,但您不知何故提前中止或完成了它,它不会做任何事情。通常在那种情况下 git commit
也会告诉您没有什么可提交的,所以这很少是重要的。