Merge conflicts in csproj files

git ghfw vs 44 comments suggest edit

In a recent version of GitHub for Windows, we made a quiet change that had a subtle effect you might have noticed. We changed the default merge strategy for *.csproj and similar files. If you make changes to a .csproj file in a branch and then merge it to another branch, you’ll probably run into more merge conflicts now than before.

Why?

Well, it used to be that we would do a union merge for *.csproj files. The git merge-file documentation describes this option as such:

Instead of leaving conflicts in the file, resolve conflicts favouring our (or their or both) side of the lines.

For those who don’t speak the commonwealth English, “favouring” is a common British misspelling of the one true spelling of “favoring”. :trollface:

So when a conflict occurs, it tries to resolve it by accepting all changes more or less. It’s basically a cop out.

This strategy can be set in a .gitattributes file like so if you really want this behavior for your repository.

*.csproj  merge=union

But let me show why you probably don’t want to do that and why we ended up changing this.

Union Merges Gone Wild

Suppose we start with the following simplified foo.csproj file in our master branch along with that .gitattributes file:

<?xml version="1.0" encoding="utf-8"?>
<Project>
  <PropertyGroup>
    <Page Include="AAA.cs">
      <SubType>Designer</SubType>
      <Generator>MSBuild:Compile</Generator>
    </Page>
    <Page Include="DDD.cs">
      <SubType>Designer</SubType>
      <Generator>MSBuild:Compile</Generator>
    </Page>
  </PropertyGroup>
</Project>

After creating that file, let’s make sure we commit it.

git init .
git add -A
git commit -m "Initial commit of gittattributes and foo.csproj"

We then create a branch (git checkout -b branch) creatively named “branch” and insert the following snippet into foo.csproj in between the AAA.cs and DDD.cs elements.

    <Page Include="BBB.cs">
      <SubType>Designer</SubType>
      <Generator>MSBuild:Compile</Generator>
    </Page>

For those who lack imagination, here’s the result that we’ll commit to this branch.

<?xml version="1.0" encoding="utf-8"?>
<Project>
  <PropertyGroup>
    <Page Include="AAA.cs">
      <SubType>Designer</SubType>
      <Generator>MSBuild:Compile</Generator>
    </Page>
    <Page Include="BBB.cs">
      <SubType>Designer</SubType>
      <Generator>MSBuild:Compile</Generator>
    </Page>
    <Page Include="DDD.cs">
      <SubType>Designer</SubType>
      <Generator>MSBuild:Compile</Generator>
    </Page>
  </PropertyGroup>
</Project>

Don’t forget to commit this if you’re following along.

git commit -a "Add BBB.cs element"

Ok, so let’s switch back to our master branch.

git checkout master

And then insert the following snippet into the same location.

    <Page Include="CCC.cs">
      <SubType>Designer</SubType>
      <Generator>MSBuild:Compile</Generator>
    </Page>

The result now in master is this:

<?xml version="1.0" encoding="utf-8"?>
<Project>
  <PropertyGroup>
    <Page Include="AAA.cs">
      <SubType>Designer</SubType>
      <Generator>MSBuild:Compile</Generator>
    </Page>
    <Page Include="CCC.cs">
      <SubType>Designer</SubType>
      <Generator>MSBuild:Compile</Generator>
    </Page>
    <Page Include="DDD.cs">
      <SubType>Designer</SubType>
      <Generator>MSBuild:Compile</Generator>
    </Page>
  </PropertyGroup>
</Project>

Ok, commit that.

git commit -a "Add CCC.cs element"

Still with me?

Ok, now let’s merge our branch into our master branch.

git merge branch

Here’s the end result with the union merge.

<?xml version="1.0" encoding="utf-8"?>
<Project>
  <PropertyGroup>
    <Page Include="AAA.cs">
      <SubType>Designer</SubType>
      <Generator>MSBuild:Compile</Generator>
    </Page>
    <Page Include="CCC.cs">
    <Page Include="BBB.cs">
      <SubType>Designer</SubType>
      <Generator>MSBuild:Compile</Generator>
    </Page>
    <Page Include="DDD.cs">
      <SubType>Designer</SubType>
      <Generator>MSBuild:Compile</Generator>
    </Page>
  </PropertyGroup>
</Project>

Eww, that did not turn out well. Notice that “BBB.cs” is nested inside of “CCC.cs” and we don’t have enough closing </Page> tags. That’s pretty awful.

Without that .gitattributes file in place and using the standard merge strategy, the last merge command would result in a merge conflict which forces you to fix it. In our minds, this is better than a quiet failure that leaves your project in this weird state.

<?xml version="1.0" encoding="utf-8"?>
<Project>
  <PropertyGroup>
    <Page Include="AAA.cs">
      <SubType>Designer</SubType>
      <Generator>MSBuild:Compile</Generator>
    </Page>
<<<<<<< HEAD
    <Page Include="CCC.cs">
=======
    <Page Include="BBB.cs">
>>>>>>> branch
      <SubType>Designer</SubType>
      <Generator>MSBuild:Compile</Generator>
    </Page>
    <Page Include="DDD.cs">
      <SubType>Designer</SubType>
      <Generator>MSBuild:Compile</Generator>
    </Page>
  </PropertyGroup>
</Project>

Obviously, in some idyllic parallel universe, git would merge the full CCC element after the BBB element without fudging it up and without bothering us with these pesky merge conflicts. We don’t live in that universe, but maybe ours could become more like that one. I hear it’s cool over there.

What’s this gotta do with Visual Studio?

I recently asked folks on Twitter to vote up this User Voice issue asking the Visual Studio team to support file patterns in project files. Wildcards in .csproj files are already supported by MSBuild, but Visual Studio doesn’t deal with them very well.

One of the big reasons to do this is to ease the pain of merge conflicts. If I could wild-card a directory, I wouldn’t need to add an entry to *.csproj every time I add afile.

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.

Even if we did this, the merge driver only solves the problem for one version control system, though arguably the only one that really matters. :trollface:

It’s been suggested that if Visual Studio sorted its elements first, that would help mitigate the problem. That helps reduce the incidental conflicts caused by Visual Studio’s apparent non-deterministic sort of elements. But it doesn’t make the issue of merge conflicts go away. In the example I presented, every element remained sorted throughout my example. So any time two different branches adds files that would be adjacent, you run the risk of this conflict. This happens quite frequently.

Wild card support would make this problem almost completely go away. Note, I said almost. There will still be the occasional conflict in the file, but they’d be very rare.

Found a typo or error? Suggest an edit! If accepted, your contribution is listed automatically here.

Comments

avatar

44 responses

  1. Avatar for schotime
    schotime April 16th, 2014

    So what should the .gitattributes file be changed to or should the *.csproj entries be removed?

  2. Avatar for haacked
    haacked April 16th, 2014

    Remove the .csproj entry if you don't want the `merge=union` behavior.

  3. Avatar for Josh Kodroff
    Josh Kodroff April 17th, 2014

    TortoiseGitMerge (included in TortoiseGit) can help ease this pain in the short term if you set it as your git mergetool. If you choose "Include theirs before mine" (or more before theirs) in TortoiseGitMerge, it'll be the right solution almost all of the time. (If I had a dime for every time I've clicked that...)

  4. Avatar for Kijana Woodard
    Kijana Woodard April 17th, 2014

    There are merge issues with .sln files too. The numbering scheme for projects makes merges... fun. And there's that guid sitting right there, laughing at you.

    Time for proj/sln file format overhaul?

  5. Avatar for trailmax
    trailmax April 17th, 2014

    oh, that is what people scream about. I could never get the so much shouted about merging problem with *.csproj files. Maybe everyone should switch to TFS for a better merging experience ::trollface::

    Honestly, VS merging tool is quite good in that sense, our team never get this issue.

  6. Avatar for Max Battcher
    Max Battcher April 17th, 2014

    Visual Studio itself seems to have a complicated *proj merge built-in. I've wondered about its heuristics before and how it mostly avoids this issue. I think it does have something like you ponder of some file-specific techniques. It is part of why I tend to only use VS for git merges of VS projects.
    It's interesting too because I've seen (and battled) TFS repositories where those same file-specific merge tooling stopped working mysteriously and left these issues in TFS merges as well. That Team Project almost needed to be nuked from orbit given how bad merging was getting in it. (Lots of baseless merges super early in its history were I presume the origins of those issues, but I'm not sure.)

  7. Avatar for lovely
    lovely April 18th, 2014

    I recently asked folks on Twitter to vote up this User Voice issue asking the Visual Studio team to

    https://sites.google.com/si...

  8. Avatar for Nick Craver
    Nick Craver April 18th, 2014

    First, I agree this decision the right direction - a break is absolutely what I want to happen here.

    I pitched the .csproj sorting in Visual Studio on user voice a while ago: http://visualstudio.uservoi...

    While I agree it doesn't eliminate the problem in your example: that neighbor addition; for our large projects, it's a very minority case. The simple sorting would help tremendously: it would not change the format, yet almost always eliminate the same-point-in-the-list insertion behavior we always see. I'd argue that you're far more likely to have more developers, more branches, and commit conflicts as the project grows larger. Also not directly in the fix, but if the elements were in a known order (simple alphabetical as in the IDE I'd say) then merge tools have the option of making some more intelligent decisions.

    I honestly don't know why this hasn't been implemented yet; I assume it's just lack of being noticed and a programmer assigned to do the work. I can't think of a technical reason it can't be implemented, but as always maybe there's a deep, dark rabbit hole in that code. If there is no rabbit hole, I wish the team would address something as fundamental as these constant conflicts. The addition of a new file picking the same spot in the .csproj almost every time actively generates more merge conflicts. I think fixing that while breaking nothing is a reasonable request.

    An IDE experience isn't awesome if you immediately have to clean up after it. I'm really hoping this simple fix for a large number of our merge cases will land in a future update.

  9. Avatar for lovely
    lovely April 21st, 2014

    I pitched the .csproj sorting in Visual Studio on user voice a while ago

    http://zalochat.edu.vn/

  10. Avatar for Asbjørn Ulsberg
    Asbjørn Ulsberg April 23rd, 2014

    While adding wildcard support to Visual Studio would work, I find the whole Solution/Project file thingamajig to be extremely cumbersome, unintuitive and quite honestly; terrible from just about every aspect I'm able to imagine.

    Why can't a Visual Studio solution instead be identified by a .vs file that works like a .gitignore file where only stuff you don't want to include is listed? Wildcards should of course be supported, like in .gitignore. Individual projects can be identified by a .vs file in the project's subfolder.

    This would also remove the need to have a "Solution Tree" that is separate and different from the physical source tree; they would be the same. By default, everything would be included in the solution, but by right-clicking on files you could exclude them (adding them to the nearest .vs file) and you could easily partition the solution into projects by right-clicking folders and saying "Add project" which would bring up the ordinary "New Project" dialogue.

    Why nothing has been done to remedy or improve the .sln and .csproj madness is beyond me. Both that and MSBuild are terrible solutions to a very simple problem and they introduce so much headache to every step of the development process; from merging (have you ever successfully merged an .sln file?) to building (having the right .target files on the build server) to deployment (deployment packages are a bag of hurt).

    I know you don't work for Microsoft any longer, Phil, but I just needed to get this off my chest. But as a former Microsoft employee, it would be interesting to hear your thoughts on the matter. :)

  11. Avatar for haacked
    haacked April 23rd, 2014

    Man, I would love if you could take the .vs file and _point it to the .gitignore_ file and VS would use the gitignore to determine which files were or were not in your solution. That'd be pretty amazing.

  12. Avatar for Asbjørn Ulsberg
    Asbjørn Ulsberg April 24th, 2014

    Yea, that would be even better. I do expect stuff to be checked in to git that Visual Studio doesn't need to know anything about, though, so the .vs file might extend the .gitignore with more exclusion rules. Either way, "include by default" would be so much better than the train-wreck we have now.

  13. Avatar for kmerkle
    kmerkle May 1st, 2014

    You misspelled mispelling (sic)

  14. Avatar for akanshatyagi
    akanshatyagi May 2nd, 2014

    Nice Blog and Great article…its always working for me well…Keep going !!

    prestigeLakeside Habitat ,

    prestigeLakeside Habitat and

    prestigeLakeside Habitat

  15. Avatar for akanshatyagi
    akanshatyagi May 2nd, 2014
  16. Avatar for akanshatyagi
    akanshatyagi May 2nd, 2014

    This nice and useful blog....Thanks for this post, Thanks for posting
    this informative blog...well done.

    prestige sunrise park

    prestige sunrise park and

    prestige sunrise park bangalore

  17. Avatar for akanshatyagi
    akanshatyagi May 2nd, 2014
  18. Avatar for akanshatyagi
    akanshatyagi May 2nd, 2014

    Thanks
    for sharing the useful information with us..Keep going

    purvakara westend

    purvakara westend bangalore

  19. Avatar for akanshatyagi
    akanshatyagi May 2nd, 2014

    I am glad to read this post, its
    an interesting one. I am always searching for quality posts and articles

    purva palm beach

    Purva palm beach

  20. Avatar for akanshatyagi
    akanshatyagi May 2nd, 2014

    This nice and
    helpful blog....Thanks for this post, Thanks for posting this instructive
    blog...well done.

    prestige royal gardens , prestige royal gardens bangalore, and
    prestige royale gardens

  21. Avatar for akanshatyagi
    akanshatyagi May 2nd, 2014

    Thanks
    for sharing the helpful information with us..Keep going


    Purva whitehall


    Purva Whitehall bangalore


    Purva whitehall

  22. Avatar for akanshatyagi
    akanshatyagi May 2nd, 2014
  23. Avatar for akanshatyagi
    akanshatyagi May 2nd, 2014

    I liked blog
    very much.........

    MJR Clique

    MJR Clique bangalore

    MJR Clique

  24. Avatar for akanshatyagi
    akanshatyagi May 2nd, 2014

    Thanks for this post, Thanks for posting this informative blog.....well
    done.


    Mjr pearl


    Mjr pearl


    Mjr pearl

  25. Avatar for akanshatyagi
    akanshatyagi May 2nd, 2014

    I am thankful to read this post.....

    .skylark Ithaca

    skylark Ithaca
    bangalore

    skylark Ithaca

  26. Avatar for akanshatyagi
    akanshatyagi May 2nd, 2014
  27. Avatar for akanshatyagi
    akanshatyagi May 2nd, 2014
  28. Avatar for akanshatyagi
    akanshatyagi May 2nd, 2014

    Excellent
    blog and high quality article...........


    Purva midtown


    Purva midtown bangalore


    Purva midtown

  29. Avatar for akanshatyagi
    akanshatyagi May 2nd, 2014

    I am always searching for quality
    posts and articles..


    Shriram chirping woods


    Shriram chirping woods Bangalore


    Shriram chirping woods

  30. Avatar for akanshatyagi
    akanshatyagi May 2nd, 2014

    Nice Blog and grand article…its
    forever working for me well…Keep going !!!!!


    Divyasree republic of Whitefield


    Divyasree republic of Whitefield
    bangalore


    Divyasree republic of Whitefield

  31. Avatar for akanshatyagi
    akanshatyagi May 2nd, 2014

    Good blog... its evermore working for me well .!!!!


    provident the tree

    provident the tree Bangalore


    provident the tree

  32. Avatar for akanshatyagi
    akanshatyagi May 2nd, 2014

    I liked blog
    very much......... Nice Blog and grand article

    Shriram
    sameeksha

    Shriram
    sameeksha bangalore

    Shriram
    sameeksha

  33. Avatar for Daniel15
    Daniel15 May 3rd, 2014

    Wouldn't a proper three-way merge fix this? This is a trivial problem given the original file, "theirs" and "mine". It should be obvious that two hunks of code were added.

  34. Avatar for haacked
    haacked May 4th, 2014
  35. Avatar for Ali Servet Donmez
    Ali Servet Donmez November 6th, 2014

    I just created a brand new repository with GHfW `2.5.2.2` (currently latest), and it appears to me that auto generated `.gitattributes` file with `*.csproj merge=union` in it (along with all the others.)

    Any ideas?

  36. Avatar for Josh Thompson
    Josh Thompson December 4th, 2014

    I just created a project on GitHub called CsProjArrange. I created it to
    fix the merging issues in my git repository. There are still some kinks
    which need to be ironed out, so I am open to some pull requests if you
    see anything that can be improved. It is located at https://github.com/miratech...

  37. Avatar for Jacob
    Jacob January 14th, 2015

    Does it make sense to remove all of the other project types, too? (*.csproj, *.vsproj, etc)? It seems like yes, but I wanted to make sure there wasn't a subtle reason not to.

  38. Avatar for haacked
    haacked January 14th, 2015

    Yep! There's no reason to be inconsistent in this case.

  39. Avatar for Piotr
    Piotr November 21st, 2016

    So true! This also applies to resource (*.resx) files :( and I've actually used sorting to make this happen less often - could I just say that "great minds think alike" ;)

  40. Avatar for LoL Auto Client
    LoL Auto Client January 24th, 2017

    And how do I explicitly enforce this behavior for other file extensions?

  41. Avatar for LoL Auto Client
    LoL Auto Client January 24th, 2017

    Anc can I add other files that will be then treated like those csproj files? I have some other file types that are often being merged wrongly, such as .filters visual studio files.

  42. Avatar for TrSek
    TrSek May 23rd, 2017

    For commit cproject use sorter before commit.
    https://github.com/Trsek/cp...

  43. Avatar for James Robertson
    James Robertson January 2nd, 2018

    I realise this is an old post, but it's encountering these sorts of problems that forced me to write Oso XML Merge in the first place. Plug it in to your favourite UI, train it up with the XML files you're dealing with, and it will resolve most "conflicts" for you automatically.

    In the given example, if you told it to identify Page elements by the value of the Include attribute, it would give you both sides (BBB and CCC) as separate additions to the merged file.

    Most of the time I tell SmartGit to open the conflict resolver, I hit save straight away (in Oso XML Merge), and get on with my life. ;)

    More info here: https://osocorporation.com/...

  44. Avatar for secretgeek
    secretgeek March 6th, 2018

    Just use AI already. Surely Machine Learning can just merge everything for us in a sensible way already?

    (I like your writing Phil, the bits like 'if you’re following along' and 'For those who lack imagination' -- good!)