Skip to content

Conflicts and rebase ​

Sometimes Git cannot combine two branches by itself and asks you to decide. This lesson shows why that happens, how to resolve it calmly, and how to use rebase to keep a history tidy without losing anyone's work.

Why conflicts happen ​

When you merge, Git looks at three versions of each file:

  • the base: the file at the last commit both branches share, where they split;
  • ours: the file on the branch you are on;
  • theirs: the file on the branch you are merging in.

This is a three-way merge. For each block of lines, Git compares both sides with the base:

BaseOursTheirsGit's result
SnacksSnacksTrail mix and fruitTrail mix and fruit: only theirs changed
BookKindleBookKindle: only ours changed
SnacksSnacks for the trainTrail mix and fruitConflict: both changed the same line differently

Most merges have no conflicts, because people usually change different parts of a project. A conflict appears only when both sides changed the same lines (or lines right next to each other), or when one side deleted a file the other edited. Git refuses to guess which version you want.

What a conflict looks like ​

On main you changed Snacks to Snacks for the train. On the weekend branch someone changed the same line to Trail mix and fruit. You merge:

bash
git merge weekend
text
Auto-merging packing.txt
CONFLICT (content): Merge conflict in packing.txt
Automatic merge failed; fix conflicts and then commit the result.

The merge is now paused, half done. git status says what is going on:

text
On branch main
You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

Changes to be committed:
	new file:   days.md

Unmerged paths:
  (use "git add <file>..." to mark resolution)
	both modified:   packing.txt

days.md merged cleanly and is already staged. packing.txt needs you. Git wrote both versions into the file, between conflict markers:

text
Water bottle
<<<<<<< HEAD
Snacks for the train
=======
Trail mix and fruit
>>>>>>> weekend
Book
MarkerMeaning
<<<<<<< HEADStart of your side (the branch you are on)
=======The divider
>>>>>>> weekendEnd of their side (the branch being merged in)

Resolve it ​

You have three ways out.

1. Edit the file by hand ​

Open the file, decide what the result should be, and delete all three marker lines. Here you want both:

text
Water bottle
Snacks for the train
Trail mix and fruit
Book

Then tell Git the file is resolved by staging it, and finish the merge:

bash
git add packing.txt
git commit
text
[main 37dcd6b] Merge branch 'weekend'

git commit opens your editor with the merge message ready; save and close.

Check for leftover markers

Git does not stop you committing a file that still contains <<<<<<< lines. Before git commit, run git diff --staged --check. It prints a leftover conflict marker line for each one it finds, and prints nothing when the file is clean.

2. Take one side for the whole file ​

If one version is right as it is, take it for the whole file:

bash
git restore --ours packing.txt     # keep the version on your branch
git restore --theirs packing.txt   # keep the incoming version
git add packing.txt

(git checkout --ours and --theirs do the same.) This drops every change the other side made to that file, not only the conflicting lines.

3. Back out ​

If you are not ready to decide, put everything back as it was before the merge:

bash
git merge --abort

Nothing is lost: both branches are exactly as they were.

Resolve conflicts in BranchPilot ​

In BranchPilot, a Merge in progress banner appears and the file is listed under Conflicted with three buttons: Take ours, Take theirs and Mark resolved. For a mix of both, edit the file in your editor, remove the markers, save, and select Mark resolved. When the Conflicted list is empty, select Continue to make the merge commit, or Abort to back out.

Resolve conflicts in DeltaView ​

A three-way merge tool shows the three versions side by side, so you can see who changed what. Delta View (the app calls itself DeltaView) can be Git's merge tool. Run these once:

bash
git config --global merge.tool deltaview
git config --global mergetool.deltaview.cmd \
  'delta-view --merge "$LOCAL" "$BASE" "$REMOTE" --output "$MERGED"'
git config --global mergetool.deltaview.trustExitCode false
git config --global mergetool.keepBackup false

When a merge stops on a conflict, run:

bash
git mergetool
text
Merging:
packing.txt

Normal merge conflict for 'packing.txt':
  {local}: modified file
  {remote}: modified file

DeltaView opens with your side in LEFT, theirs in RIGHT and the result in MERGED. Lines only one side changed are already merged. For each conflict, choose Left, Right, Both or Edit, then press Ctrl + S and close the window. Git stages the file and moves to the next one. When all are done, run git commit. The Windows setup and the meaning of each setting are in Use DeltaView with Git.

Rebase: another way to combine work ​

A merge joins two lines of work with a merge commit. A rebase instead takes your branch's commits and replays them, one at a time, on top of the other branch, as if you had started your work later.

text
Before: main and hats have diverged
          main
           ▼
… ◄── B ◄── M
       ▲
       └── H          ◄── hats

git merge hats (on main)            git rebase main (on hats)
                main                             main
                 ▼                                ▼
… ◄── B ◄── M ◄── X                 … ◄── B ◄── M ◄── H'
       ▲          │                                    ▲
       └── H ◄────┘                                   hats
       merge commit X has two parents      H' is a new copy of H
bash
git switch hats
git rebase main
text
Successfully rebased and updated refs/heads/hats.

The result is a straight line, as if the work had been done in order. H' has the same changes as H but a different parent, so it is a new commit with a new hash. The old H is no longer on any branch.

MergeRebase
HistoryShows exactly what happened, with joinsA straight line, quicker to read
Existing commitsUntouchedReplaced by new copies
Safe on shared branchesYesNo, unless everyone agrees
ConflictsResolved once, in the mergeResolved per commit, as each is replayed

The golden rule of rebasing

Only rebase commits that exist only on your computer, or on a branch nobody else uses. If others already have the old commits, rebasing gives them a history that no longer matches theirs, and their next pull becomes a tangle.

Conflicts during a rebase ​

A rebase can stop on a conflict too. You resolve it the same way, then continue instead of committing:

text
CONFLICT (content): Merge conflict in packing.txt
error: could not apply 9c4a4c5... Pack a sun hat instead of a book
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
bash
git add packing.txt
git rebase --continue

Ours and theirs swap during a rebase

In a merge, "ours" is your branch. During a rebase, Git is replaying your commits onto the other branch, so "ours" is the branch you are rebasing onto and "theirs" is your commit being replayed. Read the file before you pick a side.

Interactive rebase: tidy before you share ​

An interactive rebase lets you edit the list of commits before Git replays them. Use it to clean up work in progress before others see it: fold "fix typo" commits into the commit they fix, reword a message, reorder or drop commits.

You have three commits on weekend, and the second only fixes a typo in the first:

bash
git rebase -i main

Git opens your editor with one line per commit, oldest at the top:

text
pick 91bb216 # Add day plan
pick 706fbcf # Fix typo in day plan
pick c4d4033 # Pack hiking snacks

# Rebase eb4dd84..c4d4033 onto eb4dd84 (3 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
#                    commit's log message, …
# d, drop <commit> = remove commit
…

Change the word at the start of a line to choose what happens to that commit:

ActionWhat happens
pickKeep the commit as it is
rewordKeep it, but edit its message
squashFold it into the commit above it in this list, and combine both messages
fixupFold it into the commit above it, and keep only that commit's message
dropRemove the commit and its changes

You can also reorder lines to reorder commits. Change the second line to fixup, save and close:

text
pick 91bb216 # Add day plan
fixup 706fbcf # Fix typo in day plan
pick c4d4033 # Pack hiking snacks
text
Successfully rebased and updated refs/heads/weekend.
bash
git log --oneline -3
text
c77da86 Pack hiking snacks
a105eae Add day plan
eb4dd84 Pack sunglasses

Two commits instead of three, and "Add day plan" now includes the fix. Both have new hashes. In BranchPilot, right-click the last commit you want to leave untouched and choose Interactive rebase onto this commit…, then pick an action for each row and drag rows to reorder. Note that BranchPilot lists the newest commit on top, the opposite of the terminal, so squash and fixup fold into the row below. See Interactive rebase.

Force push, carefully: --force-with-lease ​

If you rebase or amend commits you already pushed, your branch and the remote's no longer match, and a normal push is refused:

text
 ! [rejected]        rain-gear -> rain-gear (non-fast-forward)

To replace the remote branch with yours you must force the push. Plain git push --force overwrites whatever is there, including commits a teammate pushed a minute ago. Use this instead:

bash
git push --force-with-lease

With lease means: overwrite the remote branch only if it is still where my last fetch saw it. If someone pushed in the meantime, Git refuses:

text
 ! [rejected]        rain-gear -> rain-gear (stale info)
error: failed to push some refs to '…/trip-planner.git'

When it is safe, it goes through:

text
 + 2a846e8...7af6671 rain-gear -> rain-gear (forced update)

A fetch renews the lease

The lease compares with your last fetch. If you run git fetch and then force push without looking, the check passes and your teammate's new commit is overwritten anyway. After a fetch, look at git log --oneline --graph --all and bring in any new commits before you force push.

In BranchPilot, the small arrow next to the push button has Force push (with lease)…, which also asks you to type the branch name. See Force push.

Check yourself ​

QuizConflicts and rebase

Question 1 of 6

When does a merge conflict happen?

Score: 0

What you learned ​

  • Git merges by comparing both sides with their common base; a conflict means both changed the same lines.
  • Conflict markers show your side between <<<<<<< and =======, and theirs between ======= and >>>>>>>.
  • Resolve by editing and git add, by taking one side, or back out with git merge --abort.
  • BranchPilot and DeltaView show conflicts visually; git mergetool opens DeltaView for each conflicted file.
  • Rebase replays commits as new copies for a straight history; never rebase commits others already have.
  • Interactive rebase picks, rewords, squashes, fixes up or drops commits before you share them.
  • After rewriting pushed commits, use --force-with-lease, and look before you push.

Next steps ​