首页 > 解决方案 > Git合并过程忽略文件

问题描述

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

标签: gitgit-extensions

解决方案


我记得几天前在此合并之前,我进行了另一次合并,该合并完成了我通过选择 option 解决的冲突keep ours。也许这个选项保存在某个地方,现在它确实有副作用。

我不能与其他界面(可以做这种事情)交谈,但命令行 Git 不保存合并策略和策略选项。然而,你已经合并的事实意味着你不是从你认为你开始的地方开始的。

首先,让我们做一个简单的说明:“合并”并不意味着“相同”。如果是这样,我们根本不会为分支而烦恼。假设你和我都从v1.1软件开始,我对我的副本进行了一些更改,而你对你的进行了一些更改。如果我从你那里得到你的提交并运行:

$ git merge yours

这用你所做的代替了我所做的,我可能对此不太高兴!

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

合并基地

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

$ git clone <url>

我们开始工作并做出承诺。究竟什么提交?正如任何一本优秀的 Git 书籍都会告诉你的,提交是你制作快照时所有源代码的快照,以及一些元数据:谁制作了快照,何时制作,为什么制作,以及——这对 Git 的内部操作至关重要—就在您制作快照之前,您正在使用哪个提交。

每个提交都有自己唯一的哈希 ID,例如b7bd9486b055c3f967a870311e704e3bb0654e4f. 这些十六进制数字串很大、很丑陋,而且人类无法使用。所以我们让 Git我们记住它们。每个提交不仅有自己唯一的哈希 ID,每个提交还记得我们之前签出的提交的哈希 ID 。我们和 Git 使用分支名称来记住最终提交。

当我们进行新的提交时,我们的新提交会获得一个新的又大又丑的 ID,但它也会记住哪个提交分支上的最后一个提交。然后 Git 将新提交的大丑 ID 写入分支名称,以便名称记住新的而不是旧的。

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

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

其中名称 master记住了 commit 的 ID H。CommitH记住了 commit 的 ID G,它记住了 commit 的 ID F,以此类推。这些内部链接,我们可以像这样画成向后的箭头,总是向后,从提交到提交:提交知道他们的祖先,但不知道他们的后代。一旦提交,就无法更改任何提交,因此无法更改提交以记住其子级,但子级也永远不会忘记其父级。

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

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

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

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

同时其他人做出一两次提交:

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

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

如果我们现在——我们中的任何一个人——去合并我们的工作,我们首先要获取其他人的提交。结果是一个看起来像上面的图表。然后我们git checkout一个分支,例如master,并运行一个合并命令,例如git merge other

Git 现在通过图形返回——使用从每个分支尖端开始的向后箭头——以找到合并基础。在这种情况下,这很清楚:它是 commit H。然后,Git 实际上运行了两个 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在于它有两个父级,而不仅仅是一个。Commit L,合并提交,记住是将所有“从到”与“从到”结合起来的正确方法。HIHK

合并基地,再次

现在假设你和我都继续工作。我在之后做出新的提交K,而你在之后做出新的提交L

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

如果你现在git merge other 再次运行,Git 必须找到你最近一次提交N与我最近一次提交的合并基础Q。这次的合并基地不是 H!我们从两者开始,N然后Q向后工作以找到最近的共享提交。从 开始Q,只有一条向后的路径,通过POKJH等等。

但是,从 开始N,我们可以在提交时双向使用LN, M, L,然后 I K1 所以现在是第一个共享提交K

git merge这次要做的是针对两个分支提示提交进行差异提交KN以及Q. 同时,我们之前的合并 commitL是我们告诉 Git处理与我们工作结合的正确方法。K

那么,假设这些新文件被添加到 commitsJK中,但L因为我们选择了“保留我们的”选项而不在其中。K从to的差异N会说:删除这些此处的文件。K从到 的差异Q可能会或可能不会修改这些新文件。我们可以猜测,在这种情况下,它不会修改它们,因为如果它修改了,我们会遇到新的合并冲突,Git 告诉我们我们 删除了文件,并且它们 改变了它们。如果他们没有更改它们,Git 会假设我们的“删除”是正确的答案,并保持它们被删除。


1请记住,内部箭头总是向后指向——就像我在这里绘制它们的方式一样向左。我们可以从LK,但不能从KL


该怎么办

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

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

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

然后运行git merge <hash of other commit>,在这种情况下将是 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

或者,如果您确定要为新文件之外的所有内容“保留我们的”,您可以将额外的文件复制进去,git add然后提交。

处理问题的第二种策略是让它全部消失:丢弃提交L(错误的合并)每个后续提交。这显然会让你重新做一堆工作。如果您将这些提交(无论他们的哈希 ID 是什么)提供给其他任何人,它还有另一个可怕的副作用:其他人仍然拥有这些提交。您也必须让他们丢弃这些提交副本。否则他们会不断回来。

要使用第二种(在大多数情况下效果不佳)策略,您可以使用git reset命令。由于这通常是处理问题的错误方法,因此我将在此停止,但请参阅如何将 Git 存储库恢复为先前的提交


推荐阅读