Merging and merge conflicts

What happens when two branches meet, and what to do when Git cannot decide for you

A merge is how work comes back together. Most merges are boring: Git works out the combined result on its own and you never notice. A conflict is the small minority where it cannot, and asks you.

This page is about both. As on the previous page, every step is shown first in Visual Studio Code / Positron and then on the command line.

Start with Git branching: an introduction if you have not read it.

1 First, three commands that get confused

Almost every merge surprise comes from not knowing which of these you just ran.

fetch
Download the commits from GitHub, but change nothing in your files. After a fetch you can look at what other people did before deciding anything.
pull
fetch plus merge — download and fold those commits into your current branch, in one step. This is why a pull can suddenly ask you to resolve a conflict: the merge half is doing it.
push
Send your local commits up, once your history contains everything the remote has.
Sequence diagram with two vertical lifelines: the local repository on the left and the remote repository on the right. A yellow box on the remote side reads remote commits from others. A first arrow points from the remote to the local side, labelled fetch, download commits without merging. A second arrow points the same way, labelled pull, fetch plus merge, and a yellow box on the local side reads remote commits merged locally. A third arrow points from the local side up to the remote, labelled push, upload local commits, and a yellow box on the remote side reads local commits available to others.
Figure 1: fetch downloads, pull downloads and merges locally, push uploads. Source: VS Code — Source control overview.

Sync Changes is pull then push in one click, and it is what you will use most of the time. When you want them separately — or want to fetch and look first — use the … menu at the top of the Source Control view.

Screenshot of the VS Code Source Control view with the ellipsis menu open, showing a list of Git commands including Pull, Push, Fetch, Commit, Changes, Branch, Remote and Stash.
Figure 2: The … menu gives you Pull, Push and Fetch individually, when Sync Changes is too blunt. Source: VS Code — Source control quickstart.
git fetch          # look, don't touch
git pull           # fetch + merge into the current branch
git push           # send yours up

2 The story: two people, one file

The clearest way to understand a conflict is to watch one form. Two colleagues, Dev A and Dev B, both start from the same version of a shared branch.

Diagram with three horizontal rows, labelled from top to bottom Base Branch in yellow, Dev A in pale green and Dev B in bright green. Each row has a single circle, and the three circles are stacked in one vertical column joined by a connector. A small tag above the top circle reads v1.0. All three branches start from the same commit.
Figure 3: Both start from the same place. The base branch is at v1.0, and each person creates their own branch from that exact commit. Source: Camping Coder — Git merges demystified.
The same three-row diagram. The Dev A row, in pale green, has gained a second circle to the right of the first, joined by an arrow, showing one new commit on Dev A's branch. The Base Branch and Dev B rows are unchanged.
Figure 4: Dev A commits her work on her own branch. Nothing else has moved. Source: Camping Coder — Git merges demystified.
The same three-row diagram. Now both the Dev A row and the Dev B row have gained a second circle joined by an arrow, showing that each developer has committed independently on their own branch. The Base Branch row still has a single commit tagged v1.0.
Figure 5: Dev B commits his, at the same time, on his own branch. Neither of them is blocked by the other. Source: Camping Coder — Git merges demystified.
The same diagram, now with an arrow running from Dev A's second commit up to a new circle on the Base Branch row, tagged v1.1. The base branch has advanced by one commit containing Dev A's work. Dev B's branch is unchanged and still points at the older base commit.
Figure 6: Dev A merges first. Her branch goes back into the base branch cleanly, and the base branch moves on to v1.1. Source: Camping Coder — Git merges demystified.

Here is the moment everything turns on. Dev B’s branch was created from v1.0, but the base branch is now at v1.1. His branch is behind, and if he touched the same lines Dev A did, Git cannot merge it on its own.

The same diagram with a new circle at the far right of the Base Branch row drawn as a red cross and tagged Error in red. A pale arrow runs from Dev B's commit up to that error, showing that his attempt to merge into the base branch failed.
Figure 7: Dev B tries to merge and is refused. His branch is out of date, and Git will not guess whose version of the shared lines should win. Source: Camping Coder — Git merges demystified.

The fix is not to argue about it. It is for Dev B to bring the base branch into his own branch first, and sort out the overlap there — on his branch, where nothing is at stake.

The same diagram with a new arrow running down from the base branch commit tagged v1.1 into a new circle on Dev B's row, labelled Merge Commit. Dev B has pulled the updated base branch into his own branch, producing a merge commit that combines both lines of work.
Figure 8: Dev B pulls the updated base branch into his branch and resolves the overlap locally. The result is a merge commit on his branch. Source: Camping Coder — Git merges demystified.
The final diagram. An arrow runs from Dev B's merge commit up to a new circle at the far right of the Base Branch row, tagged v1.2. Both Dev A's and Dev B's work are now in the base branch, and the earlier red error node is bypassed.
Figure 9: Now the merge succeeds. Dev B’s branch already contains Dev A’s work, so it goes back into the base branch cleanly, and the base branch reaches v1.2. Source: Camping Coder — Git merges demystified.
NoteThe takeaway from those seven pictures

Nobody did anything wrong. Dev B’s branch simply aged while he was working on it.

The rule that follows is short: before you ask anyone to merge your branch, pull the base branch into it and deal with any conflict there.

3 Bringing main into your branch

This is Figure 8, in practice.

  1. Click the branch name in the status bar and switch to main.
  2. Click Sync Changes to pull down what has been merged.
  3. Switch back to your branch.
  4. Open the … menu → Branch → Merge… and choose main.

VS Code shows how far behind you are before you start: the incoming and outgoing changes section lists the commits that are about to arrive.

Screenshot of the VS Code Source Control view for a repository named demo-travel-blog on branch main. Next to the branch name a sync icon shows 3 down-arrow and 1 up-arrow, and a blue button reads Sync Changes 3 down-arrow 1 up-arrow, indicating three commits to pull and one to push.
Figure 10: The Source Control view tells you how many commits are incoming and outgoing before you sync. Source: VS Code — Source control overview.
git switch main
git pull
git switch add-region-dummies
git merge main            # bring main's new commits into your branch

If nothing overlaps, that is the end of it. If something does, Git stops and asks — read on.

4 Resolving a conflict

A conflict means two people changed the same lines of the same file, and Git will not pick a winner. It marks the disputed region and hands it to you.

TipA conflict is not an accusation

It does not mean anyone broke anything. It means two edits landed on the same spot, and a human has to say what the combined version should be.

Often the right answer is keep both.

Conflicting files appear in the Source Control view under Merge Changes. Open one, and each disputed block gets a row of actions above it.

Screenshot of a VS Code editor showing a merge conflict in a JavaScript file. A row of blue link actions reads Accept Current Change, Accept Incoming Change, Accept Both Changes, Compare Changes. Below it the current change is highlighted in green between markers reading less-than signs HEAD Current Change, and the incoming change is highlighted in blue above a marker reading greater-than signs theirs Incoming Change. A blue button at the bottom right reads Resolve in Merge Editor.
Figure 11: Inline actions above each conflict: Accept Current Change keeps your version, Accept Incoming Change keeps theirs, Accept Both Changes keeps both. Source: VS Code — Resolve merge conflicts.

For anything more than a trivial clash, click Resolve in Merge Editor. It shows three panes: Incoming on the left, Current on the right, and the Result you are building at the bottom.

  1. Tick the changes you want from either side, or Accept Combination to take both.
  2. Edit the Result pane directly if neither side is quite right.
  3. Click Complete Merge when the counter says no conflicts remain.
  4. Commit as usual.
Screenshot of the VS Code 3-way merge editor. The top half is split into two panes: Incoming on the left and Current on the right, each showing a different version of the same JavaScript function with checkbox actions reading Accept Incoming, Accept Combination and Ignore. The bottom pane, labelled Result, shows the merged file being assembled, with a note reading No Changes Accepted, a counter reading 1 Conflict Remaining, and a blue Complete Merge button.
Figure 12: The 3-way merge editor: incoming on the left, your current version on the right, the result you are building at the bottom. Source: VS Code — Resolve merge conflicts.

Git rewrites the disputed region of the file with markers:

<<<<<<< HEAD
global root "C:/Users/aigerim/hh-survey"
=======
global root "/home/daniyar/projects/hh-survey"
>>>>>>> main
  • Everything between <<<<<<< and ======= is your version.
  • Everything between ======= and >>>>>>> is theirs.

Open the file, decide what the correct combined content is, delete all three marker lines, then:

git add clean_analyze.do
git commit                # records the resolved merge
git push

git status lists every file still waiting on you. git merge --abort throws the whole merge away and puts you back where you started, if you would rather think about it first.

WarningResolve by understanding both sides, not by picking one

The temptation is to accept whichever version makes the red markers go away. That silently deletes a colleague’s work.

Read both versions. Write the version that is actually correct — which is often neither one on its own.

5 Where to resolve it matters

You can resolve the same conflict in three places, and they are not equally good.

Where What it costs
On your branch, before opening the PR Cheapest. Nothing shared is affected, and you can take your time. Do this.
In the pull request, when GitHub flags it Fine. It is visible and reviewable, which is the whole point of a PR.
On main, after a rejected push Worst. One person, under time pressure, with no review.

That last row is the rejected-push story on the branching page, and it is exactly what branches exist to avoid.

6 Habits that prevent most conflicts

Conflicts are mostly a symptom of how work is organised, not of Git.

  • Pull from the base branch often. A branch that is one day behind conflicts rarely; one that is two months behind conflicts everywhere.
  • Keep branches short-lived. Days, not months.
  • Split large files. One 3,000-line sim.do that everybody edits will conflict constantly. Several focused do-files will not.
  • Commit only what you meant to change. Editor settings, log files and re-saved output create conflicts that carry no information at all. Keep them out with .gitignore.
  • Divide work by section, not by line. If two people must touch the same block, agree who goes first rather than racing.

7 Exercise: cause a conflict on purpose

Ten minutes, in pairs, on a throwaway repository. The point is to meet a conflict when nothing is at stake.

  1. Both clone the same repository and confirm you are on the same commit.
  2. Each create a branch: <yourname>-conflict.
  3. Both edit the same line of README.md, differently.
  4. Both commit and publish.
  5. Person A opens a pull request and merges it.
  6. Person B pulls main into their branch — the conflict appears.
  7. Person B resolves it in the merge editor, keeping both contributions, commits, and pushes.
  8. Person B’s pull request now merges cleanly.
  9. Both pull main and confirm the two changes coexist.

Then do it again and let Git surprise you: GitHub Skills — Resolve merge conflicts runs the same drill inside a real repository, with instructions built in.

8 Sources and further reading

Where the pictures come from

Read more on merging

Elsewhere in these guides