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:
| Base | Ours | Theirs | Git's result |
|---|---|---|---|
Snacks | Snacks | Trail mix and fruit | Trail mix and fruit: only theirs changed |
Book | Kindle | Book | Kindle: only ours changed |
Snacks | Snacks for the train | Trail mix and fruit | Conflict: 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:
git merge weekendAuto-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:
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.txtdays.md merged cleanly and is already staged. packing.txt needs you. Git wrote both versions into the file, between conflict markers:
Water bottle
<<<<<<< HEAD
Snacks for the train
=======
Trail mix and fruit
>>>>>>> weekend
Book| Marker | Meaning |
|---|---|
<<<<<<< HEAD | Start of your side (the branch you are on) |
======= | The divider |
>>>>>>> weekend | End 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:
Water bottle
Snacks for the train
Trail mix and fruit
BookThen tell Git the file is resolved by staging it, and finish the merge:
git add packing.txt
git commit[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:
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:
git merge --abortNothing 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:
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 falseWhen a merge stops on a conflict, run:
git mergetoolMerging:
packing.txt
Normal merge conflict for 'packing.txt':
{local}: modified file
{remote}: modified fileDeltaView 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.
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 Hgit switch hats
git rebase mainSuccessfully 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.
| Merge | Rebase | |
|---|---|---|
| History | Shows exactly what happened, with joins | A straight line, quicker to read |
| Existing commits | Untouched | Replaced by new copies |
| Safe on shared branches | Yes | No, unless everyone agrees |
| Conflicts | Resolved once, in the merge | Resolved 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:
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".git add packing.txt
git rebase --continueOurs 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:
git rebase -i mainGit opens your editor with one line per commit, oldest at the top:
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:
| Action | What happens |
|---|---|
pick | Keep the commit as it is |
reword | Keep it, but edit its message |
squash | Fold it into the commit above it in this list, and combine both messages |
fixup | Fold it into the commit above it, and keep only that commit's message |
drop | Remove the commit and its changes |
You can also reorder lines to reorder commits. Change the second line to fixup, save and close:
pick 91bb216 # Add day plan
fixup 706fbcf # Fix typo in day plan
pick c4d4033 # Pack hiking snacksSuccessfully rebased and updated refs/heads/weekend.git log --oneline -3c77da86 Pack hiking snacks
a105eae Add day plan
eb4dd84 Pack sunglassesTwo 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:
! [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:
git push --force-with-leaseWith 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:
! [rejected] rain-gear -> rain-gear (stale info)
error: failed to push some refs to '…/trip-planner.git'When it is safe, it goes through:
+ 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
Question 1 of 6
When does a merge conflict happen?
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 withgit merge --abort. - BranchPilot and DeltaView show conflicts visually;
git mergetoolopens 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
- Undo and stay safe
- Resolve conflicts in BranchPilot
- Merge three versions of a file in DeltaView