问题
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
.
What's gonna happen to the code written in both files if I merge the 2 branches in master?
回答1:
Steps do to probably :
Merge
branch1
tomaster
.This should go smoothly.
Merge
master
tobranch2
.Here you will probably get a merge conflict. Resolve it.
Merge
branch2
tomaster
.
This will make sure your master is never "poluted" even if you made some mistakes , there would be on branches, and would be resolved.
回答2:
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.
回答3:
As amer answered (correctly), you'll get a merge conflict in one of the two merges. 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. It's about commits. Most of Git is really about commits, and git merge
is no different here.
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). Then you pick some commit—any commit anywhere—and tell Git to check out that particular commit, perhaps by its raw hash 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.23git switch
command is for: you pick some commit and say get me all the files from that commit. 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, e.g., compiled files or outputs or whatever. 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).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.There is one part of the metadata, though, that is specifically for Git itself. 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. 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.
Anyway, every commit stores, as part of its metadata, the hash ID of its previous or parent commit. 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. Every snapshot is just a snapshot, but every snapshot also says: and my previous snapshot is ______ (fill in the blank with a hash 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. 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". That's mostly it—that's what a branch name does for us and for 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
. 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. Commit C
itself stores the hash ID of earlier commit B
, so that C points to B
; and commit B
stores the hash ID of earlier commit A
.
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. That's how Git knows to stop going back: when it can't.
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. This also becomes important when we start having more than one branch. Let's start with a small repository with about eight commits:
...--G--H <-- master
Now let's add a new branch. We'll start by having the new name also select 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:
...--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
:
I <-- feature1 (HEAD)
/
...--G--H <-- master
When we add a new commit, Git automatically updates the branch name to point to the new commit. Which branch name gets updated? The one HEAD
is attached-to. The others all stay in place.
Now all the commits through H
are on both branches, and commit I
is *only on feature1
. Let's make another commit, then make a new branch feature2
that selects commit H
, and start using that one:
I--J <-- feature1
/
...--G--H <-- master, feature2 (HEAD)
Now let's add two commits to 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
. Suppose that in commit K
or L
, we also created a new file named test1
.
Merging
We're now going to merge the two features into master
, one at a time. For no obvious reason,1 we will use the --no-ff
option:
git checkout master
git merge --no-ff feature1
to achieve this.
When we git checkout master
, we direct Git to:
- extract the commit identified by the name
master
—commitH
—to our work-tree (and to Git's index, which we won't get into here); and - set up our work-tree to match, which means removing the file
test1
, which is in commitL
—there is a saved snapshot file with that name—but is not in commitH
.
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 now finds three commits, not just two. The three commits of interest are:
Our current commit,
HEAD
. That's really easy to find becauseHEAD
is attached to a branch name and the branch name points to the commit, so Git finds commitH
.The other commit we named. That's really easy too: we said to merge
feature1
. The namefeature1
identifies commitJ
. (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, i.e., the best commit that's on both branches. Starting from
J
—as found by namefeature1
—we work backwards; and starting fromH
, as found bymaster
, we also work backwards. 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
In this case, the merge base is clearly commit H
itself.
1The merge I'll do here is the kind you would get on GitHub, using its "merge pull request" button. From the Git command line, you get more options. 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.
2Technically, what Git is doing is finding the Lowest Common Ancestor (LCA) in a directed graph. 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. Two commits may have no LCA, or may have more than one LCA, and merge does different things for these cases.
Merging, part 2
Having found the merge base, Git now runs two of its compare two commits and see what changed operations. Comparison #1 compares the merge base to the --ours
commit, i.e., to HEAD
. So Git will do:
git diff --find-renames <hash-of-H> <hash-of-H> # what we changed on master
Obviously, commit H
is the same as commit H
. Nothing at all changed!
Then, Git does a second diff, to see what "they" (we) changed on the other side:
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. Where they changed some file, and we didn't, Git takes their change. 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.
In this case, the --ours
diff is completely empty: we didn't change anything. So whatever "they"—really, we on feature1
—did, Git takes those changes. That includes adding a new file test1
. This combining goes well, so Git makes the new merge commit on its own.
The first parent of the new merge commit is our current commit, H
, on master
. The second parent of the new merge commit is their commit J
, on feature1
. 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.
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
.
The conflicting merge
If we now run another git merge
—this time with feature2
—Git will once again locate three commits:
- The ours and theirs commits are commits
M
andL
, of course. - The merge base is the best shared commit.
Look at the diagram. Which commits are on both master
and feature2
? Commits G-H-I-J-M
are all on master
—H
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.
Meanwhile, feature2
ends at L
, goes back to K
, then goes back to H
. So commits H
and G
are both shared. Commit H
is the best one, though. Once again, then, the merge base is commit 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. So Git is going to compare the snapshot in H
against the one in M
, to see what we changed.
What did we change, from H
to M
? Well, in M
, we added all the changes we got by comparing H
vs J
. So any files we changed in feature1
are changed in M
. But we also added a new file, test1
, in either I
or J
, so this change-set says add all-new file test1
.
When we compare H
vs L
, that, too, says add an all-new file test1
. So both changesets say to add a new file.
Git calls this kind of conflict an add/add conflict. In the work-tree, Git just leaves you with the entire contents of both files as your conflict. 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:
git add test1
and Git will assume that whatever is in the file test1
is the correct resolution for that conflict.
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! 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 will now make another merge commit; we'll call it commit N
, and draw it like this:
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
. There are now three ways to get from N
to H
, and all commits in the diagram are on 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
. 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.
来源:https://stackoverflow.com/questions/59532257/merging-2-files-with-the-same-name-in-2-different-branches-in-git