When you create your feature branch, you usually do so from either a development or main branch. This is the base of your branch and, usually, it is also the destination where you will want to merge your new feature into, once you’re finished with your development.
But in the meantime, you’re developing your awesome new feature – while your teammates work on other branches, merge branches or make commits to this base branch. As soon as developers make changes to the same files – to the same lines of code, to be more specific – someone will end up with merge conflicts, at some point in time.
Commit often — but reduce the number of commits
It might sound like an oxymoron but it can be done easily. The more commits your feature branch has, the more work you will run into once you try to get the changes from the base branch into your branch.
The one thing you need to do is to add the –amend flag to your commits like so:
git commit -m 'feat: my awesome new feature'
# more work done here...
git commit --amend
What this instructs Git to do is, oh surprise, to refactor the first commit and incorporate the changes made into it. This way your commit history stays lean and conflicts only need to be resolved once.
By doing this consistently, you eliminate the need to squash commits altogether and keep your repository history compact and lean.
Squash commits before getting changes from the base branch
Let’s say you forgot to amend commits, or simply have a big feature branch with many commits. Before you merge (or rebase) the latest changes made by your teammates to the base branch into your own branch, you can squash all of your commits into a single one. Yes you lose some context and history by doing so, but resolving possible conflicts will be as pain-free as possible.
There are several ways to squash your commits, let’s look at the easiest one first. This method should only be used when you know the <base> branch of your feature branch, for example that it originated from the main branch.
git switch myBranch
git reset --soft $(git merge-base <base> HEAD) # replace <base> with your base branch (main or develop)
git commit -m "feat: my awesome new feature" # a single commit
git push --force # force push
So the soft reset undoes all of your commits, it keeps all of your changes though in the staging area.
You can do git status
to verify that all of your changes are there. Now you’re ready to commit them into a single commit. The forced push is necessary as we are rewriting the repositories commit history.
Another way is to interactively rebase your commits. Let’s say your branch hs 5 commits that you want to squash, you do it like this:
git switch myBranch
git rebase -i HEAD~5
git push --force
A dialog will open, where you need to decide what to do with each commit. You basically need to pick the first of them, and fixup all the others. Optionally, you can comment out commits that you wish to remove. If you want to edit the commit message of the commit you picked, you should choose reword instead of pick
Finally you need to force push the changes, as you are rewriting the history of your repository.
That said, there’s a simple way to protect you from loosing parts of your work by accident: the Local History of your IDE. PhpStorm and Eclipse have a Local History built-in. For Visual Studio Code, there’s an Extension that does the job.
Merge (or rebase) changes from the base branch into your feature branch frequently
This is a step, that you always want to do after you completed your work and before you create a pull request.
Nonetheless, it doesn’t hurt to do it more frequently. If you make it a habit, to sync the changes your teammates made to the base branch into your own feature branch, you reduce the risk of getting large conflicts with many conflicting files.
Depending on your preferred strategy, you do this either with:
git checkout myBranch
git merge <base> # replace <base> (usually something like develop or main)
or with:
git checkout myBranch
git rebase <base> # replace <base> with your actual base branch (develop or main)
alternatively, you could also:
git checkout myBranch
git pull origin <main/master/develop>
Smaller branch scope
For the sake of completeness: another reason you might run into merge conflicts could be, that the scope of your branch might be too wide. Sometimes tasks are big, but if you can split them into smaller chunks of work, you could reduce the risk of future merge conflicts, as well.
Conclusion
Applying these strategies won’t eliminate git conflicts completely, there will always be scenarios when Git cannot decide on its own how to merge current and incoming changes in the same lines of code. Having said that, with these strategies in place, conflicts will be easier to resolve.