We've recently had to merge a version branch of a codebase back into the trunk. You see, two teams had been working on mostly separate parts of a system for three months. The result was quite painful. We lost several days on cursing, fixing merging- and tree conflicts, testing the code and working through new bugs. This bothered me quite a bit, not in the first place because I was the one cursing as I did most of the merge. But also because I felt that as a team, we were wasting precious time and money.
I am quite aware that spending a few days on merging conflicts is not at all that long. Many teams spend weeks or even months merging branches and trunks. But within the scope of our two-week iterations, even a few lost days are very expensive. So, in this blog I want to explore some practical tips to decrease merging conflicts. A general underlying principle is to keep your files small. Large files, especially when they have to be changed often by different people (like stylesheets, code files or script files), are very likely to cause version conflicts. Keeping everything small and efficient will make your life a lot easier. It was also make your code a easier to understand, maintain and read, so it's good advice for other reasons as well.
#1: Integrate as often as possible (or continuous integration through small commits)
The single best thing you can do is integrate versions as often as possible. If you can manage it, integrate 'continuously'. This boils down to:
- Within the team, commit/push your changes as often as possible, at least a few times a day;
- Keep your commits small, or atomic. A few files at a time is the best, because it makes it easier to determine what's happening in the code base when you have to merge versions;
- Commit/push changes when you've finished functionality, like a user story, fixed a bug or completed another kind of code change. This will also make your commits more meaningful, as they add completed functionality to the trunk. It's useful to add a 'merging requirement' to your definition of done or even add a 'merged to trunk' state column to your Scrum Board. If your user stories are small enough (as they should be) committing periodically should not be a problem;
- If multiple teams are working on the same codebase, integrate changes between the teams at the end of every sprint with the other teams. This will still be painful, but it will be far less painful than having to merge back weeks or even months of changes. This will require some planning, because only completed, working and tested functionality should be merged. But this is why you are Scrumming anyways, right? ;);
#2: Add meaningful comments to your commits/pushes
If you use a version control system (like SVN) that allows you to comment on pushes or commits, make sure to explain what you are committing. I can't stand commits with comments like 'bla' or 'changed stuff' (I've been quite guilty of this as well, by the way). If you are the guy that has to merge everything, you're going to end up cursing these kinds of messages. Once you start running into merging conflicts, you'll need all the context you can get to determine which lines of code are supposed to end up in the final version. Comments can give you this kind of context. A good comment is something like 'I completed User Story '[story]'. I changed file A because [reason] and file B because [reason]' or 'I fixed Bug [bug] and made the changes [changes] in file A'. If another developer or team changed the same file, and a merging conflicts ensues, you can determine why code was changed and what the final code look be like.
#3: One class per file
Version control becomes very painful if many people are working on the same code file. Although they may be changing different parts of the code, the simple act of re-arranging classes or adding some enters confuses a lot version control systems. Instead, stick to a 'one class per file'-strategy by putting every class in your solution in it's own file (e.g. ClassName.cs). In fact, do the same for interfaces, enumerations and structs. This greatly reduces the chance that two developers are working in the same file and reduces version conflicts.
This is good coding practice in general because it keeps your code files short, easy to locate, easy to comprehend and easy to re-arrange or re-use. Some developers don't like the number of files this strategy generates. I don't see the problem, as long as you organize your files into meaningful folders (again, a good general coding practice). If you have a bunch a classes, put them in a 'Business logic' folder. Or better, come up with a meaningful name like 'Order processing'. This kind of logical grouping is a good thing to do because it helps you to understand the code on a more abstract level.
#4: Keep your classes small
In addition to using one file per class, also keep your classes as small as possible. There is no clear guideline as to what constitutes 'small', but I think a good size is between 1 and 200 lines. If you keep your classes small, you significantly decrease the chance of several developers working in the same file. Again, this advice is a good coding practice in general. Small classes are a lot easier to comprehend, maintain and test because writing small classes requires you to very carefully consider and split responsibilities of your code. It also makes your code more modular and more suiteable for testing and design patterns.
#5: Split up your stylesheets
Stylesheets are often a major pain when merging versions as they tend to grow very large and have to be changed often by different people. Instead of putting your entire style definition in one file, consider splitting your stylesheet into many smaller files. A good strategy is to put your global style elements in a global stylesheet (e.g. 'global.css' or 'common.css') and your page-specific style elements into separate files (e.g. 'customerview.css' or 'logoff.css'). In the head of your HTML file, you can do something like:
<head> <link href="common.css" rel="stylesheet" type="text/css" /> <link href="customerview.css" rel="stylesheet" type="text/css" /> </head>
This approach will require you to consider which styles are global and which are page-specific, but it seems to me like this is a good thing to be aware of anyways. There are further advantages:
- Less bandwith used by visitors: By splitting up your stylesheets, visitors don't have to download all the style definitions. Instead, they only download what they need. The browser will cache further calls, but the initial download will be quite a bit faster if a combined stylesheet is very large;
- Easier to maintain: Smaller stylesheets are a lot easier to maintain, as long as they are carefully grouped and split. It's good to think about how to split up your stylesheets, because it gives you a more abstract understanding of which styles are needed where;
- Easier to comprehend: Whenever I run into a very big stylesheet, I often feel inclined to just add my style element somewhere at the bottom of the stylesheet (you're probably doing this as well ;). It takes a lot of time to comprehend the entire stylesheet and find the best place to insert my own style. But this is a bad practice. Smaller stylesheets make it a lot easier to comprehend what's going on. If you are adding a page-specific style, put it in the page-specific stylesheet. Put your global style elements into your global stylesheet;
If you are using MVC4, make sure to check out the cool new bundling and minification features. You can use this feature to create bundles of stylesheets and script files and also minify them while you're at it. If you are using WebForms or another framework, you can consider Cassette. But it's not hard to build your own framework. You can make a common handler like 'stylesheet.aspx' that takes the pagename as a parameter. Within the handler, you can load the global css and the page-specific stylesheet(s) and combine them into one file. You can even add your own minification logic. It's not very hard to do something similar with a StyleController in MVC1, MVC2 or MVC3 or even with HTTP modules.
#6: Split up your script files
#7: Use a distributed version control system (DVCS)
A lot of version control systems are so-called 'centralized version control systems' (CVCS). Well-known platforms are Subversion, CVS or Perforce. A more recent approach to centralized version control is a distributed version control system (DVCS). In this scenario, all developers working on a codebase have the entire repository with the entire version history on their machines. Changes are synchronized between peers using a peer-to-peer protocol. When a developer is working with a DVCS, he usually makes many small commits to his local repository. Once a whole piece of functionality is done, an entire change set can be pushed to peers. The peers will take care of the merging if there are conflicts.
The chances of version conflicts are significantly smaller with DVCS because the merging logic has a lot more information/context to work with. Instead of file commits, DVCS uses the concept of a change set (a set of related changes) to determine how to merge files. In other words; DVCSs know (when used correctly) which changes are logically related, and can merge more intelligently because of this. An added benefit of this approach is that a DVCS stores change sets themselves in the version control system. This means that you can always revert to before a change set. Information is never lost because of merging conflicts, which happens quite often with centralized version control systems.
A nice benefit of a DVCS is that it's very robust against data loss. Yes, a developer can lose it's hard drive with a copy of the repository. But the chances of losing your entire codebase is a lot smaller with many different copies of the entire repository living on every developer's desktop than when you have one centralized repository.
For Agile Teams, speed is of the essence because the sprint is always putting pressure on delivering business value. Instead of losing precious time and money on version merging conflicts, invest time in learning how to avoid unnecessary conflicts with your team. The above tips may give you some practical pointers to get on your way. Good luck and happy merging! And let me know what you think!