git合并具有不同目录结构的分支

jme*_*kow 23 git merge

我对git有些新意,我已经使用它好几个月了,我很乐于完成大部分的基本任务.所以......我认为是时候承担一些更复杂的任务了.在我的工作中,我们有一些人在使用旧代码来更新它,这涉及实际的代码工作并更新目录结构以使其更加模块化.我的问题是这两件事可以在并行分支中完成,然后合并或重新组合.我的直觉说不,因为dir重组是重命名,git通过添加新文件并删除旧文件来重命名(至少这是我理解它的方式).但我想确定一下.
这是场景:父分支看起来像:

??? a.txt
??? b.txt
??? c.txt
Run Code Online (Sandbox Code Playgroud)

然后我们分支两个说,branchA和branchB.在branchB中我们修改结构:

??? lib
?   ??? a.txt
?   ??? b.txt
??? test
    ??? c.txt
Run Code Online (Sandbox Code Playgroud)

然后在branchA中我们更新a,b和c.

有没有办法将branchA中完成的更改与branchB中的新结构合并?想到rebase,但是,我不认为lib/a.txt实际上是在git mv之后连接到a.txt ...

詹姆森

tor*_*rek 26

首先,简短说明:您可以随时尝试合并,然后将其退出,看看它的作用:

$ git checkout master
Switched to branch 'master'
$ git status
Run Code Online (Sandbox Code Playgroud)

(当变化不好时,确保它从失败的合并中恢复干净)

$ git merge feature
Run Code Online (Sandbox Code Playgroud)

如果合并失败:

$ git merge --abort
Run Code Online (Sandbox Code Playgroud)

如果自动合并成功,但您还不想保留它:

$ git reset --hard HEAD^
Run Code Online (Sandbox Code Playgroud)

(请记住,这HEAD^是当前提交的第一个父级,并且合并的第一个父级是"合并之前的内容".因此,如果合并有效,HEAD^则是合并之前的提交.)


这是一个简单的方法,用于找出重命名git merge将自动检测的内容.

  1. 确保diff.renamelimit10并且diff.renamestrue:

    $ git config --get diff.renamelimit
    0
    $ git config --get diff.renames
    true
    
    Run Code Online (Sandbox Code Playgroud)

    如果这些尚未设置,请设置它们.(这会影响以下diff步骤.)

  2. 选择要合并的分支,以及要合并的分支.也就是说,你将git checkout master; git merge feature很快做一些事情; 我们需要知道这两个名字.找到它们之间的合并基础:

    $ into=master from=feature
    $ base=$(git merge-base $into $from); echo $base
    
    Run Code Online (Sandbox Code Playgroud)

    你应该看到一些40个字符的SHA-1,就像ae47361...这里一样.(随意打出来master,并feature取代$into$from无处不在这里.我现在用的变量,所以,这是一个"处方",而不是一个"榜样".)

  3. 比较两者的合并基数,$into$from查看哪些文件被检测为"重命名":

    $ git diff --name-status $base $into
    R100    fileB   fileB.renamed
    $ git diff --name-status $base $from
    R100    fileC   fileD
    
    Run Code Online (Sandbox Code Playgroud)

(您可能希望运行这些差异并将输出保存到两个文件中,然后仔细阅读文件.旁注:您可以使用特殊语法获得第三个差异的效果master...feature:这里的三个点表示"找到合并基础".)

两个输出部分有文件的列表Added,Deleted,Modified,Renamed,等等(例子中只有两个重命名,用100%匹配).

既然$intomaster,第一个列表是git认为已经发生的事情master.(当你合并时,这些是git"想要保留"的变化feature.)

同时,$fromfeature,所以第二个列表是git认为发生的事情feature.(master当你进行合并时,这些是git想要"现在添加到" 的更改.)

此时,必须做一堆工作:

  • 标记的文件R,git将检测为重命名.
  • 如果R两个分支中的两个列表相同,那么您可能都很好(但无论如何都要阅读).如果R第一个列表中的s不在第二个...井中,请参见下文.
  • 当您运行git checkout master; git merge feature(或git checkout $into; git merge $from)git时,将执行第二个列表中显示的重命名,以便"添加这些更改" master.
  • 在任何情况下,将其与您希望 git检测为重命名的文件进行比较.寻找DA你输入想要有显示为R条目:这些发生时,在其中一个分支,你不仅重命名的文件,而且还改变了内容,以至于混帐不再检测到重命名.

如果第二个列表没有显示您想要查看的所有内容,那么您将需要帮助git out.请参阅下面的更长描述.

如果第一个列表具有不在第二个列表中的重命名,则这可能完全无害,或者可能导致"不必要的"合并冲突并错过真正合并的机会.Git将假设您打算保留此重命名,并且还要查看merge-from分支中发生的事情($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.
Run Code Online (Sandbox Code Playgroud)

这里的问题不是git以其新名称保留了该文件master(我们probalby 想要的); 这就是git可能错过了合并分支中所做更改的机会feature.

更糟糕 - 这可能是一个错误 - 如果新名称出现在merge-from分支中feature,但git认为它是一个新文件,git只留下工作树中文件的合并版本.发出的消息是相同的.在这里,我进行了一些更改master以重命名fileBfileE,并且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.
Run Code Online (Sandbox Code Playgroud)

请注意可能具有误导性的消息fileE deleted in feature.Git正在打印名称(名称的master版本); 这就是它认为你"想要"看到的名字.但它是fileB被"删除"的文件feature,取而代之的是全新的fileE.

(git-imerge,如下所述,可能能够处理这种特殊情况.)


1还可以单独设置merge.renameLimit(limit在源中拼写小写,但这些配置变量不区分大小写).将这些设置为0会告诉git使用"合适的默认值",随着CPU速度的提高,这种情况已经发生了多年的变化.如果未设置单独的合并重命名限制,git将使用diff rename限制,如果未设置或为0,则再次使用合适的默认值.如果设置不同,则merge和diff将在不同情况下检测重命名.

您现在还可以在递归合并中设置"重命名阈值" -Xrename-threshold=,例如-Xrename-threshold=50%.这里的用法与git diff's -M选项相同.该选项首先出现在git 1.7.4中.


假设你在分支机构master,你做git merge 12345467git merge otherbranch.这是git的作用:

  1. 找到合并基础:git merge-base master 1234567git merge-base master otherbranch.

    这会产生一个commit-ID.我们称这个ID B为"Base".Git现在有三个特定的提交ID:B合并基础; 当前分支的提示的提交ID master; 和你给它的提交ID,1234567或分支的提示otherbranch.为了完整性,让我们根据提交图来绘制这些; 让我们说它看起来像这样:

    A - B - C - D - E       <-- master
          \
            F - G - H - I   <-- otherbranch
    
    Run Code Online (Sandbox Code Playgroud)

    如果一切顺利,git将生成一个包含EI作为其两个父项的合并提交,但我们希望将重点放在生成的工作树而不是提交图上.

  2. 鉴于这三个提交(B EI),git计算两个差异,一个la git diff:

    git diff B E
    git diff B I
    
    Run Code Online (Sandbox Code Playgroud)

    第一个是在上面进行的更改集,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
Run Code Online (Sandbox Code Playgroud)

(在这种情况下,我只是重命名fileBfileB.txt但是检测也可以跨目录工作.)让我们注意这可以通过git diff --name-status输出方便地表示:

    $ git diff --name-status B otherbranch
    R071    fileB   fileB.txt
Run Code Online (Sandbox Code Playgroud)

(我还应该注意到我已经diff.renames设置了truediff.renamelimit = 0在我的全局git配置中.)

  1. Git的现在试图改变从结合BI(上otherbranch)从进入改变BE(上branch).

如果 git能够检测到lib/a.txt重命名的a.txt,它将连接它们.(并且您可以通过执行a来预览它git diff.)在这种情况下,自动合并结果可能是您想要的,或者足够接近.

如果没有,它不会.

当自动重命名检测失败时,有一种方法可以逐步分解提交(或者可能已经足够分解).例如,假设序列中F G H I提交,一个步骤(也许G)简单地重命名a.txtlib/a.txt,等步骤(F,H和/或I)作出许多其他变化a.txt(以任何名义)以欺骗的git到没有意识到该文件是重命名.你在这里可以做的是增加合并的数量,以便git可以"看到"重命名.让我们假设为简单起见,这F不会改变a.txtG重命名它,这样从DIFF BG显示重命名.我们能做的是首次合并提交G:

git checkout master; git merge otherbranch~2
Run Code Online (Sandbox Code Playgroud)

一旦合并完成和Git已经从更名a.txtlib/a.txt树为新的合并提交的分支branch,我们做了第二次合并的提交,使HI:

git merge otherbranch
Run Code Online (Sandbox Code Playgroud)

这两步合并导致git"做正确的事".

在最极端的情况下,增量的提交提交合并序列(手动操作非常痛苦)将获取可以拾取的所有内容.幸运的是,有人已经为您编写了这个"增量合并"计划:git-imerge.我没有试过这个,但这是针对疑难病例的明显答案.