DEV Community

Cover image for How to Prevent Merge Conflicts (or at least have less of them)
Rizèl Scarlett for GitHub

Posted on • Edited on

How to Prevent Merge Conflicts (or at least have less of them)

A few months ago, I wrote a DEV post about resolving merge commits. The resounding feedback I received from readers was: "JUST AVOID THEM." While I agree, I wrote the post for technologists who were past the point of prevention.

Merge conflicts are mostly inevitable. You will experience more than one merge conflict in your career, but with good communication and planning, you can reduce the number of merge conflicts you encounter. Let's discuss how we can do that!

Understand why merge conflicts happen

I mentioned this in an earlier DEV post, but I think it's worth repeating.

Version control systems, like git, auto-magically manage code contributions. It identifies the change, when it occurred, who made it, and on what line so that developers can easily track the history of their codebase. However, git sometimes gets confused in the following situations:

  • When more than one person changes the same line in a file and tries to merge the change to the same branch
  • When a developer deletes a file, but another developer edits it, and they both try to merge their changes to the same branch.
  • When a developer deletes a line, but another developer edits it, and they both try to merge their changes to the same branch
  • When a developer is cherry-picking a commit, which is the act of picking a commit from a branch and applying it to another
  • When a developer is rebasing a branch, which is the process of moving a sequence of commits to a base commit

Git is unsure which change to apply, so it leans on the developer for help and notifies them of a merge conflict. Your job is to help git determine which proposed change is most accurate and up to date.

The benefits of preventing merge conflicts

Sometimes, doing that upfront work for your team or yourself to avoid merge conflicts may seem tedious, but it's worthwhile. Using guard rails to prevent merge conflicts will save time and increase developer happiness. Solving a bug, writing a new feature, or scripting automation is time-consuming. Adding the barrier of debugging and resolving a merge conflict means it takes longer to merge to 'main' and longer to deploy to production. Also, if your team is constantly experiencing conflicts, they'll eventually feel disenchanted with their work.

Four ways to prevent merge conflicts

Standardize formatting rules

Many times conflicts occur because of formatting discrepancies. For example, a technologist inadvertently added extra white space on the same line where another developer added new code. Also, people have different coding styles. For instance, some JavaScript developers use semicolons, but some don't. If you're working on a team, enforce code formatters and linting rules. Make sure everyone on the team is aware of these rules and tools to reduce the number of merge conflicts you experience due to formatting.

Make small commits and frequently review pull requests

I'm going to admit something super embarrassing. Back in the day (~3 years ago), I would make pull requests changing over 50 files, and then I would get annoyed that I had so many merge conflicts. In hindsight, I was wrong. Changing over 50 files in less than two weeks increased the chances that other developers also made updates to those files.

Also, creating large pull requests discouraged my teammates from thoroughly and quickly reviewing my code. My pull request would sit longer than necessary because it was a behemoth my teammates preferred to avoid.

Take my mistakes as a lesson to make small changes, commit them, and have folks review the pull request as soon as they are available. This way, you'll have fewer chances to change files that other teammates are simultaneously working on.

Rebase, rebase, rebase (early and often)

(I write this hesitantly, and I'm preparing for the comments section to disapprove. Clearly, I don't want peace; I want problems always. I'm being dramatic and referencing a meme. See below:)

Image of a man saying I don't want peace. I want problems always

I learned about rebasing in 2019 from a coworker that my team dubbed the "Git-tator" (Git and dictator combined). He was earnest about improving our startup's git history and reducing the number of blockers we experienced due to merge conflicts. Arguments ensued because many developers were often working on extensive features simultaneously. We even argued over simple conflicts when people imported new dependencies for different features in the same file on the same line. Thus, he introduced us to rebasing, which significantly improved our software development workflow.

What is rebasing?

The git rebase command reapplies changes from one branch into another, which is very similar to the git merge command. However, in this case, git rebase rewrites the commit history to produce a straight, linear succession of commits.

How rebasing helps prevent merge conflicts

Rebasing is not going to magically remove all merge conflicts. In fact, you may encounter conflicts while rebasing. Sometimes, you will have to repeatedly resolve the same conflict while rebasing. However, merge conflicts happen because multiple changes happen to the same chunk of code simultaneously. If you rebase your local working branch with the default branch (main or master), you're rewriting your local commit history with the default branch's history and then reapplying your changes. In many situations, rebasing first and then merging can make teamwork easier. Rebasing is an option, but not the only solution.

Be careful with rebasing

Be warned – there are moments when rebasing is not recommended. Rebasing is dangerous because you're rewriting history. When rebasing, pay attention to the changes you're accepting because you risk overwriting teammates' changes or including files your teammates intended to delete. You also probably wouldn't want to rebase a public repository on the main branch because that would inaccurately rewrite the history.

If you're new to rebasing or nervous about rebasing, pair with a teammate to sanity check.

How I rebase to avoid merge conflicts

There's no correct workflow – just preferred ones. Here's the workflow I follow as determined by teams and codebases I’ve worked in:

  • If I'm working with more than one person, I'll create a feature branch from the default branch. This way, we can contribute to the feature branch in tandem.
  • I'll make a new local branch from the default or feature branch
  • I'll add and commit new changes to my local branch
  • I'll rebase updates from the default or feature branch
  • I'll merge changes from my local branch to the default or feature branch

Pay attention and communicate

No git command or software tool can replace the need for communication in engineering teams. Being a good software developer and collaborator goes beyond writing code. Good software developers communicate with teammates. Keep your team aware of what files you will be touching and coordinate with your Product Manager and SCRUM Master to avoid working on features that conflict with other features.

If you're working alone, pretend you're working on a team by:

  • creating branches
  • creating pull requests
  • Avoid allowing pull requests to become stale
  • Make sure you're not changing the same lines of code before merging a prior change
  • Establish and follow formatting rules

Working alone shouldn't stop you from practicing healthy coding habits, so try to keep your codebase clean! Your codebase may turn into an open source project or a project that you show off in interviews.

If you're reading this and you're thinking, "I just need help resolving a conflict," head over to this post.

Please note: I didn't exhaust all the options for preventing merge conflicts, so comment below with methods you use to avoid merge conflicts.

Also, follow GitHub and me on DEV for more awesome content.

Stay tuned for a post I'm working on that identifies the differences between a merge commit, squash, and rebase.

Top comments (25)

Collapse
 
tandrieu profile image
Thibaut Andrieu

"Pay attention and communicate" that's so true !

If you hear during stand-up "I'm renaming that file", or "I'm refactoring that part", just synchronize yourselves instead of continuing in your own development bubble !

Collapse
 
blackgirlbytes profile image
Rizèl Scarlett

I'm glad that you and others agree with me on this!

Collapse
 
drsensor profile image
૮༼⚆︿⚆༽つ • Edited

Based on git-branchless wiki, development is already underway for stock Git to get in-memory merges (speculative merging). Seems in the future we can have less "bad times" with merge conflicts.

Collapse
 
blackgirlbytes profile image
Rizèl Scarlett

That's exciting to look forward to!

Collapse
 
netch80 profile image
Valentin Nechayev

One thing shall be clearly noted to understand most of rebase problems:

A rebase is hidden merge.

The first direct consequence is that a rebase result shall be tested (with assigned tests) and verified (at least visually).

The second one is that conflicts found in rebase are principally the same found in merge. But, as far as people usually think easier in a sequential history, rebase result is simpler to read later on. More so, git automatically shows single-parent commits as diffs but doesn't do this for merge commits - they require separate cumbersome actions to find their changes out.

And, well, rebasing of published changes is, at least, a tricky action. Some sources afford this constantly (Linux kernel development trees). There is a section "Recover from upstream rebase" in Git manuals. Anyway, this is the true aerobatics:)

Collapse
 
blackgirlbytes profile image
Rizèl Scarlett

Well said! Thank you. I learned a lot from your comment

Collapse
 
hollyw00d profile image
Matt Jennings

I think it's good to modularize code (AKA put code for a feature into a single file).

For example, when I working in JavaScript if I'm creating a an accordion I might name the file accordion.js and then I'll use a task runner and import statements.

Collapse
 
goncalorodrigues profile image
Gonçalo Rodrigues

I absoultely hate merge conflitcts. As you said, they aren't completely avoidable but I'm pretty much doing what you outlined and it works for me.

My workflow is like:

Start writing code on a new branch
When done, commit my changes (always striving for small commits)
Pull and rebase changes from main (git pull origin main --rebase)
Push

If there are any changes while the the PR is under review, I just rebase again and push force. This makes my branch history clean and is fine because usually there's a single person per feature branch.

Also, and this might be controversial, I strive to have a single commit per PR. So I often do git commit --amend followed by git push -f when doing small fixes. I guess I picked this up from when I worked at Google and we don't use branches there and there's always exactly 1 commit per PR (called a changelist) and now I can't work any other way :)

Collapse
 
blackgirlbytes profile image
Rizèl Scarlett

ah, very nice! I love to hear about developer's workflows. Although the last part is a little controversial, this sounds like it works well for you. I hope your comment is helpful for other people.

I think sometimes people have to experiment and figure out what workflow is best for them as individuals and as teammates.

Collapse
 
suchintan profile image
SUCHINTAN DAS

Very true words It's really important to make small commits and always make a communication with the team. The less the communication gap 🗨 the less it takes to merge branches 🧑‍💻.

We developers need to understand, that yes we may have been lone wolves but communication always helps and team work does makes things less complicated and easy to approach ✌.

Collapse
 
blackgirlbytes profile image
Rizèl Scarlett

You worded this well! Communication and teamworks always makes coding a little easier!

Collapse
 
artdevgame profile image
Mike Holloway

This goes under formatting rules, but where possible I like to sort lines alphabetically (things like union types, options, language translation keys, even CSS rules and function/variable declaration)

I find this helps avoid some unnecessary conflicts, but has an added benefit of making code a little easier to grok/scan when searching for needles.

Collapse
 
blackgirlbytes profile image
Rizèl Scarlett

That's a great tip! I do this as well!

Collapse
 
mmayboy_ profile image
O ji nwayo e je

How do you reconcile small, frequent commits with the fact that what you're working on is neither complete nor has been tested, and you don't have a clue how many files it's going to affect before you're done?

Collapse
 
blackgirlbytes profile image
Rizèl Scarlett

I think that's where working with a product manager and team to make smaller stories comes in. Like if breaking the tickets into small little bits..where you don't have to complete the whole feature for it to parts of it to get merged in..

OR creating a feature branch that branches off of the main branch. Then making small commits into the feature branch and frequently rebasing off the main branch. That way your feature branch is up to date with the main branch.

Happy to hear other people's thoughts on this.

Collapse
 
aghost7 profile image
Jonathan Boudreau

Small file size is also a good way to avoid conflicts. There's a smaller chance that other people will be editing the same file that you're working on.

Collapse
 
blackgirlbytes profile image
Rizèl Scarlett

Yes, i agree with this!

Collapse
 
renanfranca profile image
Renan Franca

That's really helpful! Thank you 💕😊

Collapse
 
blackgirlbytes profile image
Rizèl Scarlett

I'm glad! You're welcome and thank you for reading!