我对git有点陌生,我已经使用了几个月,而且Im可以轻松完成大多数基本任务。所以...我认为现在该承担一些更复杂的任务了。在我的工作中,我们有一些人在处理较旧的代码以对其进行更新,这涉及实际的代码工作以及将目录结构更新为更具模块化的方式。我的问题是,这两件事可以在并行分支中完成,然后合并还是重新建立基础。我的直觉说不,因为dir重组是一个重命名,而git是通过添加新文件并删除旧文件来重命名的(至少这是我的理解)。但是我想确定。
这是场景:父分支看起来像:
├── a.txt
├── b.txt
├── c.txt
然后我们分支两个说,branchA和branchB。在branchB中,我们修改结构:
├── lib
│ ├── a.txt
│ └── b.txt
└── test
└── c.txt
然后在branchA中更新a,b和c。
是否有某种方式可以将branchA中所做的更改与branchB中的新结构合并?想到了rebase,但是,我不认为git mv之后lib / a.txt实际上连接到a.txt。
詹姆森
首先,请注意:您可以随时尝试合并,然后将其退出,以查看其作用:
$ git checkout master
Switched to branch 'master'
$ git status
(确保它是干净的—在发生更改时退出失败的合并并不好玩)
$ git merge feature
如果合并失败:
$ git merge --abort
如果自动合并成功,但是您不想保留它:
$ git reset --hard HEAD^
(请记住,这HEAD^
是当前提交的第一个父级,并且合并的第一个父级是“合并之前的内容”。因此,如果合并有效,HEAD^
则是合并之前的提交。)
这是一个简单的配方,用于查找git merge
将自动检测到的重命名。
确保diff.renamelimit
1是0
和diff.renames
是true
:
$ git config --get diff.renamelimit
0
$ git config --get diff.renames
true
如果尚未通过这种方式进行设置,请进行设置。(这会影响diff
下面的步骤。)
选择您要合并的分支,以及您要合并的分支。就是说,您将要做一些类似的事情git checkout master; git merge feature
。我们需要在这里知道这两个名字。找到它们之间的合并基础:
$ into=master from=feature
$ base=$(git merge-base $into $from); echo $base
您应该会看到一些40个字符的SHA-1,就像ae47361...
这里的或类似的东西。(随意打出来master
,并feature
取代$into
和$from
无处不在这里。我现在用的变量,所以,这是一个“处方”,而不是“例子”。)
比较两者的合并基础$into
,$from
以查看哪些文件被检测为“重命名”:
$ git diff --name-status $base $into
R100 fileB fileB.renamed
$ git diff --name-status $base $from
R100 fileC fileD
(您可能希望将这些差异运行,并将输出保存到两个文件中,然后在以后仔细阅读这些文件。旁注:您可以使用特殊语法获得第三个差异的效果master...feature
:此处的三个点表示“查找合并基础“。)
两个输出部分有文件的列表A
dded,D
eleted,M
odified,R
enamed,等等(例子中只有两个重命名,用100%匹配)。
自从$into
是以来master
,第一个列表就是git认为已经在master
。(这些是合并时git“希望保留”的更改feature
。)
同时,$from
是feature
,因此第二个列表是git认为发生在的内容feature
。(这些是git希望master
在合并时“现在添加到”中的更改。)
在这一点上,您必须做很多工作:
R
git的文件将被检测为已重命名。R
两个分支中的两个列表相同,则可能一切都很好(但还是请继续阅读)。如果R
第一个列表中有s,而第二个列表中没有...,请参见下文。git checkout master; git merge feature
(或git checkout $into; git merge $from
)git时,将执行第二个列表中所示的重命名,以便将这些更改“添加”到中master
。D
和A
输入你想有显示为R
条目:这些发生时,在其中一个分支,你不仅重命名的文件,而且还这么多,混帐不再检测到重命名改变的内容。如果第二个列表没有显示您想要看到的所有内容,则需要帮助git out。请参阅下面的详细说明。
如果第一个列表的重命名不在第二个列表中,则这可能是完全无害的,或者可能导致“不必要的”合并冲突和错过真正合并的机会。Git是要假设你打算保留此重命名,并且也期待在合并,从发生了什么分支($from
或feature
在这种情况下)。如果原始文件在此处被修改,则git将尝试将此处的更改引入重命名的文件中。那可能就是你想要的。如果原始文件未在此处修改,则git没有任何内容可放入,并且将保留该文件。那可能也是您想要的。同样,“不良”情况是未检测到的重命名: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.
这里的问题不是混帐保留了文件在其新名称master
(我们probalby想那); 这是因为git可能错过了合并在branch中所做的更改的机会feature
。
更糟的是-这可能可以归类为错误-如果新名称出现在merge-from分支中feature
,但是git认为它是那里的新文件,则git在工作树中仅留下文件的merge-into版本。发出的消息是相同的。在这里,我对进行了一些更改,master
以重命名fileB
为fileE
,并在上feature
确保git不会将更改检测为重命名:
$ 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.
请注意可能引起误导的消息fileE deleted in feature
。Git正在打印新名称(名称的master
版本);那是它相信您“想要”看到的名称。但这是fileB
被“删除”的文件,被feature
一个全新的文件所代替fileE
。
(git-imerge
下面提到的,也许可以处理这种特殊情况。)
1还可以单独设置merge.renameLimit
(limit
在源代码中拼写为小写,但这些配置变量不区分大小写)。将这些设置为0会告诉git使用“适当的默认值”,随着CPU的速度越来越快,这些默认值多年来已经发生了变化。如果未设置单独的合并重命名限制,则git使用diff重命名限制,如果未设置或为0,则再次使用合适的默认值。如果设置不同,则merge和diff将在不同情况下检测重命名。
您现在还可以设置与递归合并“重命名门槛” -Xrename-threshold=
,例如-Xrename-threshold=50%
。此处的用法与git diff
的-M
选项相同。此选项首先出现在git 1.7.4中。
假设您在分支机构上master
,而您则是git merge 12345467
或git merge otherbranch
。这是git的作用:
找到merge-base:git merge-base master 1234567
或git merge-base master otherbranch
。
这将产生一个提交ID。我们将该ID称为B
“ Base”。Git现在具有三个特定的提交ID:B
合并基础;当前分支的尖端的提交ID master
; 以及您提供的提交ID1234567
或branch的提示otherbranch
。为了完整起见,我们只用提交图来绘制这些内容。假设它看起来像这样:
A - B - C - D - E <-- master
\
F - G - H - I <-- otherbranch
如果一切顺利,git将产生一个具有E
和I
作为其两个父对象的合并提交,但我们想将注意力集中在生成的工作树上,而不是提交图上。
给定这三个提交(B
E
和I
),git会计算两个差异,即la git diff
:
git diff B E
git diff B I
在这种情况下,第一个是对所做的更改集,branch
第二个是对进行的更改集otherbranch
。
如果您git diff
手动运行,则可以设置“相似性阈值”以用于重命名检测-M
(请参见上文,以在合并期间进行设置)。Git的默认合并将自动重命名检测设置为50%,这是您没有-M
选择并diff.renames
设置为的结果true
。
如果文件“足够相似”(并且“完全相同”就足够了),则git将检测到重命名:
$ 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 diff --name-status B otherbranch
R071 fileB fileB.txt
(我还要在这里注意,我已经在全局git config中diff.renames
设置了true
和diff.renamelimit = 0
。)
B
到I
(上otherbranch
)从进入改变B
到E
(上branch
)。如果git能够检测lib/a.txt
到a.txt
,则它将连接它们。(并且您可以通过执行预览来预览它git diff
。)在这种情况下,自动合并结果可能是您想要的或足够接近的结果。
如果不是,它将不会。
当自动重命名检测失败时,有一种方法可以逐步分解提交(或者可能已经被充分分解)。例如,假设序列中F
G
H
I
提交,一个步骤(也许G
)简单地重命名a.txt
来lib/a.txt
,等步骤(F
,H
和/或I
)作出许多其他变化a.txt
(以任何名义)以欺骗的git到没有意识到该文件是重命名。您可以在此处执行的操作是增加合并次数,以便git可以“查看”重命名。让我们假设为简单起见,这F
不会改变a.txt
和G
重命名它,使DIFF从B
到G
节目重命名。我们可以做的是先合并提交G
:
git checkout master; git merge otherbranch~2
一旦合并完成和Git已经从更名a.txt
到lib/a.txt
树为新的合并提交的分支branch
,我们做了第二次合并在提交带H
和I
:
git merge otherbranch
此两步合并使git“做正确的事”。
在最极端的情况下,一个增量的,逐个提交的合并序列(手动完成将非常痛苦)将拾取所有可以拾取的内容。幸运的是,已经有人为您编写了此“增量合并”程序:git-imerge
。我没有尝试过,但是对于困难的情况,这是显而易见的答案。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句