5

Me and a friend are working on a project written in C# using VS 2013 Express with Git (not the VS Git Plugin, mind you), and we're running into a problem. Our workflow is setup like this: we have a central repository, and we each have our own fork of the repository that we work on separately. When our individual work is done we push to the central repository. When we want to sync with the central repository, we pull.

The problem is that when we pull, the csproj files randomly update. Sometimes they properly update and the files that have been added since the last pull properly show up in VS, and other times the csproj file is completely unaffected.

It's strange because the csproj gets updated properly everywhere else, including the central repository, it just sometimes doesn't update properly when we pull.

In our .gitattributes file we have .csproj set to merge=union.

The command we perform when pulling is git pull upstream where upstream is just a remote pointing to our central repository.

Any ideas?

5
  • 1
    Have you made a habit of making sure your local .csproj file is properly saved to disk (by doing "Save All" or something similar) before trying to pull or merge? Commented Apr 24, 2016 at 23:42
  • @MattiVirkkunen Yeah we are. Commented Apr 24, 2016 at 23:43
  • It's not clear what's happening unless you show some examples of errors. But why are you using merge=union? That will surely cause problems eventually, even if that's not the problem you're seeing now. Commented Apr 24, 2016 at 23:49
  • @EdwardThomson I don't remember the exact reason for using merge=union, all I know is that the default merge strategy caused a ton of errors when merging csproj files and changing it to merge=union solved said problems. I'm not quite sure how I could show you an example of an error, because it's not reproducable. I guess one time I added 4 files in Visual Studio, which properly appeared in the csproj file on both my fork and the central repository, and after my partner performed a git pull upstream the files themselves were properly pulled but the csproj file remained unchanged. Commented Apr 24, 2016 at 23:55
  • 1
    @user3002473 Here's some documentation on the problems that merge=union will cause. Better to have conflicts that you need to resolve than to have silent corruption. haacked.com/archive/2014/04/16/csproj-merge-conflicts Commented Apr 25, 2016 at 0:19

1 Answer 1

11

I'll put this in as an answer, based on Edward Thomson's comment, which contains this link to Merge conflicts in csproj files (haacked.com/archive/2014/04/16/csproj-merge-conflicts). Please note that I know nothing about Visual Studios, C#, or Windows, but I do know about git. :-)

The problem is that when we pull, the csproj files randomly update. Sometimes they properly update and the files that have been added since the last pull properly show up in VS, and other times the csproj file is completely unaffected.

First, let's do a few brief review notes:

  1. git pull is just git fetch followed by git merge. You can tell Git to do a rebase instead. This may be a good idea depending on your development flow, but won't change this particular problem, because ...
  2. Nothing else in Git makes merge commits, but ...
  3. Several other commands, including git rebase, use the merge machinery.
  4. Pushing (git push) never does any merging, so you will only see the problem in the "get me new stuff after both I and someone else did something" case. Even then, whether you see the problem depends quite heavily on what you and the someone-else did. This is because the merge machinery, used by both git merge and git rebase, is just not that smart.

Now let's take a look at the underlying problem, which (based on the link) is that these *.csproj files contain XML: structured data that git's built-in text-oriented merge operations cannot merge correctly.

Changes conflict when, in the git diff output, two different lines of development (the branches or commits being merged) made different changes to the same region of a single file. One classic example occurs in files when two editors cannot agree on spelling. For instance, I made this change some time ago:

diff --git a/plates.tex b/plates.tex
index 09939ca..3dfc610 100644
--- a/plates.tex
+++ b/plates.tex
@@ -15,7 +15,7 @@ that I took on a trip to parts of Australia in February of 2010
 \end{plate*}
 The kangaroo is probably the most widely known marsupial.
 There are actually four species of large kangaroo:
-the red, the eastern and western gray, and the antilopine.
+the red, the eastern and western grey, and the antilopine.
 There are also smaller tree-kangaroos and rat-kangaroos.
 
 \begin{plate*}[h]

If I were to merge this with someone else who made a different change to any of the lines shown in the diff, but left "grey" spelled with the letter a, I would get a conflict. For instance, if my editor were to insist that the word be spelled “kangaru”, the changes would conflict:

Auto-merging plates.tex
CONFLICT (content): Merge conflict in plates.tex
Automatic merge failed; fix conflicts and then commit the result.

When git encounters conflicting changes, its usual reaction is to simply declare that there is a conflict and stop. It leaves you with a work-tree version of the file containing conflict markers <<<<<<< ======= >>>>>>> surrounding both sides of the conflict (along with ||||||| above the base version if and only if you set merge.conflictstyle to diff3):

\end{plate*}
<<<<<<< HEAD
The kangaru is probably the most widely known marsupial.
There are actually four species of large kangaru:
the red, the eastern and western gray, and the antilopine.
There are also smaller tree-kangarus and rat-kangarus.
||||||| merged common ancestors
The kangaroo is probably the most widely known marsupial.
There are actually four species of large kangaroo:
the red, the eastern and western gray, and the antilopine.
There are also smaller tree-kangaroos and rat-kangaroos.
=======
The kangaroo is probably the most widely known marsupial.
There are actually four species of large kangaroo:
the red, the eastern and western grey, and the antilopine.
There are also smaller tree-kangaroos and rat-kangaroos.
>>>>>>> master

\begin{plate*}[h]

Again, though, this is the default behavior. You can, through command line switches or a .gitattributes file, change the default.

You selected the union merge, and the git documentation has been improved a bit:

union

Run 3-way file level merge for text files, but take lines from both versions, instead of leaving conflict markers. This tends to leave the added lines in the resulting file in random order and the user should verify the result. Do not use this if you do not understand the implications.

(boldface mine). The short version here, though, is that git will believe the merge succeeded (well, it did succeed) but will do so by producing an invalid XML file. There are examples on the linked haacked.com page of exactly how this goes wrong for the *.csproj files, but this is generally true of any union merge: it's not smart enough to get the right result and there are few places where it is a reasonable driver. It would make some sense to invoke it manually in some cases, then review the file and make any fixups by hand, but since it just succeeds and lets the merge proceed, you must be very careful with putting it in .gitattributes.

Ideally, we would like to have a merge driver that understood XML formatting and the intent of a .csproj file (XML formatting alone is not sufficient as the semantics of the markup are not tied to the syntax). Since I do not use VS on Windows, I can only quote the haacked.com article again:

Another way would be to write a proper XML merge driver for Git, but that’s quite a challenge as my co-worker Markus Olsson can attest to. If it were easy, or even moderately hard, it would have been done already. Though I wonder if we limited it to common .csproj issues could we write one that isn’t perfect but good enough to handle common merge conflicts? Perhaps.

If the contents are as simple as the example XML, I think such a driver would be not too difficult, so I suspect they get more complicated.

1
  • 1
    Fantastic answer, really clears up all the confusion I had about the union merge strategy. Thanks so much! Commented Apr 25, 2016 at 14:04

Not the answer you're looking for? Browse other questions tagged or ask your own question.