Overview
Two people edited ingest.py. git pull brings their commit. You edit the markers, git add, then git commit. Still no remote here.
On this page7 sections
The broken result
A merge conflict happens when two commits change the same lines in the same file. Git cannot decide which version to keep, so it stops and asks you to resolve the overlap manually. Conflicts are a normal part of teamwork, not an error.
When git pull tries to merge and finds overlapping changes, it writes conflict markers into the file: <<<<<<< marks the start of your version, ======= separates the two sides, and >>>>>>> marks the end of the incoming version. The file is not usable until you remove these markers and keep one coherent version.
Resolution is a human decision, not a magic flag. You read both sides, decide which logic to keep (or combine them), edit the file, then tell Git the conflict is resolved by running git add and git commit.
Why this failure matters
Imagine your team has two engineers working on the same ingest pipeline. One changes the date format, the other changes the column filter. Both push to the same branch. The second push triggers a conflict. If nobody resolves it properly, the pipeline ships with conflict markers in the code and fails at runtime. Production data stops flowing.
Conflict markers are the <<<<<<< / ======= / >>>>>>> lines Git writes into the file. Resolution is not automatic. You edit the file into one coherent version, git add the path so the index drops the conflict state, then git commit to record the merge.
How to diagnose
This tab has no Git remote. The exercise orders four human steps as strings. On your laptop you would run git pull in a clone, open the file, and commit. Do not git commit -a while markers remain; that records the conflict markers as if they were valid code.
Mental model: fetch, merge, edit, record
git fetch downloads commits from the remote. git pull is fetch plus a merge (or rebase, if configured). When both sides changed the same hunk, Git writes markers and leaves the index in a conflicted state. Resolution is: open the file, delete the markers, keep the logic you can defend, git add the path so the conflict flag clears, git commit to record the merge.
Pull first. Commit last. Add is how the index drops the conflict.
What conflict markers look like
When Git finds overlapping changes, it writes both versions into the file separated by marker lines. Everything between <<<<<<< and ======= is your version. Everything between ======= and >>>>>>> is the incoming version. Your job is to delete the markers and keep the correct logic.
conflict_file = """
<<<<<<< HEAD
date_col = "order_date"
=======
date_col = "created_at"
>>>>>>> origin/main
"""
def has_markers(text):
return "<<<<<<<" in text or ">>>>>>>" in text
print(has_markers(conflict_file))Markers are the product until you delete them
Search the tree for <<<<<<< before you commit.
| Step | Why it is here | Trap |
|---|---|---|
| git pull | Bring the other commit onto your branch | Pushing instead, which rejects or overwrites. |
| edit conflict markers | One file, no <<<<<<< left | Picking both sides and shipping a syntax error. |
| git add ingest.py | Index leaves the unmerged state | git add . of a tree still full of markers. |
| git commit | Records the merge | Commit while grep still finds the marker. |
git status during a conflict lists unmerged paths. That list shrinking to empty is the signal that resolution is complete.
Worked example: unshuffle the four beats
The starter list puts commit first, which is how people accidentally record markers as code. Sorting by a canonical ORDER uses the same rank-map pattern you will see in later lessons. result must be git pull, then edit, then add ingest.py, then commit.
ORDER = ["git pull", "edit conflict markers", "git add ingest.py", "git commit"]
SHUFFLED = ["git commit", "git add ingest.py", "git pull", "edit conflict markers"]
rank = {name: i for i, name in enumerate(ORDER)}
print(sorted(SHUFFLED, key=lambda name: rank[name]))Verifying markers are gone
Before committing, always check that no conflict markers remain. This function scans a list of file lines and returns True only when every marker is gone.
def markers_clean(lines):
for line in lines:
if line.strip().startswith("<<<<<<<") or line.strip().startswith(">>>>>>>"):
return False
return True
resolved = ["date_col = 'order_date'", "filter_col = 'status'"]
unresolved = ["<<<<<<< HEAD", "date_col = 'order_date'", "=======", "date_col = 'created_at'", ">>>>>>> origin/main"]
print("Resolved:", markers_clean(resolved))
print("Unresolved:", markers_clean(unresolved))No remote in this tab
A real pull needs an origin remote. Create a repo later, add a remote, and practice a conflict with a second clone. This tab grades the order of steps, not actual Git commands.
git add . during a conflict
Running git add . adds every file, including ones you have not resolved yet. Use git add on specific paths you have actually edited and checked for markers.
Sorting patterns from Core Python
The rank-map sorting pattern (enumerate plus sorted with a key function) appeared in Core Python when you sorted custom data. The same technique applies whenever you need to enforce a canonical order.
How this appears on the job
A PR review that says "fix conflicts" means this four-step sequence, not git push --force onto main. Force-pushing main rewrites history and can erase other people's work. Prefer merge commits or rebase on your feature branch.
- Pull (or fetch plus merge) before you edit.
- Markers out, then git add, then git commit.
- Status and a search for <<<<<<< are the done check.
The fix
When git pull finds overlapping edits, resolution is four human steps in a fixed order. The starter shuffles them; you restore the runbook.
Input: four beats that must not be reordered.
| step | action | why first/last |
|---|---|---|
| git pull | bring the other commit | you cannot merge what you have not fetched |
| edit conflict markers | one coherent file | markers are not valid code |
| git add ingest.py | clear unmerged state | index must leave conflict mode |
| git commit | record the merge | only after markers are gone |
ORDER = ["git pull", "edit conflict markers", "git add ingest.py", "git commit"]
SHUFFLED = ["git commit", "git add ingest.py", "git pull", "edit conflict markers"]
rank = {name: i for i, name in enumerate(ORDER)}
print(sorted(SHUFFLED, key=lambda name: rank[name]))Sorted output is pull, edit, add, commit. Commit first would record conflict markers as if they were valid Python.
Output: the canonical resolution order.
| position | step |
|---|---|
| 1 | git pull |
| 2 | edit conflict markers |
| 3 | git add ingest.py |
| 4 | git commit |
Copy-paste without reading the output
Run Sample first. If the numbers or row count look wrong, stop and re-read the previous section before changing code.
Common beginner questions
Why does Git not just pick one side automatically?
Git cannot know which change is correct. The date format change and the column filter change might both be needed, or one might break the other. Only a human reading the code can decide.
What if I want to keep both sides?
You can. Delete only the marker lines (<<<<<<< , =======, >>>>>>>) and keep both blocks of code. Make sure the combined result is valid syntax and correct logic.
Can I avoid conflicts entirely?
You can reduce them by pulling often, working on separate files, and using short-lived feature branches. But conflicts will happen on any team. Learning to resolve them quickly is more valuable than trying to avoid them.
What comes next
The next lesson covers Linux shell fundamentals: PATH, pipes, and exit codes. These are the building blocks of every CI pipeline and deployment script you will write.
Practice
Run Sample and confirm the four-step order. Then complete Exercise: unshuffle SHUFFLED into that order and store it in result.
Practicals · load into the editor
After you read the theory, run these in the pane on the right. They execute in this tab, no cluster.