Merging Visual Studio Project files: there’s not much nice to say about that. It’s just a pain, and I’ve ssen it become the number one or two reason of errors when merging between code branches.
Even when a project consists of a single team, it is not unusual that one developer adds a bunch of files while another one adds some more, or deletes and moves things around in the project. The source control system and its diff/merge tooling easily copes with the added and deleted code files, and it reliably notices and auto-resolves changes within code. However, to integrate into Visual Studio and the build process, code files also need to be referenced correctly in the *.csproj (or *.vbproj) project files. Here, developers often find strange conflicts, and the diff/merge tools are often not much of a help.
Project files consist or XML that is read by Visual Studio and Msbuild. Their root element is a Project. Project has child elements that indicate properties of the project like its build configuration (and some more), but most important in this context are the ItemGroup elements that contains the content the project:
- Compile elements that define files compiled into the assembly
- Content elements that define files not compiled, but otherwise included in the project: MVC views, script files, images contained in a Silverlight XAP…
- Reference elements that define the projects binary or project dependencies.
Here’s a simplified project file for an ASP.NET MVC project file:
<Project> <!-- Project properties and extensions omitted for brevity --> <ItemGroup> <Reference Include="System.Web.Mvc /> <Reference Include="System.Web.WebPages" /> </ItemGroup> <ItemGroup> <Compile Include="Controllers\SquirrelController.cs" /> <Compile Include="DataAccess\SquirrelRepository.cs" /> <Compile Include="Global.asax.cs"> <DependentUpon>Global.asax</DependentUpon> </Compile> <Content Include="Global.asax" /> <Content Include="Content\Site.css" /> <Content Include="Web.config" /> <Content Include="Web.Debug.config"> <DependentUpon>Web.config</DependentUpon> </Content> <Content Include="Web.Release.config"> <DependentUpon>Web.config</DependentUpon> </Content> <Content Include="Scripts\jquery-1.4.1.js" /> <Content Include="Scripts\jquery-1.4.1.min.js" /> </ItemGroup> </Project>
Now why is there such a problem? First, most diff/merge tools just do not understand XML. These tools are designed to identify blocks of code. They know things about code: that outside strings, different kinds of whitespace can be considered as equivalent; that changes within a comment are less significant than elsewhere; where casing is significant or not. What most tools do not understand is that all of the following statements are equivalent:
<Squirrel name="Alex" fur="red"/> <Squirrel fur="red" name="Alex" /> <Squirrel fur="red" name="Alex" /> <Squirrel fur="red" name="Alex"></Squirrel>
Secondly, the order of ItemGroup elements in the project, and of elements within them, does not matter – in programming languages, the sequence of statements is essential. Now that for project files the sequence is not significant, Visual Studio feels free to arrange items as it feels fit – and the diff/merge tool report hard to resolve conflicts even if two versions of a project file are absolutely equivalent.
Avoiding the pain
One feasible way of avoiding merging between versions of project files is to apply pessimistic locking. Only one developer can have a writable copy of the project, and there are no more conflicting version (at least within the same code branch). The downside is that developers may have to wait for their colleagues to finish their revision. The most frequent thing that a developer does with a project file is probably adding content. To get the shortest possible pessimistic lock on the project file, it should be committed as soon as the files are added – the added files should not contain any code at that point in time that could break the build until it is complete.
However, this approach does not remedy merges between code branches, and does not work with source control systems that just do not allow any pessimistic locking. And this includes each distributed source control system like Git or Mercurial.
As we’ve seen, the root of the problem is not that project files are resistant to merging by their nature; it’s just the standard tooling that do not fit. However, there are specific tools that can help. The best candidate I’ve seen so far is a commercial yet relatively cheap (19€) tool called ProjectMerge (http://www.projectmerge.com). ProjectMerge understands XML: it recognizes equivalent XML elements and attributes despite or a possibly different serialization into text. But what is really sweet is that ProjectMerge can be told if the order of elements matters, and by which attributes equivalent elements are determined. In the case of Visual Studio project files, this attribute is the Include attribute of Compile, Content, None, and Reference elements. In effect, ProjectMerge will be able to understand these instructions are equivalent:
<Compile Include="1.cs" /> <Compile Include="2.cs" /> <Compile Include="3.cs" /> <!-- and --> <Compile Include="3.cs" /> <Compile Include="2.cs" /> <Compile Include="1.cs" />
The necessary settings to understand a specific project file format are stored in a distributable file format. Here’s my (probably not very complete) shot at C# projects – download here (from my Dropbox) and store under
I haven’t used the tool in practice yet, but I think we’re going to try it in a >10 person team over the next week, and see how it performs.