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

时间:2014-03-22 19:42:15

标签: git merge

我对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 ...

詹姆森

1 个答案:

答案 0 :(得分:25)

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

$ git checkout master
Switched to branch 'master'
$ git status

(确保在没有变化的情况下,它会在失败的合并中退出清除)

$ git merge feature

如果合并失败:

$ git merge --abort

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

$ git reset --hard HEAD^

(请记住HEAD^是当前提交的第一个父级,并且合并的第一个父级是"合并之前的内容是什么"。因此,如果合并有效,{ {1}}是合并之前的提交。)


这是一个简单的方法,用于找出重命名HEAD^将自动检测到的内容。

  1. 确保git merge 1 diff.renamelimit0diff.renames

    true

    如果这些尚未设置,请设置它们。 (这会影响下面的$ git config --get diff.renamelimit 0 $ git config --get diff.renames true 步骤。)

  2. 选择您要合并的分支,以及您要合并的分支。也就是说,你很快会做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 ;而不是"示例"。)

  3. 将合并基础与$from$into进行比较,以查看哪些文件被检测为"重命名":

    $from
  4. (您可能希望运行这些差异并将输出保存到两个文件中,然后仔细阅读文件。旁注:您可以使用特殊语法$ 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
    • 在任何情况下,请将此与您想要 git检测为重命名的文件进行比较。查找您想要显示为master条目的DA条目:当您在其中一个分支中不仅重命名时,会发生这些条目该文件,但也改变了内容,以至于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上,并且mastergit merge 12345467。这就是git的作用:

    1. 找到合并基础:git merge otherbranchgit 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两个父项,但我们希望将重点放在生成的工作树上而不是提交图。

    2. 鉴于这三次提交(I BE),git会计算两个差异,即I

      git diff

      第一个是在git diff B E git diff B I 上进行的更改,第二个是在branch上进行的更改集,在这种情况下。

      如果您手动运行otherbranch,则可以设置&#34;相似度阈值&#34;使用git diff进行重命名检测(请参阅上面的内容,以便在合并期间进行设置)。 Git的默认合并将自动重命名检测设置为50%,这是您在没有-M选项且-M设置为diff.renames的情况下获得的。

    3. 如果文件是&#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.renamestrue。)

      1. Git现在尝试将diff.renamelimit = 0BI)的更改合并到otherbranchB的更改中({{1} }})。
      2. 如果 git能够检测到从E重命名branch,它将连接它们。 (并且您可以通过执行lib/a.txt来预览它。)在这种情况下,自动合并结果可能是您想要的,或者足够接近。

        如果不是,那就赢了。

        当自动重命名检测失败时,可以逐步分解提交(或者可能已经足够分解)。例如,假设在a.txt git diff F G提交的序列中,一步(可能H)只需将I重命名为{{ 1}}和其他步骤(Ga.txt和/或lib/a.txt)对F(无论名称)进行了如此多的其他更改,以便将git变为非意识到文件已重命名。你可以在这里做的是增加合并的数量,这样git可以&#34;看到&#34;重命名。为简单起见,我们假设H不会更改Ia.txt重命名它,以便从Fa.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。我没有试过这个,但它是针对疑难病例的明显答案。