Part 7 of 11 · 3 min read
Merge conflicts
What a conflict is, how to resolve one calmly, and when to merge or rebase.
Two people changing different parts of a file is easy: Git combines the changes by itself. A conflict happens only when two branches change the same lines differently, so Git cannot know which version you want. It stops and asks you.
Merge conflict
A situation where Git cannot combine two changes automatically because both edited the same lines. Git marks the file and waits for a person to choose the final text. Nothing is broken, and no work is lost.
Here is one made on purpose. On main, someone lowered a timeout to 5. On a branch feature/retry, someone else raised it to 30. Merging the branch:
$ git merge feature/retry
Auto-merging settings.txt
CONFLICT (content): Merge conflict in settings.txt
Automatic merge failed; fix conflicts and then commit the result.
git status tells you where you are and what to do next:
$ git status
On branch main
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: settings.txt
Open the file. Git has written both versions into it between three kinds of marker lines:
<<<<<<< HEAD
timeout = 5
=======
timeout = 30
>>>>>>> feature/retry
retries = 3
Everything between <<<<<<< HEAD and ======= is your side (the branch you are on). Everything between ======= and >>>>>>> feature/retry is their side. The line retries = 3 is outside the markers, so it merged cleanly.
Resolve it in four steps
- Open each file that
git statuslists under "both modified". - Decide the correct final text. It may be your side, their side or a mix. Edit the file so that it contains only that text, and delete all three marker lines. Here we choose
timeout = 15. - Run the tests or the program. A conflict can be resolved wrongly and still look tidy.
- Tell Git it is resolved:
git add settings.txt, thengit commit(for a merge).
$ git add settings.txt
$ git commit -m "Merge feature/retry, use 15s"
$ git log --oneline --graph --all
* 9506b91 Merge feature/retry, use 15s
|\
| * d53364c Raise timeout for slow providers
* | bd882c7 Lower timeout for faster failure
|/
* 220de1b Add settings
The graph shows the two lines of work splitting apart and joining again at the merge commit.
If you get lost half-way, git merge --abort puts everything back exactly as it was before the merge. You can always try again.
Many editors help
VS Code shows "Accept Current Change", "Accept Incoming Change" and "Accept Both" buttons above each conflict. They only edit the text in the file. You still need to run the tests and run git add and git commit.
Merge or rebase?
Both bring one branch's work into another. They differ in what the history looks like afterwards.
Merge and rebase
Merge joins two histories with a new commit that has two parents. The history shows what really happened, including the moment the lines of work split. Rebase takes your commits and replays them, one at a time, on top of another branch, giving a straight line. The history is tidier, but the replayed commits are new copies with new IDs.
A rule of thumb that prevents most trouble: rebase your own branch that nobody else has pulled; never rebase a branch other people are using. Rewriting commits others already have forces them to untangle two versions of the same history.
For the curious Going deeper: why rebase makes new commits
A commit's ID is calculated from its content, its message, its author and the ID of its parent (see Part 2). Replaying a commit onto a different parent changes the parent, so the ID changes. The old commits still exist for a while, and that is why git reflog (Part 8) can recover them. If you rebase a branch you already pushed, the remote and your computer now disagree about its history, and you need git push --force-with-lease to overwrite the remote safely. That command refuses to overwrite if someone else has pushed in the meantime.