问题
I frequently come across the following Merge Conflict issue and resolving it but I would like to ask if my solution is correct. Personally, I do not see any issues given that the functional tests are passing but it would be nice to have an insight on this one.
So, let's say we have got two branches:
develop
feature
I am changing the code in feature
branch and then creating a PR to merge into develop
. However, Github
's or Bitbucket
's diff tool suggests that we have got a Merge Conflict; pretty common situation in coding.
I then take the following steps:
git checkout develop
andgit pull origin develop
git checkout feature
and thengit pull origin feature
git merge develop
, so to update my current branch. These steps help me out to spot the files that the conflicts occur.- Then I fix the conflicts manually and I see:
`You have unmerged paths. (fix conflicts and run "git commit")
Changes to be committed:
modified: app/BlablaFile.php
modified: app/Blabla2File.php
renamed: app/OldName.php -> app/NewName.php
and then:
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: app/UnmergedFile.php
which is basically a list of the files that I edited to resolve the merge conflict.
Question is should I only git add app/UnmergedFile.php
and then git commit -m "Updated app/UnmergedFile.php"
? Or I should git add
also the files that are included in the Changes to be committed:
section?
I normally do the first one but it would be nice to have another thought.
回答1:
TL;DR: You do not need to re-add files that are in "changes to be committed".
Git's "index"
Git has a thing called, variously, the index or the staging area, or sometimes even the cache (as in git diff --cached
). It's not very well explained, in part because it's complicated. Fortunately, the use of the index is pretty simple, except (alas) during merges. :-)
What's in the index is best described as the next commit you will make. It also helps to remember that Git always has a current commit, named HEAD
, and (except in --bare
repositories), a work-tree or working tree, which is where you have your files ready for viewing, compiling, editing, and whatever else it is you do with your files.
Initially, your work-tree, index, and HEAD
commit all match (when you first clone a repository). You can't touch files directly within the index, so you make any changes—or even create new files—in the work-tree, where you do all your work. Then, to put a modified file back into the index, you run git add
on the path, so that Git copies the modified file into the index. To put a new file into the index, you also run git add
on the path.
If you were, for some reason, to get all excited and run git commit
right now, Git would read your current index and package it up as a new commit. The new commit would have, as its work-tree snapshot, every file that is in the index now, in the state that it is now, in the index.
If that state matches the HEAD
commit, git commit
just says: "Huhwha? Why you try to make new commit now?" So presumably you've changed something in the index by this point. git status
will show you what's in the index that's different from the HEAD
commit: these are "changes to be committed".
Merging
Running git merge
sometimes reveals a secret about the index. Each entry in the index actually has four slots, which are numbered. Normally, you only ever see slot zero, which is reserved for "normal" files, ready to be committed.
Merging is intended to combine two (or sometimes more, but let's stick with two) sets of changes. For instance, perhaps you and Bob started with the same files. Then you made some changes to some files, and git add
-ed and committed new snapshots of the source. Meanwhile Bob made some changes to some files—maybe the same files, even—and also git add
-ed and committed, and now it's time to combine your changes and Bob's changes.
To perform a merge—to merge-as-a-verb some file(s)—Git first finds the merge base, i.e., the point where you and Bob were in sync. Then Git runs two git diff
s: one from that merge base to your latest commit, and one from that same merge base to Bob's latest commit. This shows "what you changed" and "what Bob changed".
Git then does its best to combine those changes (including, as in this case, figuring out and following file renames, and figuring out what to do if only one of you renamed some file—but most merge conflicts don't include file renames, unless you write a lot of Java code...). Sometimes, though, Git can't do this on its own. In that particular case, Git puts the merge work back to you, and it's at this point that you see all the slots in the index.
Index slot 1 is where Git stores the base version of the file. That's the common version, from which you both started.
Index slot 2 is the HEAD
or --ours
version of the file. (It is also sometimes called the "local" version.)
Index slot 3 is the other or --theirs
(or sometimes "remote") version of the file.
That's almost all there is to it: the three slots hold the three versions, and you end up with "unmerged paths". The file in your work-tree holds Git's initial effort to merge, along with conflict markers and some mash-up of the --ours
and --theirs
version. If you set merge.conflictstyle
to diff3
, Git will also include the parts from the base version of the file, stored in index slot 1. It's now up to you to resolve the merge.
Once you've figured out the correct resolution, and updated the work-tree version of the file, you must git add
the path. This stores the work-tree version into index slot zero, wiping out the three entries in slots 1-3. Since slot zero holds the normal, to-be-committed version, the file is now ready to commit.
For files where there were no changes at all, or just one of you changed something, or where Git thinks it correctly combined your changes and Bob's changes, those files are already updated in both work-tree and (slot zero of) index. If the result differs from the current HEAD
commit, git status
shows the file as "changes to be committed".
The last bits
I said above that this is almost all there is to it. The last few things to remember about the index slots are:
Some slots might be empty. Suppose both you and Bob added a file. In this case, you'll get an "add/add conflict". What version of the file is the common base version? There is none, of course: you both added a new file. So in this case, the base slot—slot 1—is empty.
The same happens if you delete a file and Bob changes it. In this case, the
--ours
slot, slot 2, is empty, while slots 1 and 3 hold the base version and Bob's version. It's up to you to decide whether to keep Bob's version, keep the file removed, or use yet a third variant, but once again only two of the three slots are filled.If one or both of you renames a file, the file versions in the three slots may come from files that had different names in earlier commits. For instance, your output shows:
renamed: app/OldName.php -> app/NewName.php
There was no conflict here, but if there had been, at least one of the slots for
app/NewName.php
would be filled from some other commit's version ofapp/OldName.php
.This mainly matters when you go to look at the old version. If, in a separate clone or work-tree, you check out one of the commits where the file is not yet renamed—or if you use
git show <commit>:app/OldName.php
to view the file without checking it out—you must use the old name wherever it has the old name. But if you usegit checkout --ours
orgit checkout --theirs
to extract just one of these two versions into the work-tree, you must use the new name, because the index now has the file stored under the new name.If you do use
git checkout --ours
orgit checkout --theirs
to get your or, respectively, Bob's version of a file, this does not resolve the file, but does clobber Git's attempt to merge them in the work-tree. If you want to restore Git's attempt to merge them, usegit checkout -m
. Note that all of these overwrite the work-tree version of the file, while leaving the three unresolved index slots alone.Weirdly, though, if you
git checkout <commit-id> -- <path>
to get an old version of a file out of some specific commit, that overwrites the index: it copies the commit's version of the file to index slot zero, clobbering the slot 1-3 entries. The file now appears resolved! Again, you can restore the unresolved state withgit checkout -m
. (Of course that overwrites the work-tree version.)Similarly, if you mistakenly resolve the file with
git add
when it's not quite done yet, you cangit checkout -m
to unresolve it—but of course, that overwrites the work-tree version. If you're mostly done resolving, you might as well finish resolving and re-git add
the result. This replaces the slot-zero index entry with a version freshly copied from the work-tree: the file stays resolved, but the version ready for the next commit changes to the latestgit add
-ed version.
回答2:
I usually do the latter one and haven't faced and issue with it.
I think it depends on what you're going to achieve.
If you want to explicitly mention what are the files you've exactly resolved conflicts in, you could do the first one. Otherwise you could do the other one.
回答3:
The files named in the "changes to be committed" list are already in the staging area, (also called the index).
Git add is what you use to place your work into the index, which tells git that you want them to be included in the commit.
So a git add of the files that are already staged would be redundant.
An additional add of the files in the "to be committed" list is only needed if the files are listed in both lists (which sometimes happens if you stage files and then modify them some more).
来源:https://stackoverflow.com/questions/41489331/git-merge-conflict-add-changes-to-be-committed-when-resolved