有两个分支:branch A
包含一些文件,branch B
包含例如名为FolderB
的文件夹。当我将分支B合并到分支A中时,提交的暂存文件中没有“ FolderB”。对我来说这是意外的行为。我记得几天前,在进行合并之前,我进行了另一次合并,但最终由于选择了选项keep ours
而解决了冲突。也许此选项保存在某个地方,但现在确实有副作用。如果是,我在哪里可以删除这些设置?
答案 0 :(得分:2)
我还记得几天前,在进行合并之前,我进行了另一次合并,但最终由于冲突而解决,通过选择选项
keep ours
解决了这一冲突。也许此选项保存在某个地方,现在它会产生副作用。
我无法与其他界面交谈(可以执行此类操作),但是命令行Git不会保存合并策略和策略选项。但是,您已经已经合并的事实意味着您并没有从您认为的起点开始。
首先,让我们做一个简单的注释:“合并”并不意味着“使相同”。如果是这样,我们根本不会理会分支机构。假设您和我都从该软件的v1.1
开始,并且我对副本进行了一些更改,而您对您的副本进行了一些更改。如果然后我从您那里得到了您的提交并运行:
$ git merge yours
然后用这个替换了我所做的与您您所做的一样,我对此可能并不满意!
因此,git merge
所做的就是找到一些共同的起点(Git称这为合并基础提交),并找出您和他们的共同点,从那时起,无论“他们”是谁。但这意味着您必须理解 Git如何找到这个共同的起点。
让我们看一下正常的提交过程。我们首先克隆一些现有的存储库:
$ git clone <url>
,我们开始工作并进行提交。 到底是什么?正如任何good Git book会告诉您的那样,提交是您制作快照时所有源的快照以及一些元数据:快照,何时,为什么以及(这对Git的内部操作至关重要)-在您制作快照之前,使您正在工作。
每个提交都有自己的唯一哈希ID,例如b7bd9486b055c3f967a870311e704e3bb0654e4f
。这些十六进制数字字符串很大,很丑陋,人类无法使用。因此,我们让Git记住他们 for 。每个提交不仅具有自己唯一的哈希ID,而且每个 都会记住我们在之前签出的提交的哈希ID。我们和Git使用分支名称来记住最终提交。
当我们进行新的提交时,我们的新提交会获得一个新的丑陋的ID,但它也会记住哪个提交是分支上的最后一次提交。然后,Git将新提交的丑陋ID写入分支 name 中,以便该名称记住新的而不是旧的。
结果是我们从一些提交链开始,这些提交以这样的 tip 提交结束:
... <-F <-G <-H <--master
其中名称 master
记住提交H
的ID。提交H
记住提交G
的ID,它记住提交F
的ID,依此类推。这些内部链接可以像这样向后指向的箭头绘制,它们总是从 child commit到 parent commit向后退:commit知道他们的祖先,但不知道他们的祖先。子孙。一旦提交,就不能更改任何提交,因此无法更改提交以记住其子项,但是子项也不能忘记其父项。
因此,假设您克隆了存储库,而我也克隆了存储库,我们都有:
...--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使用从每个分支尖端开始的向后箭头,遍历图形以查找合并基础。在这种情况下,这很清楚:它是提交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
的特殊之处在于它具有两个父,而不是一个。提交L
(合并提交)时,请记住,此是将所有从“ H
到I
”的内容与一切都从“ H
到K
”。
现在假设您和我俩都继续工作。我在K
之后进行新的提交,而您在L
之后进行新的提交:
...--F--G--H--I---L--M--N <-- master (HEAD)
\ /
J--K--O--P--Q <-- other
如果您现在再次运行git merge other
,则Git必须找到最新提交N
与我最新提交Q
的合并库。这次的合并基础是 not H
!我们从N
和Q
开始,然后倒退以找到最新的 shared 提交。从Q
开始,只有一条向后的路径,依次经过P
,O
,K
,J
,H
等。
从N
开始,在提交L
时,我们可以两种方式:N
,M
,L
,然后是两者 I
和 K
。 1 因此,第一个 shared 提交是现在K
!
这次git merge
要做的是针对两个分支提示提交K
和N
的diff commit Q
。同时,提交L
(我们之前的合并)是我们告诉Git处理K
与我们的工作相结合的正确方法。
然后,假设这些新文件是在提交J
或K
中添加的,但不在L
中,因为我们选择了“保持我们的”选项。从K
到N
的差异表示:删除这些文件。从K
到Q
的差异可能会或可能不会修改这些新文件。文件。我们可以猜测,在这种情况下,它不会对其进行修改,因为如果这样做,则会出现新的合并冲突,而Git告诉我们我们 已删除 ,并且他们 更改了。如果他们没有更改,Git会认为我们的“删除”是正确的答案,并将其删除。
1 请记住,内部箭头始终指向后方,即我在此处绘制它们的方式。我们可以从L
到K
,但不能从K
到L
。
如果这是正在发生的事情(根据我的经验,是这样),则真正的问题是您先前的合并(我们在上面L
处绘制的提交)是错误的:它删除了文件。但是我们已经注意到,您不能更改任何更早的提交。
有两种解决问题的策略。第一个方法(通常是最好的方法)是确认错误并将其保留在适当的位置:将错误的合并保留在原处,但添加一个新的提交 now 来存储已纠正的 快照。我们可以通过多种方式来做到这一点。它们都涉及很多工作。在大多数情况下,我首选的方法是重新执行合并,但这次要正确执行:在错误的合并之前创建一个指向提交的新分支:
............<-- 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
或者,如果您确定要对所有除新文件以外的所有内容都“保留我们的文件”,则只需复制多余的文件,git add
,然后提交。 / p>
处理该问题的第二种策略是将其全部消除:丢弃commit L
(错误合并)和每一个后续的提交。显然,这使您需要重新做很多工作。如果您已经将这些提交(无论其哈希ID是什么)给了其他任何人,它也会带来另一个可怕的副作用:那些其他人仍然有这些提交。您还必须获得 them 来丢弃这些提交的他们的副本。否则他们会继续回来。
要使用第二种策略(在大多数情况下效果不佳),可以使用git reset
命令。由于这通常是处理问题的错误方法,因此我将在此处停止,但请参见How to revert a Git repository to a previous commit。
答案 1 :(得分:1)
通常,当一个文件被git进程忽略时,这是因为该文件与.gitignore
文件的其中一行匹配。
如果您确定FolderB
应该属于您的仓库,请确保分支A和分支B的.gitignore
文件都不包含FolderB
。另外,请确保FolderB
尚未完全合并到分支A中。