简体   繁体   English

在git的2个不同分支中合并2个同名文件

[英]Merging 2 files with the same name in 2 different branches in git

I currently have a file named test1 in a branch named branch1 created from master and another file also named test1 in a branch named branch2 also created from master .我现在有一个文件名为test1一个分支命名为branch1从创建master及其他文件也被命名test1一个分支命名为branch2也创造master What's gonna happen to the code written in both files if I merge the 2 branches in master?如果我在 master 中合并 2 个分支,在这两个文件中编写的代码会发生什么?

As amer answered (correctly), you'll get a merge conflict in one of the two merges.正如amer 回答(正确),您将在两个合并之一中遇到合并冲突。 You will have to do something to handle the merge conflict.您将不得不做一些事情来处理合并冲突。 What to do is up to you.做什么取决于你。 It's worth pointing out, though, why you will get one merge conflict, not two, and why the merge conflict happens.不过,值得指出的是,为什么会出现一个合并冲突,而不是两个,以及为什么会发生合并冲突。

Git's merging is not really about branches . Git 的合并实际上与分支无关。 It's about commits .这是关于commits Most of Git is really about commits, and git merge is no different here.大多数 Git 都是关于提交的,而git merge在这里也不例外。

Let's note here what a commit is and does.让我们在这里注意提交是什么和做什么。 Each commit has two parts: its data —a saved snapshot of all of your files—and its metadata , or information about the commit.每个提交都有两部分:它的数据——所有文件的保存快照——和它的元数据,或关于提交的信息。

  • The saved snapshot is pretty straightforward: if you clone a repository and use --no-checkout , you have an empty work-tree (no editable or usable copies of any file, yet).保存的快照非常简单:如果您克隆存储库并使用--no-checkout ,您将拥有一个空的工作树(还没有任何文件的可编辑或可用副本)。 Then you pick some commit—any commit anywhere—and tell Git to check out that particular commit, perhaps by its raw hash ID.然后你选择一些提交——任何地方的任何提交——并告诉 Git 检查那个特定的提交,也许是通过它的原始哈希 ID。 Now you have a copy of all the files as of the way they looked when whoever made that commit, made that commit.现在,您拥有了所有文件的副本,这些文件在进行该提交的人进行了该提交时的外观。

    In general, this is what git checkout or the new-in-Git-2.23 git switch command is for: you pick some commit and say get me all the files from that commit .通常,这就是git checkout或 new-in-Git-2.23 git switch命令的用途:您选择一些提交并说从该提交中获取所有文件 They go into your working tree or work-tree where you can see them and work on them.它们进入您的工作树工作树,您可以在其中看到它们并对其进行处理。 You can also put other, un-Git-ified files into your work-tree, eg, compiled files or outputs or whatever.您还可以将其他未经 Git 化的文件放入您的工作树中,例如,已编译的文件或输出或其他任何内容。 These files remain untracked (I won't go into the precise details here, but they're not in Git, they just sit there in your work-tree unless/until you remove them).这些文件仍然未被跟踪(我不会在这里详细介绍,但它们不在 Git 中,它们只是坐在你的工作树中,除非/直到删除它们)。

  • The metadata in a commit records things like who made it—their name and email address—and when they made it.提交中的元数据记录了诸如是谁做出的事情——他们的姓名和电子邮件地址——以及他们何时做出的。 This is the stuff that you see in git log output.这是您在git log输出中看到的内容。

    There is one part of the metadata, though, that is specifically for Git itself.但是,元数据的一部分是专门针对 Git 本身的。 Every commit has a unique hash ID, which every Git everywhere agrees on: that one hash ID is for that commit, and never for any other commit.每个提交都有一个唯一的哈希 ID,每个地方的每个 Git 都同意:一个哈希 ID 用于提交,而不用于任何其他提交。 So it's easy to tell if you have some commit: you just give its hash ID to Git, and your Git either has it, in which case, it has that commit, or your Git doesn't have it, in which case you'll have to find some other Git hat has it.所以很容易判断你是否有一些提交:你只需将它的哈希 ID 提供给 Git,你的 Git 要么拥有它,在这种情况下,它有那个提交,或者你的 Git 没有它,在这种情况下你'必须找到一些其他的 Git 帽子。

    Anyway, every commit stores, as part of its metadata, the hash ID of its previous or parent commit.无论如何,每个提交都将其先前或提交的哈希 ID 作为其元数据的一部分存储。 Most commits just have one.大多数提交只有一个。 Merge commits are slightly special because they have two parents (or more, but most just have two).合并提交有点特殊,因为它们有两个父级(或更多,但大多数只有两个)。

    These parent commits—or parents, for merges—are how Git stores history.这些父提交——或父提交,用于合并——是 Git 存储历史的方式。 Every snapshot is just a snapshot, but every snapshot also says: and my previous snapshot is ______ (fill in the blank with a hash ID).每个快照都只是一个快照,但每个快照还说:而我之前的快照是______ (用哈希ID填空)。 For merges, this is the first parent.对于合并,这是第一个父级。 For regular commits, it's the only parent (and hence also the first parent).对于常规提交,它是唯一的父级(因此也是第一个父级)。 So by going back to the first parent of each commit, Git can trace back what has happened over time.因此,通过返回到每个提交的第一个父级,Git 可以追溯随着时间的推移发生了什么。 Put up two snapshots: an old snapshot, on the left, and a new snapshot, on the right, and compare them.放两张快照:左边是旧快照,右边是新快照,然后比较它们。 What's different?有什么不同? That difference tells you what happened: what changed between the old one and the new one.这种差异告诉你发生了什么:旧的和新的之间发生了什么变化。

Once you know this about commits, we need only add one more thing to make branches work.一旦你知道了关于提交的这一点,我们只需要再添加一件事就可以让分支工作。 In Git, a branch name records the hash ID of the latest commit that we wish to call "part of the branch".在 Git 中,分支名称记录了我们希望称为“分支的一部分”的最新提交的哈希 ID。 That's mostly it—that's what a branch name does for us and for Git.基本上就是这样——这就是分支名称对我们和 Git 的作用。 It records the last commit.它记录了最后一次提交。 The commits themselves record the history.提交本身记录了历史。

So, given a series of commits, in a very small repository with just three commits and one branch name, we have, for instance:因此,给定一系列提交,在一个只有三个提交和一个分支名称的非常​​小的存储库中,我们有,例如:

A <-B <-C   <--master

The last commit is C .最后一次提交是C We have the name master to store its actual hash ID—which is really some big ugly random-looking string of letters and digits, that we never could guess.我们有名字master来存储它的实际哈希 ID——它实际上是一些大而难看的随机字母和数字字符串,我们永远猜不到。 Commit C itself stores the hash ID of earlier commit B , so that C points to B ;提交C本身存储了先前提交B的哈希 ID,因此 C指向B and commit B stores the hash ID of earlier commit A .提交B存储较早提交A的哈希 ID。

Commit A is special: it doesn't point back at all, because it was the very first commit and can't point back to an earlier commit.提交A很特别:它根本不指向回,因为它是第一次提交并且不能指向更早的提交。 That's how Git knows to stop going back: when it can't.这就是 Git 知道停止返回的方式:当它不能返回时。

We could, given a Git repository, go in and find all commits, and see which ones are the last one(s), but having a name that finds them quickly is faster.我们可以,给定一个 Git 存储库,进入并查找所有提交,并查看哪些是最后一个,但是拥有一个可以快速找到它们的名称会更快。 This also becomes important when we start having more than one branch.当我们开始拥有多个分支时,这一点也变得很重要。 Let's start with a small repository with about eight commits:让我们从一个包含大约 8 个提交的小型存储库开始:

...--G--H   <-- master

Now let's add a new branch.现在让我们添加一个新分支。 We'll start by having the new name also select commit H .我们将从让新名称选择 commit H We need a way to know which branch we're using , so we'll attach the special name HEAD to one of the branch names:我们需要一种方法来知道我们正在使用哪个分支,因此我们将特殊名称HEAD附加到其中一个分支名称:

...--G--H   <-- master, feature1 (HEAD)

Now we'll add a new commit, which gets some new random-looking hash ID that we will just call I :现在我们将添加一个新的提交,它获得一些新的随机哈希 ID,我们将称之为I

          I   <-- feature1 (HEAD)
         /
...--G--H   <-- master

When we add a new commit, Git automatically updates the branch name to point to the new commit.当我们添加新的提交时,Git 会自动更新分支名称以指向新的提交。 Which branch name gets updated?哪个分支名称得到更新? The one HEAD is attached-to.一个HEAD附加到。 The others all stay in place.其他人都留在原地。

Now all the commits through H are on both branches, and commit I is *only on feature1 .现在通过H所有提交都在两个分支上,并且提交I *仅在feature1 Let's make another commit, then make a new branch feature2 that selects commit H , and start using that one:让我们再做一次提交,然后创建一个新的分支feature2来选择提交H ,然后开始使用那个:

          I--J   <-- feature1
         /
...--G--H   <-- master, feature2 (HEAD)

Now let's add two commits to feature2 :现在让我们向feature2添加两个提交:

          I--J   <-- feature1
         /
...--G--H   <-- master
         \
          K--L   <-- feature2 (HEAD)

Now, suppose in commit I or J , we created a new file test1 , that's not yet in commit H .现在,假设在提交IJ ,我们创建了一个文件test1 ,该文件尚未在提交H Suppose that in commit K or L , we also created a new file named test1 .假设在提交KL ,我们创建了一个名为test1的新文件。

Merging合并

We're now going to merge the two features into master , one at a time.我们现在将这两个功能合并到master ,一次一个。 For no obvious reason, 1 we will use the --no-ff option:没有明显的原因, 1我们将使用--no-ff选项:

git checkout master
git merge --no-ff feature1

to achieve this.为达到这个。

When we git checkout master , we direct Git to:当我们git checkout master ,我们将 Git 引导至:

  1. extract the commit identified by the name master —commit H —to our work-tree (and to Git's index , which we won't get into here);将由名称master —commit H — 标识的提交提取到我们的工作树(以及 Git 的index ,我们不会在这里讨论); and
  2. set up our work-tree to match, which means removing the file test1 , which is in commit L —there is a saved snapshot file with that name—but is not in commit H .设置我们的工作树以匹配,这意味着删除文件test1 ,它在提交L ——有一个同名的已保存快照文件——但不在提交H

So, now we have:所以,现在我们有:

          I--J   <-- feature1
         /
...--G--H   <-- master (HEAD)
         \
          K--L   <-- feature2

and we're ready to run git merge --no-ff feature1 .我们已经准备好运行git merge --no-ff feature1

Git now finds three commits , not just two. Git 现在找到了三个提交,而不仅仅是两个。 The three commits of interest are:三个感兴趣的提交是:

  • Our current commit, HEAD .我们当前的提交, HEAD That's really easy to find because HEAD is attached to a branch name and the branch name points to the commit, so Git finds commit H .这真的很容易找到,因为HEAD附加到分支名称并且分支名称指向提交,因此 Git 找到提交H

  • The other commit we named.我们命名的另一个提交。 That's really easy too: we said to merge feature1 .这也很简单:我们说要合并feature1 The name feature1 identifies commit J .名称feature1标识提交J (Just look at the drawing!) (只看图!)

  • The merge base .合并基础 The merge base is defined by the commit graph , formed by the inter-connections from one commit to another.合并基础由提交图定义,由从一个提交到另一个提交的相互连接形成。 While we won't go into all the details, you can think of this as the best shared commit , ie, the best commit that's on both branches.虽然我们不会详细介绍所有细节,但您可以将其视为最佳共享提交,即两个分支上的最佳提交。 Starting from J —as found by name feature1 —we work backwards;J开始——如名称feature1我们向后工作; and starting from H , as found by master , we also work backwards.H开始,正如master发现的那样,我们也向后工作。 When some commit is on both branches, that's a shared commit.当一些提交在两个分支上时,这是一个共享提交。 The newest such commit—with newest not being properly defined here, but in most cases it's obvious—is usually the best commit.最新的这样的提交——这里没有正确定义最新的,但在大多数情况下很明显——通常是最好的提交。 2 2

In this case, the merge base is clearly commit H itself.在这种情况下,合并基础显然是提交H本身。


1 The merge I'll do here is the kind you would get on GitHub, using its "merge pull request" button. 1我在这里做的合并是你在 GitHub 上得到的那种,使用它的“合并拉取请求”按钮。 From the Git command line, you get more options.从 Git 命令行,您可以获得更多选项。 The --no-ff option forces command-line Git to make a real merge, instead of using its short-cut "fast forward not-really-a-merge" option. --no-ff选项强制命令行 Git 进行真正的合并,而不是使用其快捷方式“快进非真正合并”选项。

2 Technically, what Git is doing is finding the Lowest Common Ancestor (LCA) in a directed graph. 2从技术上讲,Git 所做的是在有向图中找到最低公共祖先 (LCA)。 In a tree , there is always one well-defined LCA, but Git's commit graph is not necessarily a single tree: it's just a Directed Acyclic Graph or DAG.树中,总是有一个明确定义的 LCA,但 Git 的提交图不一定是单个树:它只是一个有向无环图或 DAG。 Two commits may have no LCA, or may have more than one LCA, and merge does different things for these cases.两次提交可能没有 LCA,或者可能有多个 LCA,而合并对这些情况做了不同的事情。


Merging, part 2合并,第 2 部分

Having found the merge base, Git now runs two of its compare two commits and see what changed operations.找到合并基础后,Git 现在运行其比较两个提交中的两个并查看更改了哪些操作。 Comparison #1 compares the merge base to the --ours commit, ie, to HEAD .比较 #1 将合并基础与--ours提交进行比较,即与HEAD So Git will do:所以 Git 会这样做:

git diff --find-renames <hash-of-H> <hash-of-H>   # what we changed on master

Obviously, commit H is the same as commit H .显然,提交H与提交H相同。 Nothing at all changed!什么都没有改变!

Then, Git does a second diff, to see what "they" (we) changed on the other side:然后,Git 做第二个 diff,看看“他们”(我们)在另一边改变了什么:

git diff --find-renames <hash-of-H> <hash-of-J>   # what they changed on feature1

What merge does, then, is to combine these two sets of changes.合并所做的就是将这两组更改组合起来 Where we changed some file, and they didn't, Git takes our change.在我们更改了一些文件而他们没有更改的地方,Git 接受了我们的更改。 Where they changed some file, and we didn't, Git takes their change.在他们更改了一些文件而我们没有更改的地方,Git 接受了他们的更改。 These combined changes get applied to the merge-base snapshot.这些组合更改将应用​​于基于合并的快照。 That way, we keep all our work and add their work—but wherever we and they made different changes to some file or files, Git will show a merge conflict .这样,我们保留了所有的工作并添加了他们的工作——但是无论我们和他们对某个或多个文件进行了哪些不同的更改,Git 都会显示合并冲突

In this case, the --ours diff is completely empty: we didn't change anything.在这种情况下, --ours diff 完全是空的:我们没有改变任何东西。 So whatever "they"—really, we on feature1 —did, Git takes those changes.所以无论“他们”—— feature1 ,我们在feature1上—— feature1什么,Git 都会采取这些更改。 That includes adding a new file test1 .这包括添加一个新文件test1 This combining goes well, so Git makes the new merge commit on its own.这种组合进行得很顺利,所以 Git 自己进行新的合并提交。

The first parent of the new merge commit is our current commit, H , on master .新合并提交的第一个父项是我们当前在master上的提交H The second parent of the new merge commit is their commit J , on feature1 .新合并提交的第二个父项是他们在feature1上的提交J We can draw that—the drawing here doesn't show first vs second commit properly, but we can just remember it if we need to, or ask Git about both parents to see which one is first, or whatever.我们可以画出来——这里的画没有正确地显示第一次提交和第二次提交,但我们可以在需要时记住它,或者向 Git 询问父母双方,看看哪个是第一个,或者其他什么。

The result looks like this:结果如下所示:

          I--J   <-- feature1
         /    \
...--G--H------M   <-- master (HEAD)
         \
          K--L   <-- feature2

Note how no other branch name moved: we're still on master , and it has moved to point to M , and feature1 still names commit J and feature2 still names commit L .注意没有其他分支名称移动:我们仍然在master ,它已经移动到指向M ,并且feature1仍然命名 commit Jfeature2仍然命名 commit L

The conflicting merge冲突的合并

If we now run another git merge —this time with feature2 —Git will once again locate three commits:如果我们现在运行另一个git merge ——这次使用feature2 ——Git 将再次定位三个提交:

  • The ours and theirs commits are commits M and L , of course.我们和他们的提交当然是提交ML
  • The merge base is the best shared commit.合并基础是最好的共享提交。

Look at the diagram.看图。 Which commits are on both master and feature2 ?哪些提交同时发生在masterfeature2 Commits GHIJM are all on masterH in two ways, directly from the first parent of M , and indirectly by going from J to I to H through the second parent of M —and hence G is there in two ways, and so on, but all we really care about is that H and G are there.提交GHIJM都在master - H两种方式,直接从的第一个亲本M ,并通过间接从去JIH通过的第二个亲本M -and因此G是有两种方式,等等,但我们真正关心的是HG是否存在。

Meanwhile, feature2 ends at L , goes back to K , then goes back to H .同时, feature2L结束,回到K ,然后回到H So commits H and G are both shared.所以提交HG都是共享的。 Commit H is the best one, though.不过,Commit H最好的 Once again, then, the merge base is commit H .再一次,合并基础是提交H

Git will once again run two git diff s, both with --find-renames (to check for renamed files) and both from H to the two branch tips. Git 将再次运行两个git diff ,两个都带有--find-renames (检查重命名的文件),并且都从H到两个分支提示。 So Git is going to compare the snapshot in H against the one in M , to see what we changed.因此,Git 会将H中的快照与M的快照进行比较,以查看我们更改了什么。

What did we change, from H to M ?我们得到什么变化,从HM Well, in M , we added all the changes we got by comparing H vs J .好吧,在M ,我们添加了通过比较HJ获得的所有更改。 So any files we changed in feature1 are changed in M .因此,我们在改变任何文件feature1在改变M But we also added a new file, test1 , in either I or J , so this change-set says add all-new file test1 .但是我们还在IJ添加了一个新文件test1 ,因此此更改集表示添加全新文件test1

When we compare H vs L , that, too, says add an all-new file test1 .当我们比较HL时,也表明添加一个全新的文件test1 So both changesets say to add a new file .所以两个变更集都说要添加一个新文件

Git calls this kind of conflict an add/add conflict . Git 将这种冲突称为添加/添加冲突 In the work-tree, Git just leaves you with the entire contents of both files as your conflict.在工作树中,Git 只留下两个文件的全部内容作为冲突。 You must resolve this conflict in some way.您必须以某种方式解决此冲突。 How you go about it is up to you.你怎么做取决于你。 Whatever you choose to put in file test1 , you can now run:无论您选择在文件test1放入什么,您现在都可以运行:

git add test1

and Git will assume that whatever is in the file test1 is the correct resolution for that conflict. Git 会假设文件test1中的任何内容都是该冲突的正确解决方案。

Be sure to edit the file!一定要编辑文件! If you don't, it just has the conflict markers in it, and Git thinks that's the right answer!如果没有,它只是包含冲突标记,Git 认为这是正确的答案! It probably isn't.可能不是。

Once you have resolved all conflicts, and are sure that the merge result is correct—you've done any testing you need to do, for instance—you can safely finish the merge by running either:一旦您解决了所有冲突,并确定合并结果是正确的——例如,您已经完成了任何需要做的测试——您可以通过运行以下任一命令来安全地完成合并:

git merge --continue

or:或者:

git commit

(The git merge --continue just makes sure you're still finishing the merge, then runs git commit for you, so they wind up doing the same thing—unless you already finished, or terminated, the merge, that is.) git merge --continue只是确保您仍在完成合并,然后为您运行git commit ,因此他们最终会做同样的事情 - 除非您已经完成或终止合并,即。)

Git will now make another merge commit; Git 现在将进行另一个合并提交; we'll call it commit N , and draw it like this:我们将其称为 commit N ,并像这样绘制它:

          I--J   <-- feature1
         /    \
...--G--H------M--N   <-- master (HEAD)
         \       /
          K-----L   <-- feature2

The first parent of N is M , and the second parent of N is L .的第一个亲本NM ,和第二亲本NL There are now three ways to get from N to H , and all commits in the diagram are on master .现在有三种方法可以从NH ,图中的所有提交都在master

It's now safe to delete the names feature1 and feature2 because Git can find those commits—including J and L —by going backwards from commit N .现在删除名称feature1feature2是安全的,因为 Git 可以通过从提交N向后找到这些提交——包括JL You don't have to delete the names, if you want to retain the ability to find commits J and L directly and quickly, but they're no longer necessary , like they were before the merge operations.不必删除名字,如果你想保留找到提交的能力JL直接和迅速,但他们不再是必要的,像他们的合并操作之前。

If you made changes to both files in the same place, there will be a merge conflict.如果在同一个地方对两个文件进行了更改,就会出现合并冲突。

If you didn't made changes to both files in the same place, it will merge without problems(maybe a conflict at another location).如果您没有在同一位置对两个文件进行更改,它将顺利合并(可能在另一个位置发生冲突)。

You can solve a conflict by editing the conflicting files, removing the commit markers, adding the files and committing it.您可以通过编辑冲突文件、删除提交标记、添加文件并提交来解决冲突。

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

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