open atlas
↑ Back to track
Git, from zero to senior GIT · 05 · 03

Fetch and integrate

git fetch only updates origin/*, never your work. Then choose how to integrate: pull (merge) makes merge bubbles, pull --rebase replays your commits for a linear history, pull --ff-only refuses to merge when diverged. Merge keeps the true history; rebase keeps it linear.

GIT Middle ◷ 17 min
Level
FoundationsJuniorMiddleSenior

git pull feels like one safe button, but it quietly makes a decision for you: how to fold the remote’s commits into your own. Pull two commits while you have local work and you get a merge commit — a little “bubble” in the history — whether you wanted one or not. Teams that care about a clean log learn to stop pulling blindly and split the operation back into its two halves: fetch, then integrate on purpose.

By the end of this lesson you will be able to fetch without touching your work, and choose deliberately between merge, rebase, and fast-forward-only integration — knowing the history each one produces.

Goal

After this lesson you can use git fetch to update origin/* safely, then integrate with merge, --rebase, or --ff-only, explain the history shape each produces, and set pull.rebase/pull.ff so git pull does what you intend by default.

1

git fetch updates your remote-tracking branches and nothing else. It downloads new commits from the server and advances origin/main, origin/feature, and friends. It does not touch your local branches, your index, or your working tree — so it can never cause a conflict or lose work:

git fetch origin
git log --oneline main..origin/main   # what the server has that I don't

Because fetch is non-destructive, it is the command to run when you just want to see what changed upstream before deciding how to react.

2

Integrating by merge — the git pull default — preserves true history but adds merge bubbles. After fetching, git merge origin/main (which plain pull runs for you) joins the two lines of work with a merge commit that has two parents:

git fetch origin
git merge origin/main      # or just: git pull

The upside: the history is truthful — it records that two people genuinely worked in parallel and where their work joined. The downside: many such joins produce a tangle of “merge bubbles” that makes git log noisy and git bisect harder to read.

3

Integrating by rebase replays your local commits on top of the remote, giving a linear history. Instead of a merge commit, rebase takes the commits unique to your branch, sets them aside, fast-forwards your branch to the remote tip, then re-applies your commits one by one on top:

git fetch origin
git rebase origin/main     # or in one step: git pull --rebase

The result is a single straight line — as if you had started your work after the remote commits all along. No merge bubble. The cost: your commits get new hashes (they are rewritten), so you must not rebase commits you have already shared on a branch others build on.

Why this works

Merge versus rebase is a genuine tradeoff, not a style war. Merge keeps the true topology: you can always see that a feature was developed alongside main and merged on a given day — valuable for auditing and for long-lived branches. Rebase discards that parallelism in favour of a clean, bisectable straight line that reads like a story. The widely-used rule of thumb: rebase your local, unpushed work to tidy it before sharing; merge (or use a platform merge) to integrate shared branches, and never rebase commits other people have already pulled.

4

git pull --ff-only refuses to integrate when the branches have diverged. A fast-forward is possible only when your branch has no commits the remote lacks — then git can just slide your branch pointer forward with no new commit. --ff-only allows exactly that and fails loudly otherwise:

git pull --ff-only
# fatal: Not possible to fast-forward, aborting.

That failure is a feature: instead of silently creating a merge you did not ask for, git stops and makes you decide — merge or rebase. Many engineers set this as the default precisely to kill surprise merge commits.

5

Configure the default so git pull stops guessing. You can lock in the integration strategy globally or per-repo:

git config --global pull.ff only        # pull fast-forwards or aborts
git config --global pull.rebase true    # OR: pull always rebases

Set one of these and git pull becomes predictable: either it cleanly fast-forwards / aborts, or it rebases your work onto the remote. Picking a team-wide policy removes a whole class of accidental-merge-bubble noise from the history.

Worked example

The same divergence, three ways.

You branched from main at A, then committed X and Y locally. The team pushed B and C to origin/main. You git fetch — now origin/main is at C, your main is at Y, and the two lines have diverged from A.

  • git merge origin/main creates a merge commit M whose parents are Y and C. History keeps both lines visible and joins them at M. git log --graph shows a bubble. Your commits X, Y keep their original hashes.
  • git rebase origin/main moves to C, then re-applies X and Y on top as new commits X' and Y'. History is the straight line A-B-C-X'-Y'. No merge commit, but X and Y are gone, replaced by rewritten copies.
  • git pull --ff-only aborts immediately with “Not possible to fast-forward” — because your Y is not contained in C. Nothing changes; git hands the decision back to you.

Same starting point, three different histories. Choosing on purpose — rather than letting plain pull pick merge for you — is the skill.

Common mistake

A costly mistake is running git pull --rebase on a branch whose commits you already pushed and a colleague already pulled. Rebase rewrites those commits into new hashes; the colleague’s clone still has the originals, and your next push either conflicts or, if forced, orphans their work. The safe boundary: rebase only commits that live solely in your local repo. Once a commit is shared, integrate with merge, not rebase.

Check yourself
Quiz

Your branch and origin/main have diverged. You want to avoid an accidental merge commit and be forced to decide consciously. Which command does that?

Recap

Splitting pull back into its halves gives you control. git fetch safely advances origin/* and never touches your work, so you can inspect the divergence first. Then you integrate on purpose: merge (the pull default) preserves the true parallel history but adds merge bubbles; --rebase replays your local commits for a clean linear history at the cost of rewriting their hashes; --ff-only refuses to integrate a diverged branch so you choose consciously. Lock the behaviour in with pull.ff = only or pull.rebase = true, and never rebase commits others already have. Next: when rewriting history is the goal, how to push it safely.

Practice

Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.

recallapplystretch0 of 4 done

Something unclear?

Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.

shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources2
expand
  1. 01
  2. 02

Trademarks belong to their respective owners. Editorial reference only.