For teams using version control, merge conflicts are just a fact of life. They can never be entirely avoided when you have groups of developers working on a number of shared projects. It’s not that adopting version control creates a new problem. Merge conflicts just highlight where work would have been overwritten when deploying between orgs.
In teams that haven’t yet found an effective way to work with version control, or who are new to Git, merge conflicts often become a big point of frustration. It’s easy to forget that small changes to the way your team works can dramatically reduce the likelihood of merge conflicts, and significantly reduce their complexity when they rear their ugly heads. In this post, we’ll break down the main causes of merge conflicts, help with ways to minimize their likelihood and offer a solution to keep them at bay.
What is a merge conflict?
Merge conflicts happen when two different Git branches, that have both had changes made to the same files, are merged back into a common branch.
These are usually changes made on the same or adjacent lines, and require human intervention to decide what the resulting change should look like. So, Git knows there’s an issue, doesn’t know how to deal with it, and to prevent something being overwritten, will ask you how to proceed.
What causes merge conflicts for Salesforce teams?
The longer you leave a branch open, the more likely it is that a teammate will begin working on the same files as you. You could be stepping on each other’s toes, which will cause havoc when you try to merge.
Salesforce’s representation of metadata
There are a few ways in which the on-disk representation of Salesforce metadata isn’t ideal:
Salesforce metadata is represented as XML, and Git’s diff is notoriously bad at comparing changes in XML files.
Metadata is often returned in a non-deterministic order by Salesforce’s metadata API. This means the ordering of elements within an XML file returned by the API can vary from request to request without any underlying semantic difference. As a result, you may hit all sorts of conflicts where you might not have expected any.
Salesforce uses very large files to represent certain metadata types containing many configurable elements. Profiles are a great (or infuriating) example where changing the permissions for two unrelated objects can result in adjacent changes in a Profile giving rise to merge conflicts.
5 ways to avoid merge conflicts
1. Avoid teammates overlapping on the same parts of an org
It’s a good idea to avoid having multiple developers working on the same areas of the codebase at the same time. Merge conflicts can only arise when multiple changes are made to the same file. Avoiding this can be really difficult for those larger types, such as Lightning Pages and Layouts.
Profiles are notorious for breeding merge conflicts because they contain entries for lots of different parts of your org; just take a look at the API docs to see how many subtypes there are. Since Profiles contain entries for the majority of your org, and because they’re stored as large, single files, two developers making changes to independent parts of an org may end up touching the same Profiles. Hello merge conflict!
2. Merge your branches early
The sooner you merge your branches, the easier it will be to resolve any conflicts when they do occur. That’s because the closer you are to the point when you did the work, the more likely it is you still have the context of the changes you were making and your main branch won’t have changed that much. Fewer changes will have been added and merged since you made your change, reducing the likelihood of merge conflicts in the first place.
Let’s consider two scenarios: ‘Merge late’ and ‘Merge early’. Both options involve the same feature branches being created on the same day. But the branches are all merged late in the sprint under ‘Merge late’, and merged as soon as they’re ready under ‘Merge early’.
There are a few key things to note here:
It’s easier to resolve a conflict when you merge early. Resolving a conflict is significantly easier when the changes involved are fresh in your mind. In the ‘Merge late’ example, feature branch F1 isn’t merged until right before the release, even though the developer responsible went on to work on other features. The details of F1 won’t be fresh in their mind, making it harder for them to remember what really matters about the changes. This will mean it’s more difficult to resolve any conflicts, and take time to work out which changes should be kept.
By merging early, later branches start off with the added features already in them. Under ‘Merge early’, F3 has the changes from F1 at the outset, so F3 and F1 can’t be in conflict. F4 has the changes from F1 and F2, so F4 can’t be in conflict with either of those features. Conflicts can only arise when two or more branches are used in parallel. For instance, F4 can only conflict with F3 - the one branch created while F4 was open.
Merging late results in 6 possible conflicts, whereas Merge early only has 3. Trying to merge 4 features at the end of a sprint, when each feature could conflict with any other, means there are 6 possible merge conflicts. This is cut in half under ‘Merge early’; as we’ve seen, 3 of the possible conflicts are avoided. What’s more, those 3 conflicts will be resolved as soon as the work is finished, without any context switching, so they’ll be much easier to resolve.
These are fairly contrived examples used to illustrate the point, but the fact remains: merging early reduces the likelihood of conflicts, and makes conflicts much easier to resolve.
3. Merge often
The longer a branch exists, the more changes it’s likely to accumulate. If everybody on your team is merging early and merging often, your branches will be short-lived. As a result, you’ll have far fewer accumulated changes in each branch. And because your branches touch fewer items, they’ll be significantly less likely to cause a conflict when merged.
A common argument against merging often is that long-running projects will take a long time to get into a mergeable state. There’s something to be said for that argument, but it almost always boils down to a matter of process and mindset, rather than any kind of technical necessity.
At Gearset, we deliberately break up large, complex features into small slices. We use
feature flagging to keep features hidden from users while in development, which allows us to merge our changes regularly and keep our feature branches short-lived, even for long-running projects. When a larger project is ready to be shipped to users, rather than having to merge lots of changes in one go and resolve a large number of conflicts, we just switch a feature flag to enable the feature for the users.
4. Encourage teammates to take ownership of their merges
It’s quite common for teams to hand off the responsibility of merging to release managers. There are many reasons why teams might want to do this, including freeing up a busy developer or keeping consistency with one team that merges all changes.
Whatever the case, merges will cause much less of a bottleneck for your team if you encourage team members to merge their own work. The reason for this is simple: developers are much more familiar with their own work and are much better placed to make decisions on how to resolve any resulting conflicts. Team members who merge their own work also tend to merge earlier, bringing all the benefits described above.
I’m not suggesting we eschew peer review here. Using pull requests or their equivalent to have each change reviewed by another team member is fundamental to a healthy development process. But until that change is merged, it should be the responsibility of the original author to make sure that feedback is responded to and the change is ready for release.
5. Follow good practices of decomposition for your Apex classes
Remember that merge conflicts arise as a result of team members making changes to the same file. A good way to avoid this happening is to use smaller files, and more of them. There aren’t many places you have control over the composition of the source for your org. Where you do, you should take advantage of it.
Apex classes are a great example. By using smaller files, one for each unit of code, you’ll also be following good software engineering practice. The single responsibility principle (SRP), one of the SOLID principles for writing maintainable code, states that each unit in your codebase should be responsible for a single piece of functionality.
In practical terms, SRP means you shouldn’t have large classes that do multiple things. ‘Utilities’ classes are a classic example of this, where lots of independent bits of functionality are grouped under one ambiguous name. Another common example is a controller that contains lots of business logic. Rather than build all of your business logic into your controller, it should be split into independent classes, with smaller files responsible for specific actions.
Pipelines: Avoid Salesforce merge conflicts once and for all
Okay, so you can’t banish all merge conflicts forever, but what you can do is try to limit their frequency and remove the pain when one crops up. One way to do this is to use Pipelines, Gearset’s new interface that gives your whole team an overview of the current state of all the environments in the release pipeline.
Pipelines is the command center and source of truth for your Salesforce release process - even if only some of your team use the rest of Gearset’s DevOps toolset. It gives your team complete visibility of all changes that are happening in your release pipeline, how much work there is waiting to be pushed, and provides you with the tooling to get these changes promoted through your environments quickly and successfully, without ever leaving Gearset.
But how can Pipelines help with merge conflicts? Rather than rely on Git to merge changes and suffer the resulting conflicts, we’ve implemented a new metadata-aware semantic merge algorithm, designed to dramatically reduce the frequency of merge conflicts in Salesforce metadata.
For example, if two developers on two different Git branches both add a new Custom Field, they might easily end up with new permissions in a Profile, on the same line of that Profile. Whoever merges their branch first will ‘win’ the line, and the other person will need to edit the XML manually, or decide to take one side of the conflict.
Gearset’s semantic merge will know that each of the field permissions is actually for a different object - so it knows to put them each on a new line, in any order. Headache avoided, and you’re free to continue deploying happily.
There are some cases where even Gearset won’t be able to make the decision for you, for example if two developers change the name of a
label. It’s not physically possible to include both so someone needs to choose which one to move forward with. We’ve made it as easy as possible to do this right in the Gearset UI. Just click on the merge conflict when Pipelines flags it, and decide which changes to accept.
Give in to the urge to merge!
DevOps is fundamental to any high-performing Salesforce team. But having a stalled release pipeline and an overworked release manager frantically working through a big list of merge conflicts can erode a team’s faith in the process, leading teams to fall back on their former ‘easier’ practices.
This isn’t a failure of DevOps, but a failure to embrace DevOps as a way of working. Adopting source-driven development means much more than injecting Git into your existing process. The highest performing teams actively go out of their way to avoid merge conflicts altogether following the 5 steps above, and let Gearset do the hard work whenever merge conflicts do crop up. If you’re not already doing the same, you have a massive opportunity to transform your DevOps process, and your business.
You can start using Gearset today to take full advantage of Pipelines, get a clear overview of your orgs and spend way less time resolving merge conflicts. Find out how Pipelines can change the way you deploy today!