In honor of spooky season, I’ve decided to write about a few scary topics in version control. Anyone familiar with Git probably knows the dread of the six-letter word: rebase. It’s not often that one word can bring so much fear and dread into the hearts and minds of Git users. The runner-up? Merge conflict. Yikes!
Since Git rebasing and merge conflicts are basically Git’s version of Disney villains, I thought I’d demystify these concepts and share some tips and tricks for dealing with them when they inevitably show up.
What Is Rebasing?
Rebasing is a Git operation that replays commits from one branch on top of another, creating a more linear commit history. Unlike merging, which can preserve the history of how branches diverged and came back together, rebasing creates new commits with new commit hashes.
You might first encounter rebasing when installing Git through a command-line or graphical installer. During setup, Git may ask how you want to handle merging: merge, rebase, or another option depending on the installer version.


Git provides helpful diagrams to illustrate the difference, but the concepts can still be confusing in practice. Technically, you could configure Git to rebase whenever you pull changes, but that doesn’t mean you should blindly rebase every branch in every situation. If you rewrite commits that other people are already working with, everyone will hate you. Nobody wants to be the reason their coworkers spend the afternoon untangling Git history!
If you’re new to Git, merging might seem like the safer option, and that’s a perfectly reasonable. But eventually, you’ll probably encounter a situation where rebasing is useful.
Normally, you can pull the latest changes from the remote repository, continue working, and push your changes when you’re ready. But sometimes your feature branch falls behind main (I promise it will happen), and you need to incorporate the latest changes before moving forward. This is where rebasing can help.
So, how do you rebase? Let’s walk through it.
How to Rebase a Branch?
Imagine you’re working on a feature branch called my-feature, while other contributors have continued updating main. You want to replay your feature commits on top of the latest version of main.
First, switch to your feature branch and fetch the latest remote changes:
# Switch onto your feature branch
git switch my-feature
# Fetch the latest changes from the remote
git fetch origin
Next, rebase your changes onto the latest main:
git rebase origin/main
Git will attempt to replay your feature commits on top of origin/main. If everything applies cleanly, you’re done! You can continue working with your updated branch.
However, if Git encounters conflicting changes, it will pause the rebase and ask you to resolve them.
What If a Conflict Occurs?
First, check which files have conflicts using git status
Open the affected files in your editor and look for Git’s conflict markers (see the below section on resolving merge conflicts). Once you’ve resolved the conflicts, stage the corrected files:
git add <resolved-file>
Then tell Git to continue replaying the commits:
git rebase --continue
You may need to repeat this process if additional conflicts arise while Git replays subsequent commits.
If you decide that you don’t want to continue with the rebase, you can cancel it:
git rebase --abort
This returns your branch to its state before the rebase began.
One important warning: if you’ve already pushed your feature branch and then rebase it, you may need to update the remote branch using a force push. Because rebasing creates new commit hashes, Git may not be able to push the rewritten history as a normal fast-forward update.
If you need to do this instead:
git push --force-with-lease origin my-feature
Use this carefully, especially if other people might be working on the same branch. The --force-with-lease option provides a safeguard against overwriting remote changes you haven’t accounted for, but it is not a substitute for coordinating with your team.
Git Merge vs. Git Rebase
So why would you choose one approach over the other?
Merging preserves the existing commits and records how branches were integrated. When a merge commit is created, it provides a record of that integration in the repository's history. In a shared repository with many contributors, however, the log can become crowded with merge commits that aren't easy to follow.
Rebasing creates a more linear history, which can make it easier to follow the changes. The trade-off is that rebasing rewrites commit history: the original commits may be replaced by new commits with different hashes. The original objects may be recoverable for a short time, but you shouldn't rely on this.
Here's a quick comparison:
| Git Merge | Git Rebase | |
|---|---|---|
| What does it do? | Integrates changes from one branch into another, potentially creating a merge commit. | Replays commits from one branch on top of another base. |
| When should you use it? | When you want to preserve the history of how branches came together, particularly in shared workflows. | When you want a linear history or need to update your own feature branch with the latest changes from main. |
| Pros | Preserves existing commits and makes branch integration visible in the history. | Creates a linear history and can make a feature branch's changes easier to follow. |
| Cons | Frequent merges can clutter the history with merge commits. | Creates new commit hashes and can complicate collaboration if you rewrite shared history. |
Neither approach is inherently better. The right choice depends on your team's workflow and how you want to maintain the repository's history.
When Should You Rebase?
With all this in mind, you might wonder why you'd ever want to rebase?
Imagine you're working on a new feature and have accumulated several intermediate commits. Meanwhile, other developers have been pushing changes to main. When you're ready to integrate your work, you realize your feature branch is behind.
Sound familiar? I told you it would happen.
Rebasing your feature branch onto the latest main lets you incorporate those changes while keeping your feature commits on top. It can also make your work easier to review by presenting a more linear sequence of commits.
However, rebasing doesn't magically prevent merge conflicts. If your changes overlap with changes made on main, Git may still need your help resolving them. The difference is that conflicts can occur while Git replays individual commits during a rebase, so you may need to resolve more than one.
And that brings us to the other Git villain: merge conflicts.
How to Resolve a Merge Conflict
Merge conflicts occur when Git cannot automatically reconcile changes between branches. This often happens when two branches modify the same part of a file, although conflicts can arise in other situations, too.
Fortunately, you don't need to panic. You can resolve conflicts in a code editor such as VS Code or IntelliJ, or use the command line. GitHub also provides tools for resolving certain conflicts directly in its interface.
When a conflict occurs, Git inserts markers into the affected file. A typical conflict looks something like this:
<<<<<<< HEAD
Your current version
=======
Incoming version
>>>>>>> feature-branch
Here's what the markers mean:
<<<<<<<marks the beginning of the first conflicting section.=======separates the two conflicting versions.>>>>>>>marks the end of the second section.
During a typical merge, HEAD refers to your current branch, and the incoming section represents the changes being merged into it. During a rebase, the labels can be less intuitive because Git is replaying commits onto a different base.
Your job is to decide how the final code should look. You might keep your changes, keep the incoming changes, combine both, or rewrite the section entirely. In some cases, you may need to resolve a conflict by deciding whether a file should be deleted or retained.
Once you've edited the file, remove the conflict markers and save the final version. Then stage the resolved file.
For a merge, you can complete the operation with:
git add <resolved-file>
git merge --continue
For a rebase, use:
git add <resolved-file>
git rebase --continue
If you're resolving conflicts through a graphical editor, make sure you understand which version you're accepting. Clicking a button labeled “Accept Current” or “Accept Incoming” without checking the code can accidentally discard changes you needed.
After the operation completes, review the resulting changes. Resolving a conflict only tells Git how to combine the code; it doesn't guarantee that the combined result works correctly.
For a more detailed walkthrough, check out GitHub's guide.
If you're really interested in diving into different types of git merging, checkout squash merges. Squash merges combine all commits from a feature branch into a single commit.
You're now better equipped to face two of Git's most intimidating concepts: rebasing and merge conflicts.
If there's one takeaway from this post, it's that understanding what Git is doing is far more useful than memorizing commands. Once you understand how branches and commits fit together, these once-scary operations become much easier to manage.
