How to pull down someone else's changes safely, why some updates are effortless and others need a real merge, and exactly how to resolve a conflict when two branches edit the same lines. The thread running through all of it: fetch first, inspect before you change anything, then only act when the inspection proves it's safe.
Learn what's out there before you touch your own work.
git fetch origin downloads the latest state of the remote — new commits and branches — into your local copy without changing any of your working files. It updates the origin/* "remote-tracking" references: think of them as a local snapshot of what the remote looks like right now. This is always the safe first move.
Then git status tells you where you stand relative to the remote: behind (the remote has commits you don't), ahead (you have local commits not yet pushed), or diverged (both sides have unique commits — the tricky case). For an exact count:
git rev-list --left-right --count HEAD...origin/main
# prints two numbers: <your-unique> <remote-unique>
# e.g. "0 2" → you have 0 unique, remote has 2
That 0 on the left is the magic number — it means your side has nothing unique, so the update can't possibly conflict.
git log A..B lists "commits in B but not in A." To ask "is it safe to delete this branch?", run git log origin/main..origin/some-branch — empty output means every commit is already in main, so nothing is lost.
No merge needed — just slide the pointer forward.
When your branch has zero unique commits and the remote is ahead, there's nothing to actually combine. Git can just move your branch pointer forward to match the remote. This is a fast-forward:
git merge --ff-only origin/main
The --ff-only flag is a safety rail: "only do this if it's a clean fast-forward; refuse and stop if a real merge would be needed." That way you never silently create a merge commit or trigger a conflict. (Plain git pull works too, but --ff-only makes the safety explicit.) Visually, your pointer just slides from C to E:
before: A---B---C (you)
\
D---E (origin/main)
after: A---B---C---D---E (you = origin/main)
Both sides built on the same commit, then went separate ways.
When git status says "have diverged, and have N and M different commits each," there's no pointer to just slide — Git has to genuinely combine two lines of history:
D---E (your local main)
/
A---B---C
\
F---G (origin/main)
One key distinction first: diverging is not the same as conflicting. If the two sides touched different files (or different parts of the same file), Git combines them automatically. A real conflict only happens when both sides changed the same lines differently — then Git can't guess which you want, so it stops and asks. You have two strategies to reconcile:
git merge origin/main)Creates a new merge commit that ties both histories together. Preserves exactly what happened on both sides; history shows the fork. The default, and the right choice when others share the branch.
A---B---C---D---E---M
\ /
F---G
git rebase origin/main)Replays your commits on top of the remote's, as if you'd started from the latest. Linear history, no merge commit — cleaner. But it rewrites your commits (D → D', new hashes), so the rule is: only rebase commits you haven't pushed/shared yet.
A---B---C---F---G---D'---E'
Git pauses and hands you the file. You decide what's correct.
When either strategy hits the same lines, Git stops and git status shows the file as "both modified." Open it and you'll see conflict markers:
<<<<<<< HEAD
color: #F5A623; /* your version */
=======
color: #E8930C; /* incoming from origin/main */
>>>>>>> origin/main
<<<<<<< HEAD and ======= is your side.======= and >>>>>>> is theirs.You fix it by hand: edit the file so it reads correctly — keep one side, keep the other, or blend them — and delete all three marker lines. The goal is a clean file, as if you'd written it correctly the first time. Then mark it resolved and finish:
git add path/to/file # mark this file's conflict resolved
# ...repeat for each conflicted file...
git commit # if merging — finalizes the merge commit
# ── OR ──
git rebase --continue # if rebasing — on to your next replayed commit
For big or many conflicts, lean on tooling: git mergetool opens a visual 3-way diff (yours / theirs / common base), and editors like VS Code put clickable Accept Current / Accept Incoming / Accept Both buttons right on the markers. If you know you want one side wholesale, git checkout --ours path or --theirs path resolves a whole file in one shot.
Nothing is final until you say so — you can always back out.
If a merge or rebase gets messy and you'd rather return to exactly where you started, bail out with no harm done:
git merge --abort # during a merge
git rebase --abort # during a rebase
This is why the fetch-then-inspect habit is so safe: nothing is committed until you resolve and confirm, and until then you can always abort back to the starting point. The same range trick that tells you a branch is safe to delete (A..B) is the one that tells you what a pull or a PR actually adds — learn it once, reuse it everywhere.
Fetch → inspect → act only when it's provably safe → clean up.
add + continue/commit, or --abort to back out.