Hello again. In the previous lesson, you created a focused feature branch, tested its work, and merged it into main cleanly. That successful merge was likely a fast-forward because only the feature branch had moved.
This time, you will deliberately create a merge conflict: both branches will change the same line of a project note in different, valid ways. You will then inspect the conflict, combine the intended changes, verify the result, stage it deliberately, and complete the merge. The essential goal is not to “make Git stop complaining”; it is to preserve the correct final behavior and history.
A merge conflict is Git asking for a decision
Git can merge most independent edits automatically. If one branch changes src/main.js and another changes src/style.css, Git generally combines both without intervention.
A conflict occurs when Git cannot safely infer the intended result—for example:
- both branches replace the same line differently;
- nearby edits overlap enough that their order is ambiguous;
- one branch deletes a file that the other branch modifies.
Git is conservative here. It pauses rather than silently discarding one developer’s valid work.
The merge uses three versions of a file:
| Version | Meaning during git merge feature-name while on main |
|---|---|
| Base | The common ancestor: what both branches started from |
Current / HEAD | The checked-out target branch, here main |
| Incoming | The source branch being merged, here feature-name |
| Result | The final file you consciously construct |
Conceptually, the history just before a conflict looks like this:
feature/clarify-loading-notes: B
/
A: shared base
\
main (HEAD): C
When you run:
git switch main
git merge feature/clarify-loading-notes
Git compares both and against their shared base . If both changed the same original line incompatibly, Git creates a conflict in that file and stops before making the merge commit.
This is a paused merge, not a damaged repository.
The EXTREMELY helpful guide to merge conflicts
Watch “The EXTREMELY helpful guide to merge conflicts” from the Visual Studio Code channel for a visual explanation of the shared base and the merge-editor workflow. The key idea is that a sound resolution considers the original version as well as both competing edits.
Watch the base to see why Git needs a shared predecessor. Then watch the merge editor, which labels the current, incoming, and result views. Finish with resolution choices and finalizing safely. Notice that “take current,” “take incoming,” and “take both” are aids—not substitutes for reviewing the resulting code or text.
Read the conflict markers precisely
When Git cannot merge a file automatically, it writes conflict markers directly into the working copy. Editors such as VS Code may provide buttons around them, but the markers are real text in the file.
Suppose you are on main and merge feature/clarify-loading-notes. You may see:
<<<<<<< HEAD
- Loading: validate API data and provide retry feedback through an accessible status message.
=======
- Loading: validate API data, provide retry feedback, and report the video count.
>>>>>>> feature/clarify-loading-notes
Read this from top to bottom:
<<<<<<< HEADbegins the current branch version—the content frommain, becausemainis currently checked out.=======separates the two proposals.- The lower section is the incoming branch version—the content from
feature/clarify-loading-notes. >>>>>>> feature/clarify-loading-notesends the conflict.
The correct resolution is often neither entire side by itself. Here, both changes represent useful application behavior: accessible loading feedback and a count summary. A careful final result is:
- Loading: validate API data, provide retry feedback through an accessible status message, and report the video count.
The markers must be removed completely. Leaving even one marker in JavaScript, HTML, CSS, or a Python file can break the application; leaving them in documentation still means the merge has not been meaningfully reviewed.
Resolving a merge conflict using the command line - GitHub Docs
Read GitHub Docs’ “Resolving a merge conflict using the command line” for the standard command-line sequence: identify the unmerged file, edit the intended final content, stage the resolution, and commit it.
In the “Competing line change merge conflicts” section, begin with the explanation that competing changes must be chosen and committed. Read the complete resolution flow. Pay particular attention to the meaning of the three marker lines and to the fact that git add marks a file as resolved; it does not decide the resolution for you.
Lab: create a controlled conflict, then preserve both changes
You will use a small Markdown note rather than production code to make the Git mechanics visible without risking your video-library interface. The reasoning process is exactly the same for a conflict in src/main.js, a Django view, or a Docker configuration.
1. Start clean and create a shared baseline
From the Vite project root, confirm the previous lesson left you on main with no unfinished work:
git switch main
git status
If git status is not clean, stop and understand those changes before proceeding. A merge may refuse to start if it would overwrite uncommitted work; that is a protective warning, but it is not a merge conflict.
Using the VS Code Explorer, create a docs directory if needed, then create:
docs/merge-conflict-lab.md
Put this content in it:
# Video library integration notes
- Loading: validate API data and provide retry feedback.
Commit this shared baseline on main:
git add docs/merge-conflict-lab.md
git diff --staged
git commit -m "Add video library integration notes"
Both upcoming changes will now begin from exactly the same line.
2. Make the feature branch’s valid change
Create and switch to a feature branch:
git switch -c feature/clarify-loading-notes
In docs/merge-conflict-lab.md, replace the bullet with:
- Loading: validate API data, provide retry feedback, and report the video count.
This represents the count summary added in the previous lesson. Review and commit it:
git diff
git add docs/merge-conflict-lab.md
git diff --staged
git commit -m "Document video count loading behavior"
At this point, the feature branch contains a valid change, but main does not.
3. Make a different valid change on main
Switch back to the target branch:
git switch main
Replace the same bullet in docs/merge-conflict-lab.md with:
- Loading: validate API data and provide retry feedback through an accessible status message.
This represents another legitimate concern: the loading and failure feedback should be communicated accessibly.
Commit it:
git add docs/merge-conflict-lab.md
git commit -m "Document accessible loading feedback"
Now inspect the divergent history:
git log --oneline --graph --decorate --all
You should see separate commits on main and feature/clarify-loading-notes, both descending from the same baseline commit.
4. Trigger the conflict intentionally
You are on main, so this command tries to integrate the feature branch into main:
git merge feature/clarify-loading-notes
Git should report something similar to:
Auto-merging docs/merge-conflict-lab.md
CONFLICT (content): Merge conflict in docs/merge-conflict-lab.md
Automatic merge failed; fix conflicts and then commit the result.
Immediately inspect the state:
git status
git diff --name-only --diff-filter=U
git status is your primary source of truth. It should say a merge is in progress and list docs/merge-conflict-lab.md as both modified. The second command lists unresolved files only.
Do not run another merge, switch branches, delete a branch, or use reset --hard. Git has paused at exactly the point where your judgment is needed.
Resolve, verify, stage, and complete the merge
Open docs/merge-conflict-lab.md. You should see markers around the two competing bullet lines.
Replace the entire conflicted region—including all three marker lines—with this final result:
- Loading: validate API data, provide retry feedback through an accessible status message, and report the video count.
This is the key professional habit:
Preserve valid intent, not mechanically every line from both sides.
For example, “accept both changes” can produce duplicated imports, repeated JSX elements, conflicting variable names, or invalid syntax. The result must be coherent in the context of the whole file.
Before staging, inspect your result:
git diff --check
git diff
git diff --check looks for whitespace errors. Then use your editor’s Find feature to search the file for <<<<<<<, =======, and >>>>>>>; there should be no remaining conflict markers.
Now stage the resolved file:
git add docs/merge-conflict-lab.md
git status
git diff --staged
Staging has a specific meaning during a merge: you are telling Git that this file’s merged state is intentional and complete. Read the staged diff one last time. It should contain the combined bullet, not marker lines or an accidental deletion.
Complete the merge with a commit:
git commit -m "Merge feature loading notes"
Git now creates a merge commit joining the two histories:
feature: B
/ \
A: base ------- M: merge result
\ /
main: C
Confirm that the merge is complete and that your working tree is clean:
git status
git log --oneline --graph --decorate --all
npm run build
The build is not expected to change because this conflict was in documentation, but running it reinforces a useful integration rule: after resolving a real application conflict, run the relevant build, tests, and targeted browser checks before treating the merge as complete.
Finally, delete the integrated feature branch:
git branch -d feature/clarify-loading-notes
If you need to stop instead of resolve
Sometimes you discover that you merged the wrong branch, lack enough context to decide safely, or need to talk with the author of the incoming change. In that case, aborting is better than guessing:
git merge --abort
This returns the branch and working tree to their pre-merge state in the normal clean-working-tree workflow used here. It does not erase either branch’s committed work; it only abandons this attempt to combine them.
A conflict visible in a GitHub pull request is the same underlying situation:

The GitHub web editor can be convenient for simple line conflicts, but the decision process remains unchanged: understand the base, current, and incoming intent; build the correct result; then verify it. For code conflicts, resolving locally is often safer because you can run your build and tests before pushing.
Wrap-up
You can now handle the full merge-conflict lifecycle without discarding valid changes:
- Confirm the target branch and run the merge.
- Use
git statusto identify unresolved files. - Interpret
HEADas the current target branch and the lower marker section as the incoming branch. - Edit a deliberate final result, often combining intent from both sides.
- Remove all conflict markers and verify the file.
- Use
git addto mark each resolved file. - Commit to complete the paused merge—or use
git merge --abortif you must safely step back.
Next, you will move from local integration to collaborative GitHub workflow: creating issues, opening pull requests, reviewing changes, and integrating a reviewed feature.
Can't find a good explanation? Sign up and we'll make it for you
Sign up