Git 合并过程忽略文件

Git merge process ignores files

有两个分支:branch A 有一些文件,branch B 有例如名为 FolderB 的文件夹。当我将分支 B 合并到分支 A 时,提交的暂存文件中没有 "FolderB"。这对我来说是一个意想不到的行为。我记得几天前,在这次合并之前,我进行了另一次合并,该合并以冲突结束,我通过选择选项 keep ours 解决了冲突。也许这个选项保存在某个地方,现在它产生了副作用。如果是,我在哪里可以删除这些设置?

通常,当一个文件被 git 进程 忽略 时,这是因为该文件与 .gitignore 文件的其中一行相匹配。

如果您确定 FolderB 应该是您的存储库的一部分,请确保分支 A 和 B 的 .gitignore 文件不包含 FolderB。还要确保 FolderB 尚未完全合并到分支 A.

I remember that several days ago before this merging I did another merge that finished with conflict that I resolved by selecting option keep ours. Maybe this option saved in somewhere and now it does side effect.

我不能与其他接口(可以做这种事情)对话,但是 command-line Git 不保存合并策略和 strategy-options。但是,您 已经合并 ​​ 的事实意味着您并没有从您认为的起点开始。

首先简单说明一下:"merge"不等于"make same"。如果是这样,我们根本就不会为 b运行ches 而烦恼。假设你和我都从软件的 v1.1 开始,我对我的副本进行了一些更改,你对你的进行了一些更改。如果我然后得到你的提交和 运行:

$ git merge yours

并且这个取代了我用所做的,我可能不会很高兴!

因此,git merge 所做的是找到一些共同的起点—Git 称其为合并基础 提交——并找出你和他们,无论 "they" 是谁,从那时起都做了什么。但这意味着您必须了解如何 Git找到这个共同的起点。

合并基地

让我们看看正常的提交过程。我们首先克隆一些现有的存​​储库:

$ git clone <url>

然后我们开始工作并进行提交。 究竟是什么提交?正如任何 good Git book 会告诉您的那样,提交是您 制作 快照时所有源的 快照 带有一些元数据:谁制作了快照,何时制作快照,为什么制作快照,以及——这对 Git 的内部操作至关重要——提交你 were 工作,就在你制作之前快照。

每个提交都有自己唯一的哈希 ID,例如 b7bd9486b055c3f967a870311e704e3bb0654e4f。这些十六进制数字串又大又丑,人类无法使用。所以我们Git为了我们记住了他们。每个提交不仅有自己唯一的哈希 ID,每个提交 记住我们在 之前 签出的提交的哈希 ID。我们——和 Git——使用 b运行ch name 来记住 final 提交。

当我们进行新的提交时,我们的新提交会得到一个新的丑陋的大 ID,但它还会记住哪个提交 b运行 上的最后一次提交通道然后Git将新commit的big ugly ID写入b运行ch name,让name记住新的而不是旧的。

结果是我们从一些以 提示 结尾的提交链开始,如下所示:

... <-F <-G <-H   <--master

其中 name master 记住提交的 ID H。 CommitH记住commitG的ID,它记住commitF的ID,以此类推。这些内部链接,我们可以像这样画成 backwards-pointing 箭头,总是向后走,从 child 提交到 parent commit:commit知其祖而不知其后。一旦做出,任何提交都不能更改,因此不能更改提交以记住它们的 children,但是 child 也不会忘记它的 parent.

所以,假设你克隆存储库,我克隆存储库,我们都有:

...--F--G--H   <-- master

现在你进行一两次新的提交:

...--F--G--H--I   <-- master

同时其他人做出一两次承诺:

...--F--G--H--I   <-- master
            \
             J--K   <-- other

(每个提交都有自己唯一的 ID,这些大写字母只是代表真实的 ID。我们将 运行 在 26 次提交后用完字母。这就是为什么 Git 使用一些东西更大更丑。)

如果我们现在——我们中的任何一个——去合并我们的工作,我们首先获得另一个人的提交。结果是一个看起来和上面一样的图表。然后我们git checkout一个b运行ch,比如master,运行一个合并命令,比如git merge other.

Git 现在通过图表返回 - 使用从每个 b运行ch 尖端开始的 backwards-pointing 箭头 - 找到合并基础。在这种情况下,这很清楚:它是提交 H。 Git 然后 运行s,实际上, 两个 git diff 命令:

git diff --find-renames <hash-of-H> <hash-of-I>   # what we did on master
git diff --find-renames <hash-of-H> <hash-of-K>   # what they did on other

Git现在的工作是合并这些更改,将它们应用到H中的快照,并制作一个新的从结果中合并提交。如果一切顺利,Git 会自行完成此操作。否则,您会遇到合并冲突, 必须正确解决它们。您告诉 Git 您已完成此操作,然后 Git 进行新的合并提交:

...--F--G--H--I---L   <-- master (HEAD)
            \    /
             J--K   <-- other

这个合并提交 L 的特别之处在于它有 两个 parent,而不是只有一个。提交 L,合并提交,记住 this 正确的 方法,可以将所有内容 "from H to I" 与所有内容 "from H to K".

合并基地,再次

现在假设你我都继续工作。我在 K 之后进行新提交,而你在 L 之后进行新提交

...--F--G--H--I---L--M--N   <-- master (HEAD)
            \    /
             J--K--O--P--Q   <-- other

如果您现在 运行 git merge other 再次,Git 必须找到最新提交的合并基础 N我最近的提交 Q。这次合并基数是不是H!我们从 NQ 开始,然后向后查找最近的 shared 提交。从Q开始,向后只有一条路,通过POKJH,依此类推。

N 开始,但是,我们可以在提交 L两种 方式:NML,然后两者 I K.1 所以第一个 shared 提交现在是 K!

这次 git merge 将做的是 diff commit K 针对两个 b运行ch tip 提交 NQ。同时,提交 L,我们之前的合并,是我们告诉 Git 的 正确的方法 来处理 K 与我们的工作相结合。

那么,假设这些新文件是在提交 JK 中添加的,但不在 L 中,因为我们选择了 "keep ours" 选项。从 KN 的差异会说:删除这些文件。KQ 的差异可能会也可能不会修改那些新文件。我们可以猜测,在这种情况下,它不会修改它们,因为如果修改了,我们会得到一个新的合并冲突,Git 告诉我们 we 删除了文件,他们更改了它们。如果他们没有更改它们,Git 假定我们的 "delete" 是正确答案,并保留它们。


1记住,内部箭头总是指向后方——left-ish就像我在这里画的那样。我们可以从 LK,但不能从 KL


怎么办

如果这是正在发生的事情(根据我的经验,确实如此),真正的问题是你之前的合并——我们在上面绘制为 L 的提交——是错误的:它 删除 文件。但是我们已经注意到您不能更改任何较早的提交。

有两种解决问题的策略。首先,通常也是最好的,是承认错误并将其留在原地:将错误的合并保留在原处,但添加一个新的提交 now 来存储 更正了 快照。我们可以通过多种方式做到这一点。所有这些都涉及大量工作。在大多数情况下,我的首选方法是 re-perform 合并 但这次要正确执行:创建一个新的 b运行ch 指向之前的提交不正确的合并:

                ............<-- rework (HEAD)
               .
...--F--G--H--I---L--M--N   <-- master
            \    /
             J--K--O--P--Q   <-- other

然后是 运行 git merge <hash of other commit>,在本例中是提交 K 的散列。你会得到和上次一样的合并冲突,这次你可以正确解决。

正确解决并提交后,您可以运行 git diff <hash-of-original-merge> <hash-of-corrected-merge> 查看现在需要添加哪些更改来更正问题。例如:

$ git diff <hash-of-L> <hash-of-new-merge> > /tmp/patch
$ git checkout master
$ git apply /tmp/patch

或者,如果您确定要 "keep ours" 所有内容 除了 新文件,您可以将额外的文件复制到 git add他们,并提交。

处理该问题的第二个策略是让它全部消失:在每个后续提交中抛出提交L(错误合并)。这显然让你 re-do 工作量很大。它还有另一个可怕的副作用,如果你已经将这些提交(无论它们的哈希 ID 是什么)提供给其他任何人:那些其他人仍然有提交。您还必须让 他们 丢弃这些提交的 他们 副本。否则他们会继续回来。

要使用第二种(在大多数情况下不太好)策略,您可以使用 git reset 命令。由于这通常是处理问题的错误方法,我将在这里停止,但请参阅How to revert a Git repository to a previous commit