0

Background

I'm newish to Git and resolving my first merge conflict. I'm working in C# using Visual Studio on the Scott Lilly C# SuperAdventure tutorial. While building the tutorial, I'm using Github to learn SCM and Git as well. As part of this process, I learned to have a clean master that directly follows the tutorial without deviation, create a branch for each chapter, and merge it back to master at the end, and then keep any experiments on separate branches - that way my experiments never conflict with the tutorial when it creates new features or refactors existing code.

So far, so good. One of my experiments is adding some unit tests. I started a separate unittesting branch, and decided to pull in changes from master to this branch to keep it up-to-date. On the first merge, I had a conflict, in the Visual Studio project file for the project I was adding unit tests to, due to different packages being installed. This is not a big deal, but the mechanics are tripping me up. There are 5 conflicts from additions, which can all be merged directly, and one case of a true conflict. I've gotten most of the way through it, but just need some final help.

Step-by-Step So far

I'm using a public repo on Github, and Visual Studio 2017 Community on Windows 10 with the VS Github extension and local Git repo - but let's face it, the Visual Studio plug-in commands are quite limited to very normal workflow items. So - I've started using the Git bash as needed for command-line, and Git Extensions for a Git GUI, which is working pretty well.

I recently completed Chapter 23 of the tutorial, and merged Chap_23 branch into master. I then tried to merge master up to unittesting, but got a conflict in the SuperAdventureConsole/SuperAdventureConsole.csproj file. A git status at this point shows:

$ git status
On branch unittesting
Your branch is ahead of 'origin/unittesting' by 1 commit.
  (use "git push" to publish your local commits)
You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

Changes to be committed:

        modified:   SuperAdventure.sln
        modified:   SuperAdventureConsole/App.config
        modified:   SuperAdventureConsole/Properties/AssemblyInfo.cs

Unmerged paths:
  (use "git add <file>..." to mark resolution)

        both modified:   SuperAdventureConsole/SuperAdventureConsole.csproj

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        SuperAdventureConsole/SuperAdventureConsole.csproj.BASE
        SuperAdventureConsole/SuperAdventureConsole.csproj.LOCAL
        SuperAdventureConsole/SuperAdventureConsole.csproj.REMOTE
        SuperAdventureConsole/SuperAdventureConsole.csproj.orig

At this point I ran git mergetool based on Q: How to resolve merge conflicts in Git?, which runs kdiff3 as a merge tool (on my setup, anyway), with the original file as A file, and LOCAL and REMOTE as B and C (I'm not sure which was which). I was able to blunder through that one, and pick between B and C for each conflict (6 total). This is where I got stuck.

At first the output file (at the bottom below A, B, and C in kdiff3) wasn't taking, but on the 3rd try, I saved and exited kdiff3, and there was no longer a merge conflict:

$ git mergetool
No files need merging

But, I still have Untracked files, per "git status":

$ git status
On branch unittesting
Your branch is ahead of 'origin/unittesting' by 1 commit.
  (use "git push" to publish your local commits)
All conflicts fixed but you are still merging.
  (use "git commit" to conclude merge)

Changes to be committed:

        modified:   SuperAdventure.sln
        modified:   SuperAdventureConsole/App.config
        modified:   SuperAdventureConsole/Properties/AssemblyInfo.cs
        modified:   SuperAdventureConsole/SuperAdventureConsole.csproj

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        SuperAdventureConsole/SuperAdventureConsole.csproj.BASE
        SuperAdventureConsole/SuperAdventureConsole.csproj.LOCAL
        SuperAdventureConsole/SuperAdventureConsole.csproj.REMOTE
        SuperAdventureConsole/SuperAdventureConsole.csproj.orig

Question(s)

It looks like the modified file is the saved output from kdiff3 and therefore is already added to the index, and therefore I can commit. Is that correct? I assume in this case I just commit to complete the merge?

Overall, I'm not sure what happened here - why does "git status" show 4 untracked files with an extension (i.e. .BASE, .LOCAL, .REMOTE, and .orig), but shows a SuperAdventureConsole/SuperAdventureConsole.csproj without an extension in the "modified" file list?

I understand .LOCAL and .REMOTE, but what is the difference between .BASE and .orig? One of these should be the parent of both branches, but which one is it, and what is the other one?

Also, is there anything I can do from the Git Bash or using Git Extensions or another Git GUI to double check whether the merge is ready to commit the way I expect?

Thanks!!

LightCC
  • 9,804
  • 5
  • 52
  • 92

1 Answers1

2

It looks like the modified file is the saved output from kdiff3 and therefore is already added to the index, and therefore I can commit. Is that correct? I assume in this case I just commit to complete the merge?

This looks like it's the case. Use git diff --cached (or git diff --staged, if you prefer: both do exactly the same thing) to compare what's in the index right now, i.e., what would go in a new commit, to what's in the current commit right now. See also the answer to the second question.

(git status does this same git diff --cached but with --name-status, so that you only see any files added, deleted, or modified, and not what the actual change is.)

Overall, I'm not sure what happened here - why does "git status" show 4 untracked files with an extension (i.e. .BASE, .LOCAL, .REMOTE, and .orig) ...

This is a bit of a mystery: it suggests you still have a git mergetool running.

What git mergetool does is to grub around through the mess left behind in the index after a failed merge. Remember, the index, AKA the staging area or cache—three names for one thing—is normally just a place where you build up your next commit. It has one copy of each file that will go into the new commit. During a failing merge, though, it can have three copies of each failed-merge file instead of just one.

(It seems likely that only one file's automatic merge actually failed, namely SuperAdventureConsole/SuperAdventureConsole.csproj.)

The three copies in the index are:

  • The merge-base version of the file.
  • The current (HEAD, --ours, or—aha!—local) version of the file.
  • The other (--theirs or, a very poor name, remote) version of the file.

Files that are in the index are in a special, Git-only form, so what git mergetool does is to extract each of these to an ordinary file in your work-tree. It also copies the failed-merge-result file, left behind in SuperAdventureConsole/SuperAdventureConsole.csproj, to .orig.

Then, git mergetool runs your chosen file-merge program. You get to manipulate the file in this program. When you are done, you save the file and exit. The mergetool script compares the .orig file to the final version to see if you actually fixed anything, and/or looks at the exit code from the program.

If it looks like you did something about the failed merge, the git mergetool script should remove at least three, and often all four, of these files. The fact that it did not suggests that git mergetool is still running. You might check and make sure that it really isn't. Perhaps something killed it off and it never had a chance to clean up.

I understand .LOCAL and .REMOTE, but what is the difference between .BASE and .orig? One of these should be the parent of both branches, but which one is it, and what is the other one?

The .LOCAL file is just the --ours version from the index (git show :2:<path>, during the conflict). The .REMOTE file is the --theirs version (git show :3:<path>). The .BASE file is the merge-base version (common parent, as you said; git show :1:<path>). The .orig is a backup of whatever the file-level merge left behind. The mergetool script mainly uses it to compare to what's left in the original file when your chosen merge-program exits.

torek
  • 448,244
  • 59
  • 642
  • 775
  • Thanks - complete answer and explains what I'm seeing. Since then I also found out about `git status -v` which basically does a status and then `git diff --staged` from what I can tell. Finally, I found out that `git difftool -y --staged` will launch each file in **kdiff3** (or other visual diff tool installed). And... finally, finally, `git difftool -y --staged SuperAdventureConsole/SuperAdventureConsole.csproj` will show the staged changes in that particular file... :) – LightCC Aug 14 '17 at 23:47
  • I can't find an open `mergetool`. But anyway - still have that final question - what do I do to complete the merge? Just commit? I suppose I can always commit away, then rollback the changes and try again if it doesn't go right. – LightCC Aug 15 '17 at 00:11
  • Yes, just commit: that will make a merge commit, concluding the merge. (And, yes, `-v` in `git status` makes it show more. I just now discovered that the option was undocumented until Git 2.4.0, which added `-vv` to add the second `git diff`'s output as well.) – torek Aug 15 '17 at 00:30