我对git有点新鲜,我已经使用它好几个月了,我很乐于完成大部分的基本任务。所以......我认为是时候承担一些更复杂的任务了。 在我的工作中,我们有一些人在使用旧代码来更新它,这涉及实际的代码工作并更新目录结构以使其更加模块化。我的问题是这两件事可以在并行分支中完成,然后合并或重新组合。我的直觉说不,因为dir重组是重命名,git通过添加新文件并删除旧文件来重命名(至少这是我理解它的方式)。但我想确定一下 这是一个场景: parent-branch看起来像:
├── a.txt
├── b.txt
├── c.txt
然后我们分支两个说,branchA和branchB。在branchB中我们修改结构:
├── lib
│ ├── a.txt
│ └── b.txt
└── test
└── c.txt
然后在branchA中我们更新a,b和c。
是否有人将branchA中完成的更改与branchB中的新结构合并? 想到rebase,但是,我不认为lib / a.txt实际上是在git mv之后连接到a.txt ...
詹姆森
答案 0 :(得分:25)
首先,简短说明:您始终可以尝试合并,然后将其退出,看看它的作用:
$ git checkout master
Switched to branch 'master'
$ git status
(确保在没有变化的情况下,它会在失败的合并中退出清除)
$ git merge feature
如果合并失败:
$ git merge --abort
如果自动合并成功,但您还不想保留它:
$ git reset --hard HEAD^
(请记住HEAD^
是当前提交的第一个父级,并且合并的第一个父级是"合并之前的内容是什么"。因此,如果合并有效,{ {1}}是合并之前的提交。)
这是一个简单的方法,用于找出重命名HEAD^
将自动检测到的内容。
确保git merge
1 为diff.renamelimit
且0
为diff.renames
:
true
如果这些尚未设置,请设置它们。 (这会影响下面的$ git config --get diff.renamelimit
0
$ git config --get diff.renames
true
步骤。)
选择您要合并的分支,以及您要合并的分支。也就是说,你很快会做diff
之类的事情。我们需要知道这两个名字。找到它们之间的合并基础:
git checkout master; git merge feature
你应该看到一些40个字符的SHA-1,比如$ into=master from=feature
$ base=$(git merge-base $into $from); echo $base
或者其他什么。 (请随意输入ae47361...
和master
,而不是feature
和$into
。我正在使用变量,这是一个"配方&#34 ;而不是"示例"。)
将合并基础与$from
和$into
进行比较,以查看哪些文件被检测为"重命名":
$from
(您可能希望运行这些差异并将输出保存到两个文件中,然后仔细阅读文件。旁注:您可以使用特殊语法$ git diff --name-status $base $into
R100 fileB fileB.renamed
$ git diff --name-status $base $from
R100 fileC fileD
获得第三个差异的效果:三个这里的点意味着"找到合并基础"。)
两个输出部分有一个文件列表master...feature
dded,A
eleted,D
odified,M
enamed等等(此示例只有两个重命名,100%匹配)。
由于R
为$into
,因此第一个列表是git认为已经在master
中发生的事情。 (当你合并master
时,这些是git"想要保留"的变化。)
同时,feature
为$from
,因此第二个列表是git认为在feature
中发生的事情。 (这些是git想要进行的更改"现在添加到feature
",当您进行合并时。)
此时,你必须做一大堆工作:
master
的文件,git将检测为已重命名。R
列表在两个分支中都相同,那么您可能都很好(但无论如何都要阅读)。如果第一个列表中有R
个不在第二个...井中,请参见下文。R
(或git checkout master; git merge feature
)时,git会执行第二个列表中显示的重命名,以便"添加这些更改"到git checkout $into; git merge $from
。master
条目的D
和A
条目:当您在其中一个分支中不仅重命名时,会发生这些条目该文件,但也改变了内容,以至于git不再检测到重命名。如果第二个列表没有显示您想要查看的所有内容,那么您将不得不帮忙。请参阅下面更详细的说明。
如果 first 列表中的重命名不在 second 中,则可能完全无害,或者可能导致“不必要” #34;合并冲突和错失真正合并的机会。 Git将假设您打算保留此重命名,并且还要查看合并来自分支(R
或$from
的情况)。如果原始文件在那里被修改,git将尝试将更改从那里带入重命名的文件。这可能就是你想要的。如果原始文件未在其中进行修改,则git没有任何内容可以引入并将文件单独保留。这也可能是你想要的。 "坏" case又是一个未检测到的重命名:git认为原始文件在分支feature
中被删除,并且创建了一个带有其他名称的新文件。
在这"坏" case,git会给你一个合并冲突。例如,它可能会说:
feature
这里的问题不是git在CONFLICT (rename/delete): newname deleted in feature and renamed in HEAD.
Version HEAD of newname left in tree.
Automatic merge failed; fix conflicts and then commit the result.
中以新名称保留了该文件(我们probalby 想要那个);它可能已经错过了合并分支master
中的更改的机会。
更糟 - 这可能是一个错误 - 如果新名称出现在合并来自分支feature
,但是git认为它是一个新文件,git只留下合并-into版本的工作树中的文件。发出的消息是相同的。在这里,我在feature
中进行了一些更改,以将master
重命名为fileB
,并在fileE
上确保git不会将更改检测为重命名:< / p>
feature
请注意可能具有误导性的消息$ git diff --name-status $base master
R100 fileB fileE
$ git diff --name-status $base feature
D fileB
R100 fileC fileD
A fileE
$ git checkout master; git merge feature
CONFLICT (rename/delete): fileE deleted in feature and renamed in HEAD.
Version HEAD of fileE left in tree.
Automatic merge failed; fix conflicts and then commit the result.
。 Git正在打印 new 名称(名称的fileE deleted in feature
版本);那个它相信你的名字&#34;想要&#34;查看。但是文件master
已删除&#34;在fileB
中,由全新的feature
替换。
(fileE
,如下所述,可能能够处理这种特殊情况。)
1 还有一个git-imerge
(在源代码中拼写为小写merge.renameLimit
,但这些配置变量不区分大小写),您可以单独设置。将这些设置为0告诉git使用&#34;一个合适的默认值&#34;,随着CPU变得更快,这些年来已经发生了变化。如果未设置单独的合并重命名限制,git将使用差异重命名限制,如果未设置或为0,则再次使用合适的默认值。如果以不同方式设置它们,merge和diff将在不同情况下检测重命名,虽然。
您现在也可以设置&#34;重命名阈值&#34;在与limit
的递归合并中,例如-Xrename-threshold=
。此处的用法与-Xrename-threshold=50%
&n; git diff
选项的用法相同。该选项首先出现在git 1.7.4中。
我们假设您在分支-M
上,并且master
或git merge 12345467
。这就是git的作用:
找到合并基础:git merge otherbranch
或git merge-base master 1234567
。
这会产生一个commit-ID。我们将该ID git merge-base master otherbranch
称为&#34; Base&#34;。 Git现在有三个特定的提交ID:B
,合并基础;当前分支B
的提示的提交ID;以及您提供的提交ID,master
或分支1234567
的提示。为了完整性,让我们根据提交图来绘制这些;让我们说它看起来像这样:
otherbranch
如果一切顺利,git将生成一个合并提交,其中包含A - B - C - D - E <-- master
\
F - G - H - I <-- otherbranch
和E
两个父项,但我们希望将重点放在生成的工作树上而不是提交图。
鉴于这三次提交(I
B
和E
),git会计算两个差异,即I
:
git diff
第一个是在git diff B E
git diff B I
上进行的更改,第二个是在branch
上进行的更改集,在这种情况下。
如果您手动运行otherbranch
,则可以设置&#34;相似度阈值&#34;使用git diff
进行重命名检测(请参阅上面的内容,以便在合并期间进行设置)。 Git的默认合并将自动重命名检测设置为50%,这是您在没有-M
选项且-M
设置为diff.renames
的情况下获得的。
如果文件是&#34;足够相似&#34; (和&#34;完全相同&#34;总是足够的),git将检测重命名:
true
(在这种情况下,我刚刚从 $ git diff B otherbranch # I tagged the merge-base `B`
diff --git a/fileB b/fileB.txt
similarity index 71%
rename from fileB
rename to fileB.txt
index cfe0655..478b6c5 100644
--- a/fileB
+++ b/fileB.txt
@@ -1,3 +1,4 @@
file B contains
several lines of
stuff.
+changeandrename
重命名为fileB
,但检测也适用于目录。)请注意,这可以通过fileB.txt
输出方便地表示:
git diff --name-status
(我还应该注意,我的全局git配置中 $ git diff --name-status B otherbranch
R071 fileB fileB.txt
设置为diff.renames
和true
。)
diff.renamelimit = 0
到B
(I
)的更改合并到otherbranch
到B
的更改中({{1} }})。 如果 git能够检测到从E
重命名branch
,它将连接它们。 (并且您可以通过执行lib/a.txt
来预览它。)在这种情况下,自动合并结果可能是您想要的,或者足够接近。
如果不是,那就赢了。
当自动重命名检测失败时,可以逐步分解提交(或者可能已经足够分解)。例如,假设在a.txt
git diff
F
G
提交的序列中,一步(可能H
)只需将I
重命名为{{ 1}}和其他步骤(G
,a.txt
和/或lib/a.txt
)对F
(无论名称)进行了如此多的其他更改,以便将git变为非意识到文件已重命名。你可以在这里做的是增加合并的数量,这样git可以&#34;看到&#34;重命名。为简单起见,我们假设H
不会更改I
并a.txt
重命名它,以便从F
到a.txt
的差异显示重命名。我们可以做的是首先合并提交G
:
B
一旦这个合并完成并且git已经从树中的G
重命名为G
,用于分支git checkout master; git merge otherbranch~2
上的新合并提交,我们会进行第二次合并以引入提交{ {1}}和a.txt
:
lib/a.txt
这两步合并导致git&#34;做正确的事情&#34;。
在最极端的情况下,增量的commit-by-commit合并序列(手动操作会非常痛苦)会拾取所有可以拾取的内容。幸运的是,有人已经写过这个&#34;增量合并&#34;适合您的计划:git-imerge
。我没有试过这个,但它是针对疑难病例的明显答案。