简体   繁体   English

git merge不会丢失merge-to分支上的新文件

[英]git merge without losing new files on merge-to branch

I have two branches that have a common parent in the recent past. 我有两个分支,最近有一个共同的父母。 Lets call them feature-br-A and feature-br-B. 我们称它们为Feature-br-A和feature-br-B。

Development work has continued in parallel on both branches. 两个分支的开发工作并行进行。 feature-br-B has mostly bug fixes. Feature-br-B主要修复了错误。 feature-br-A has some bug fixes and at least one new feature. feature-br-A修复了一些错误,并至少提供了一项新功能。

Now as come the time to bring the bug fixes made in feature-br-B forward into feature-br-A. 现在是时候将功能br-B中的错误修复程序带到功能br-A中了。

git checkout feature-br-A
git merge feature-br-B
<< conflicts >>

The few file conflicts are simple and easy to resolve. 很少有文件冲突,很容易解决。 But there are a whole bunch of files that were added only to feature-br-A that are marked for deletion. 但是,有一堆只添加到Feature-br-A的文件被标记为删除。 And there are a set of few more files only added to feature-br-A that are in conflict that marked 'deleted by them'. 另外,还有一组仅添加到Feature-br-A且有冲突的文件标记为“已被它们删除”。

How can I merge the changes made on feature-br-B into feature-br-A without losing all the changes made already on feature-br-A? 如何将功能br-B上所做的更改合并到功能br-A中,而又不丢失已经在功能br-A上进行的所有更改?

I tried 我试过了

git merge -Xours --no-commit feature-br-B

but that created the same issue. 但这造成了同样的问题。

My other concern is if this happening to added files, is this also happening to the other files that were changed to add features to feature-br-A? 我的另一个担心是,如果添加文件中发生了这种情况,更改为向Feature-br-A添加功能的其他文件是否也发生了这种情况?

I suppose I can unstage the deletions from the merge, but it doesn't seem right that it should be deleting the new files at all. 我想我可以取消合并中的删除操作,但是似乎应该删除所有新文件似乎并不正确。

git merge starts by finding the merge base between your current (or "local" or --ours ) commit and your target (or "remote" or --theirs or "other") commit. git merge从找到当前(或“ local”或--ours )提交与目标(或“ remote”或--theirs或“ other”)提交之间的合并基础开始。 In this case, your local commit is the tip-most commit on feature-br-A , and the "other" commit is the tip-most commit on feature-br-B . 在这种情况下,本地提交是feature-br-A上最尖端的提交,而“ other”提交是feature-br-B上最尖端的提交。

I don't have enough information here to identify the merge base, but you (and your Git) do. 我在这里没有足够的信息来确定合并基础,但是您(和您的Git)可以。 It's the commit at which the two branches "join up". 这是两个分支“联合”的提交。 In some cases there may be more than one merge base commit, but probably there is just the one. 在某些情况下,可能有多个合并基础提交,但可能只有一个。 If we draw a diagram of your two branches—note that git log --graph --decorate feature-br-A feature-br-B will draw this too, in a somewhat different form 1 —we will see something like this: 如果我们绘制您的两个分支的图表(请注意, git log --graph --decorate feature-br-A feature-br-B也会以略有不同的形式1绘制此图),我们将看到如下所示:

...--o--*--o--o--o--o   <-- HEAD -> feature-br-A
         \
          o--o--o--o    <-- feature-br-B

Note that the two branch names point to the two commits that are being merged. 请注意,两个分支名称指向正在合并的两个提交。 The commit I marked with * is where the graph "joins up"; 我用*标记的提交是图形“连接”的地方; this is the merge base. 这是合并基础。

From the merge base, run two diffs 在合并基础上,运行两个差异

Having now identified the merge base commit, git merge does its thing by running two git diff commands. 现在已经确定了合并基础提交, git merge通过运行两个git diff命令来完成它的工作。 Simplified a bit, these are: 简化一下,这些是:

git diff base feature-br-A
git diff base feature-br-B

These two diffs give Git—and you—all the information needed to perform a normal merge (adding strategies or strategy options changes the merge, of course). 这两个差异为Git和您提供了执行常规合并所需的所有信息(当然,添加策略或策略选项会更改合并)。 They tell you what you did—what you changed from base to the tip of feature-br-A —and what they did: what "they", whoever they are, changed from base to the tip of feature-br-B . 他们告诉您做了什么(从基础feature-br-A的小技巧),以及他们做了什么:无论是谁,“他们”从基础feature-br-B的小技巧都变成了什么。

Conflicts 矛盾冲突

Conflicts occur whenever Git itself cannot reconcile "what you did" with "what they did". 只要Git本身无法协调“您所做的”与“他们所做的”,就会发生冲突。

You mentioned some regular conflicts, and then: 您提到了一些常规冲突,然后:

The few file conflicts are simple and easy to resolve. 很少有文件冲突,很容易解决。 But there are a whole bunch of files that were added only to feature-br-A that are marked for deletion. 但是,有一堆只添加到Feature-br-A的文件被标记为删除。 And there are a set of few more files only added to feature-br-A that are in conflict that marked 'deleted by them'. 另外,还有一组仅添加到Feature-br-A且有冲突的文件标记为“已被它们删除”。

Files added to feature-br-A (as compared to base ) would not be marked for deletion. 添加feature-br-A (与base相比)不会被标记为删除。 Those are part of "what you did". 这些是“您做了什么”的一部分。 They are only in conflict if they are also added to feature-br-B (as compared to base ). 只有将它们添加到feature-br-B (与base相比),它们才会发生冲突。

Files deleted in feature-br-B (as compared to base ) are also not in conflict, with one exception. feature-br-B 删除的文件(与base相比)也没有冲突,只有一个例外。 Suppose that, for path P , you changed something in feature-br-A , so that comparing base and feature-br-A , file P is modified. 假设,对于路径P ,您在feature-br-A中进行了更改 ,从而比较了basefeature-br-A ,文件P被修改了。 Suppose further that in comparing base and feature-br-B , Git sees P as deleted. 进一步假设在比较basefeature-br-B ,Git将P视为已删除。 It cannot combine your change ("modify some lines") with theirs ("delete the entire file"). 它无法将您的更改(“修改某些行”)与其更改(“删除整个文件”)组合在一起。 In this case it leaves you with your modified file checked-out but in "needs merge" state. 在这种情况下,您可以将已修改的文件检出,但处于“需要合并”状态。

Technical stuff: slot numbers in the index / staging-area 技术资料:索引/暂存区中的插槽编号

In order to do the merge work, Git makes use of a special feature of the index / staging-area. 为了完成合并工作,Git利用了索引/临时区域的特殊功能。 Normally, the index contains just one entry per file (path) name: the state of the file as it will be in the next commit, once you make it. 通常,索引在每个文件(路径)名称中只包含一个条目:一旦创建,文件在下一次提交时的状态。

That is, git commit starts by turning the staging-area into a tree—more precisely, a series of trees, one for each directory, with a single top-level Git tree object for the top of the work tree—of whatever files are staged. 也就是说, git commit首先将暂存区变成一棵树(更确切地说,是一系列树,每个目录一个树,工作树顶部有一个顶级Git树对象),无论文件是上演。 Then Git makes a commit object that refers to this tree object and updates the branch pointer. 然后,Git创建一个引用该树对象的提交对象,并更新分支指针。 That completes the process of committing, and is why git commit is so fast: there's very little work to do as all the files are already in the staging area. 这样就完成了提交过程,这就是为什么git commit这么快的原因:要做的工作很少,因为所有文件都已经在暂存区中。

During a merge, however, Git treats the staging area differently. 但是,在合并期间,Git会以不同的方式对待过渡区域。 The index now contains three entries per path—or really, up to three. 目前该指数包含每三个条目路径还是真的, 最多三个。 These entries are numbered: stage slot 1 is the version of the file that is in the merge base, slot 2 is the "local" or --ours version, and slot 3 is the other or --theirs version. 这些条目的编号是:阶段插槽1是合并库中文件的版本,插槽2是“本地”或- --ours版本,插槽3是另一个或- --theirs版本。 If a file was created in one or both branch tips, slot 1 is empty. 如果在一个或两个分支提示中创建了文件,则插槽1为空。 If a file was deleted in one branch tip, one of slots 2 or 3 is empty. 如果在一个分支提示中删除了文件,则插槽2或3中的一个为空。 (If the file is deleted in both tips, Git resolves everything for you and does not leave a conflicted entry.) (如果两个技巧都删除了该文件,Git会为您解决所有问题,并且不会留下冲突的条目。)

To resolve something, you git add (or git rm ) the path after editing the work-tree file as needed. 要解决问题,请在根据需要编辑工作树文件后git add (或git rm )路径。 This empties out slots 1, 2, and 3, writing the file into slot 0—which is where normal, not-conflicted files live. 这将清空插槽1、2和3,将文件写入插槽0,这是正常的,没有冲突的文件所在的位置。 Once there are no slot 1-to-3 entries, the merge is complete and you may commit the staging area. 一旦没有插槽1到3条目,合并就完成了,您可以提交暂存区域。

More technical stuff: renames 更多技术资料:重命名

Git attempts to guess at which file(s), if any, were renamed. Git尝试猜测哪个文件(如果有)被重命名。 It does this when doing the two git diff steps. 在执行两个git diff步骤时会执行此操作。 If you run git diff manually yourself, you may need to tell Git to use the same "find renames" settings that it uses for merges. 如果您自己手动运行git diff ,则可能需要告诉Git使用与合并相同的“查找重命名”设置。 By default, Git uses the equivalent of a plain git diff -M (50% similarity). 默认情况下,Git使用纯git diff -M (50%相似度)的等效项。 You can control the merge rename threshold via various options and git config values. 您可以通过各种选项和git config值控制合并重命名阈值。 Most of the time the defaults are fine and you can simply leave them alone. 在大多数情况下,默认设置是可以的,您可以直接保留它们。

It's worth remembering that Git is doing this kind of rename detection, though, regardless of whether you try tweaking the settings. 值得记住的是,无论您是否尝试调整设置,Git都进行了这种重命名检测。 This is important because it can show up in your conflicts. 这很重要,因为它可能会出现在您的冲突中。 Suppose, for instance, that Git decides that lib/foo.py was renamed to misc/zap.py in the change-set from base to feature-br-A , but was not renamed in the change-set from base to feature-br-B . 假设,例如,了Git决定lib/foo.py更名为misc/zap.py在改变设置从底部feature-br-A但在改变设置从底部没有更名feature-br-B In this case, Git will try to combine the two changes by keeping the rename, and also applying "their" changes it sees between lib/foo.py in base and lib/foo.py in feature-br-B , to the file whose name is misc/zap.py in feature-br-A . 在这种情况下,Git将尝试通过保留重命名,并将在基数的 lib/foo.pyfeature-br-B lib/foo.py之间看到的“它们”更改应用于文件,来合并这两个更改在feature-br-A名称为misc/zap.py

If Git is correct—if this file really was renamed like this—that's exactly what you want. 如果Git是正确的-如果此文件确实是这样重命名的-这正是您想要的。 But if Git is wrong, it will mis-merge things. 但是,如果Git错误,它将合并错误。 You will need to correct it by hand, or perhaps retry the merge with different rename detection values. 您将需要手动更正它,或者使用不同的重命名检测值重试合并。

The general rule here is that Git keeps all the rename changes made in your current (local) commit—the tip of feature-br-A in this case—and sets up the index's stage slots so that their contents reflect the contents of the files as (Git thinks) they were named in the base and feature-br-B (other) commits. 这里的一般规则是,Git保留在当前(本地)提交中所做的所有重命名更改(在这种情况下为feature-br-A的技巧),并设置索引的阶段槽,以使它们的内容反映文件的内容正如(Git认为的)它们是在基本名称中命名的,而feature-br-B (其他)名称是提交的。 That's why you may want to refer to the files using the slot numbers, or the more convenient --ours and --theirs flags to git checkout . 这就是为什么您可能想使用插槽号或更方便的--ours--theirs标志--theirs git checkout来引用文件的原因

Bottom line 底线

If the automatic merge fails, you must fix things up by hand. 如果自动合并失败,则必须手动修复。 Using git diff may help. 使用git diff可能会有所帮助。 Setting merge.conflictstyle to diff3 may also help (I find it very useful). merge.conflictstyle设置为diff3也可能有所帮助(我发现它非常有用)。 Note that as long as there is only a single merge base commit (search for "virtual merge base" for more complex cases), you can simplify the two git diff commands. 请注意,只要只有一个合并基础提交(对于更复杂的情况搜索“虚拟合并基础”),就可以简化两个git diff命令。 In this case, your current commit is feature-br-A and you are merging feature-br-B , so these two commands will get you the two diffs: 在这种情况下,您当前的提交是feature-br-A并且您正在合并feature-br-B ,因此这两个命令将为您提供两个差异:

git diff feature-br-B...feature-br-A # "our" changes
git diff feature-br-A...feature-br-B # "their" changes

Note that there are three dots here. 请注意,这里有三个点。 In this case, git diff finds the merge base commit (or, if there are multiple merge bases, picks one more or less randomly) and compares it to the commit named on the right . 在这种情况下, git diff查找合并基础提交(或者,如果有多个合并基础,则随机选择一个或多个)并将其与右侧命名的提交进行比较。 Hence the first git diff command finds "our" changes, and the second finds "theirs". 因此,第一个git diff命令找到“我们的”更改,第二个找到“他们的”更改。

In any case, as with any merge, you must check the results of the merge. 无论如何,与任何合并一样,您必须检查合并的结果。 For simple merges with few changes, a quick glance ("that looks right") and/or passing whatever test suite(s) you have is sufficient. 对于只需很少更改的简单合并,快速浏览(“看起来正确”)和/或通过您拥有的任何测试套件就足够了。 For complicated merges, test suites are a very good idea , and a more careful eyeball examination of the merge result is not a bad idea either. 对于复杂的合并,测试套件是一个很好的主意 ,对合并结果进行更仔细的眼光检查也不是坏主意。


1 This is like any other git log but it adds graph information. 1这和其他git log但是会添加图形信息。 To get a more succinct drawing, add --oneline . 要获得更简洁的图形,请添加--oneline To get just the commits on the two branches, excluding commits on both branches, use the three-dot syntax. 获取两个分支上的提交(不包括两个分支上的提交),请使用三点语法。 To add the merge base(s) and other boundary commits to this drawing, add --boundary . 要将合并基础和其他边界提交添加到此图形,请添加--boundary Hence a more complete but shorter version is: 因此,一个更完整但更短的版本是:

git log --graph --oneline --decorate --boundary feature-br-A...feature-br-B

Note that there are three —not two, not any other number, but exactly three —dots between the branch names in this syntax. 请注意,在此语法中,分支名称之间有三个点 (不是两个,不是任何其他数字,而是恰好三个)

Problem(1) How can I merge the changes made on feature-br-B into feature-br-A without losing all the changes made already on feature-br-A? 问题(1)如何在不丢失对功能br-A所做的所有更改的情况下,将对功能br-B进行的更改合并到功能br-A中?

The problem causing the issue was that at some point some of the original commits made in feature-br-A were once merged into feature-br-B and then deleted by a subsequent commit. 导致该问题的问题是,有时在特征br-A中进行的某些原始提交曾经合并到特征br-B中,然后由后续的提交删除。 git is dutifully replicating the delete. git忠实地复制了删除操作。 It's not clear to me at the moment that if the original merge was reverted instead of file delete's that git would do the same thing, but I suspect that it would still be the case. 目前我还不清楚,如果恢复原始合并而不是删除文件,git会做同样的事情,但是我怀疑情况仍然如此。

The error made seems to prevent automatically merging from feature-br-B into feature-br-A. 所犯的错误似乎阻止了功能br-B自动合并到功能br-A。 The only suggestion I have to solve this is to identify the commits made on feature-br-B and move them into feature-br-A with git cherry-pick. 我要解决的唯一建议是,识别在feature-br-B上所做的提交,并通过git cherry-pick将其移入Feature-br-A。

I plan on doing some more tests now that I can explain the deleted files from the merge. 现在,我计划进行更多测试,以解释合并中已删除的文件。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM