Git rebase conflicts
פורסם בתאריך 22 באפריל 2025
תוכן זה אינו זמין עדיין בשפה שלך.
I see many developers struggle with using git rebase
and I think the main reason of the struggle is
caused by the conflicts that arise when rebasing.
So right from the start let’s clarify something:
Does git rebase
has more conflicts than merge
?:
The answer is if you know what you are doing than usually no - the number of conflicts will be the same.
The know what you are doing is often the problem.
If you are working in a branch and your commits are messy (We will explain what messy means) then you will have tons of conflicts when working with rebase
.
In this article we will try and understand how git rebase
works, and why we have so many conflicts when rebasing.
Rebase and Merge
Let’s start by understanding what git rebase
and git merge
are doing.
Our starting point is that we started from a branch called main
.
We wanted to work on a new feature, so we created a new branch called feature
.
We created a few commits on the feature
branch and while we were working on the branch, the main
branch was updated with new commits.
we are on the feaure
branch and we want to get the new commits from main
into our feature
branch we can do one of two things:
- We can use
git merge
to merge themain
branch into ourfeature
branch.
git merge main
This assumes we are on the feature
branch.
- We can use
git rebase
to rebase ourfeature
branch on top of themain
branch.
git rebase main
Let’s try and understand what each of these commands does.
Git Merge
When we run git merge main
we are telling git to merge the main
branch into our current branch.
Git will create a new commit that combines the changes from both branches.
This new commit will have two parents: the last commit on the main
branch and the last commit on the feature
branch.
Notice that a new commit was added on the feature
branch.
Conflicts with Merge
Let’s examine the image above and imagine that on the feature
branch in commit 1
we installed some package using npm install <some-package1>
.
Then in the same branch in commit 3
we installed another package using npm install <some-package2>
.
So in commit 1
and commit 3
we have changes in the package.json
file.
Now let’s imagine in the main
branch in commit C
another developer installed a package using npm install <some-package3>
.
So in commit C
we also have changes in the package.json
file.
Now when we try to merge the main
branch into our feature
branch, there is a good chance that we will have a conflict in the package.json
file.
Let’s define 2 concepts that relate to conflicts and their value when doing a merge:
ours
- the changes that we made in our branch (thefeature
branch).theirs
- the changes that were made in the other branch (themain
branch).
In the following conflict we will have a conflict on the package.json
file, where ours
is the aggregated change in package.json
from commit 1
and commit 3
and theirs
is the change in package.json
from commit C
.
So we will have one conflict to solve on the package.json
file.
Git Rebase
When we run git rebase main
we are telling git to take all the commits from the feature
branch and replay them on top of the main
branch.
Git will take each commit from the feature
branch and apply it on top of the main
branch.
This means that the commits from the feature
branch will be added to the main
branch one by one.
Conflicts with Rebase
Same conflict scenario as before. we installed some package using npm install <some-package1>
in commit 1
and in commit 3
we installed another package using npm install <some-package2>
.
In the main
branch in commit C
another developer installed a package using npm install <some-package3>
.
Let’s define 2 concepts that relate to conflicts and their value when doing a rebase - Notice that the ours
and theirs
are different than the merge:
ours
- represet the branch that we rebase on top of (themain
branch).theirs
- represent the commit that we are trying to rebase (thefeature
branch).
Now here is the catch:
When we rebase we might have conflicts on every commit that we are trying to rebase.
So in our case we will have a conflict on commit 1
that introduced a change in package.json
that we will have to solve before the rebase can continue.
Then we will have to solve another conflict on package.json
in commit 3
.
This means that in our Rebase example we had to solve 2 conflicts on the package.json
file, while in the Merge example we only had to solve one conflict on the package.json
file.
It seems from this example that git rebase
has more conflicts than git merge
.
OR IS IT?
git rebase interactive mode
Rebase interactive mode gives you a chance to edit the commits that you are rebasing. You can change the commit message, squash commits, drop commits, reorder commits, and more. There are times when we code something commit and then after few commits in the future we realize that a commit we made had issues and needs a bit of change. We can fix the issue and create a new commit but that’s not always the best solution.
Let’s try to explain the problem with an example.
Let’s say you have a task where you need to install a package that will help you with PDF files, and using that package you will have
to read the pdf, and search for a specific string.
So here is the workflow of the task:
- You find a popular package
pdf-reader
and you install it. You create your first commitchore: install pdf-reader
. - You implement reading the pdf and create another commit
feat: read pdf
.
You find out that the package you chose cannot be used for searching for a string in the pdf.
You decide to go with a different package pdf-lib
that can do the job.
- you create a new commit
chore: install pdf-lib
. - You implement reading the pdf and create a commit
- You implement searching for a string in the pdf and create another commit.
Then you realize that the package you chose are not maintained and has a lot of issues.
The you decide to go with a 3rd package awesome-pdf
which you manage to create all the features you need.
So a commit history in your branch might look like this:
That trial and error is not necessarily something you want to keep in your commit history, and at times can be sufficent just placing a trial you did in a commit body instead of creating a full commit. For example that same commit history can be arranged differently using git rebase interactive mode:
I would argue that the second commit history is much cleaner and easier to understand. And what about conflicts?
If we take the previous example that we had a conflict on the package.json
, in the example where we don’t arrange and squash our commits properly
we might have conflicts like the number of commits that changed the package.json
file.
Which means in this example we will have 3 conflicts, on the other hand after arranging and squashing our commits we will have only one conflict on the package.json
file because only one commit is changing that file.
And to give a few examples how to arrange and maintain a clean commit history, we highly recommend to read a tutorial we wrote on the topic: Clean your commits
Convlicts Rebase vs Merge
So actually if we make sure to arrange our commits properly and squash them when needed, then most likely the number of conflicts we will have when rebasing will be the same as the number of conflicts we will have when merging. The only difference might be if there is changes in the same file that the developer wants to emphasize the changes in different commits. Changes that are not related to each other and are more suited to a different commit then a remark on the commit body. Have to say that those are relatively rare, especially if you arrange you work properly to PR’s with single responsibility and not a single PR that does everything. But there are cases when developer choose to split a change on a single file into multiple commits, in that case yes there will be more conflicts, but those changes are aimed for different pr’s it makes sense that those changes will be reflected multiple times if that was the choice of the developer. On the merge side you will suddently have a commit with solved conflicts that reflect different commits from the past, very confusing in terms of looking back at the commit history.
So bottom line if you arrange your PR properly 99% of the time - same conflicts, the 1% is your choice but it’s actually better in terms of commit history.
What about pull?
To understand how pull affects in terms of conflicts, rebase, and merge, let’s think about the following scenario:
I’m on a branch called feature
I create 3 commits on that branch.
commit 1
and commit 3
are changing the same file package.json
.
On the main
branch a developer pushed a commit with changes to the package.json
file.
This image describe the scenario, we want to examine what happens on different pull commands.
git pull
git pull
with no additional argument will pull from the upstread branch (provided there is one) and merge the changes into the current branch.
Meaning if we are on the feature
branch and we run git pull
it will git fetch
to get the latest changes to the local repository and then it will merge the changes into the feature
branch from the upstream of the feature
branch (usually origin/feature
).
so provided that your remote name is origin
and the upstream branch is origin/feature
, the command will be equivalent to:
git fetch origingit merge origin/feature
and a more explicit command would be:
git pull origin feature
which in this case will be equivalent.
git pull origin main
If I’m on the branch feature
and I run git pull origin main
, it will fetch the changes from the main
branch and merge them into the feature
branch.
This is equivalent to running:
git fetch origin maingit merge origin/main
This means that the changes from the main
branch will be merged into the feature
branch.
Merge meaning another commit will be created on the feature
branch that will combine the changes from both branches.
Notice that the main
branch is updated in our local repository but not in the working directory, so if we transition to main we won’t see the changes.
We will have to run git pull
to get the changes from the main
branch into our local repository.
git pull —rebase
You can also run git pull
with the --rebase
option.
So if we are on the feature
branch and we run:
git pull --rebase origin main
This will fetch the changes from the upstream branch and rebase the current branch on top of the upstream branch. This is equivalent to running:
git fetch origin maingit rebase origin/main # we are currently on the feature branch
Notice that the working directory is not updated with the changes from the main
branch.
This means the if we transition to the main
branch we won’t see the changes until we run git pull
to get the changes from the main
local repository to the working directory.
without the --rebase
option the default behavior of git pull
is to merge the changes from the upstream branch into the current branch.
Since I prefer to use rebase
instead of merge
I can set the default behavior of git pull
to rebase
by running:
git config pull.rebase true
In general I do find it clearer if the working directory is updated with the changes from the upstream branch and local repository, so personally I prefer not to do a git pull origin main
directly to my feature
branch.
I prefer to do a git pull origin main
to the main
branch and then do a git rebase main
to my feature
branch.
Some developers might look at it as a bit more work, but I find it clearer and easier to understand.
Conclusion
If your PR branch is organized properly and you are using git rebase
instead of git merge
then the number of conflicts you will have when rebasing will be the same as the number of conflicts you will have when merging.
With Rebase there are certain changes in the same file that you choose not to squash to emphasize some change, on those not that common occasions that specific file can have more than
one conflict, but it does make the history cleaner and easier to understand and those changes you want to emphasize are more understandable
if the commit is solved on that specific commit and not one time on the merged version of the change.