Every line of git pull, explainedEvery line of git pull, explainedStage 1 of 8 · 8 stages · ~5 min
Carol types git pull and presses enter. For a moment her terminal shows nothing at all, yet the wire to the server is busy. Then a dozen lines print and it’s over. Think about what just happened. The server had 2 commits Carol didn’t have, and Carol had 3 the server had never seen. Git sorted that out, and her terminal counted only 667 bytes.
ToySims

How git pull works: every line of its output, explained

Replay a real git pull: how Carol’s Git and the server find the history they share, and why only the new commits come down the wire.

Lesson summary

A pull is a conversation that finds the history both sides share, so only what is missing comes down the wire.

  1. A pull starts by asking only for the refs it needs: 1 line, not 501.
  2. Carol’s Git names commits until the server recognises one, and that one stands for all its history.
  3. The server walks from the new tip through parents, trees and files, and stops at anything Carol has.
  4. Deltas against objects Carol already has shrink 4348 bytes to 705.
  5. Carol’s Git rebuilds each delta from her own copies before anything is saved.
  6. The fetch only moves origin/main. The merge is what changes Carol’s files.
What does the server tell Carol first?

Where each branch she asked about ends, and nothing else.

Carol’s Git names twenty commits and the server has eight of them. How many does it need?

The newest commit the server has stands for every commit behind it, so the rest add nothing.

Why is a changed file not enough on its own?

Carol’s Git starts at the new commit and follows its tree to each file, so the commit and tree must come too.

A delta for numbers.txt leans on a base. Which base is safe?

Carol can only rebuild a delta from an object she already has or one that arrives with it.

Carol has an old branch of 200 commits she never pushed. Why can it slow her fetch?

By default her Git names commits from every branch, newest first, and none of the old branch’s commits can match.

After the fetch, why hasn’t Carol’s numbers.txt changed?

The fetch moved origin/main only. Her files follow main, and only the merge moves main.

Stage 1 of 8

What is Git telling you when you pull?

Carol types git pull and presses enter. For a moment her terminal shows nothing at all, yet the wire to the server is busy. Then a dozen lines print and it’s over. Think about what just happened. The server had 2 commits Carol didn’t have, and Carol had 3 the server had never seen. Git sorted that out, and her terminal counted only 667 bytes.

Each line of that output is a step Git took, but two steps printed nothing. Before the first line appeared, Carol’s Git asked the server what it has, and then the two sides worked out which commits they share. Those silent steps are the busy wire you just saw, and they decide how little has to come down.

So we’ll follow the pull from the top, one question per stop, and fill in both gaps on the way. We start where Git starts, before anything prints: Carol’s Git doesn’t yet know what the server has.

Stage 2 of 8

What does the server have?

Carol’s Git can’t ask for new commits until it knows what the server has. A server keeps a name for each branch and tag, called a ref, and each ref points at the commit it ends on. This server has main, plus 500 refs for pull requests other people opened. Plug Carol’s Git in and let the server tell her what it has.

That was protocol version 0, where the server always talks first. The line Carol needed, main, printed first, so it is now at the bottom of the pile. Protocol version 2 flips the order. Carol’s Git speaks first, with a command called ls-refs, and names prefixes: list only the refs whose names start with refs/heads/, where branches live, or refs/tags/.

How many lines will the server send back now?

The server checks each ref name against the prefixes, and only refs/heads/main matches. One line comes back, 61 bytes instead of 31953: main ends at 3e48d8d. The 500 pull-request refs never leave the server.

When you pull, Git picks those prefixes for you. Now you pick. Turn on the prefixes Carol’s Git should name, then press ask. Either way the round ends on the same fact: the server’s main ends at 3e48d8d, a commit Carol has never seen.

Stage 3 of 8

How does the server learn what Carol already has?

The server has told Carol that its main branch now ends at 3e48d8d. Before it can pack anything, it has to learn what Carol already has, and it can’t see her laptop. It hears only what her Git pushes through this slot, and it can only check each commit ID against its own shelf. Try the obvious move: send Carol’s newest commit.

Her newest commits exist only on her laptop, so one ID is not enough. Her Git walks back through her history, newest first, and packs sixteen IDs into one message before it hears anything back. The server checks each ID against its shelf, and every commit it keeps there records the commit before it, its parent.

How many of these sixteen IDs will the server actually need?

The first three are Carol’s unpushed commits, so they fall like the first one did. The fourth is 4bc5d89, the last commit Carol fetched from this server. It matches, and because every commit points back to its parent, that one match lights every lamp behind it.

One commit the server recognises tells it Carol has every commit behind it.

The other twelve IDs land on lamps that are already lit. Git sent them anyway, because it wrote the whole message before any reply came back. Only two lamps stay dark: 3e48d8d and 9b55aea, the commits Alice pushed. The server answers ready, and those two are all it will pack for Carol.

Now Carol also has experiment, a branch of 200 commits she never pushed, and it holds her newest work. By default her Git names commits from every branch, newest first. It can skip ahead instead of naming each commit, or name only the branches you choose. Set it up, then pull. The goal is just the two new commits, in one round trip.

Stage 4 of 8

Which objects does Carol need?

Two commits, then. But the server doesn’t keep a commit as one lump. It stores every piece as its own object with its own ID, and the pieces Carol really cares about are her files. Commit 32 changed just two of them, README.md and numbers.txt. Try the obvious move: send Carol only those two files.

Her Git got two files and nothing that says where they belong. It looks for 3e48d8d, the commit the server named, and that commit never arrived. So the server doesn’t guess which files changed. It walks. It starts at main’s newest commit and follows the parent ID written inside each one: 3e48d8d, then 9b55aea, then 4bc5d89. Carol already has that one, so the walk stops there.

Each new commit also names a tree: a list of every file in that snapshot, with the ID of the blob that holds each file’s contents. The server follows those pointers too. The new README.md and numbers.txt go in the crate. The 30 note files haven’t changed since 4bc5d89, so Carol has them and the walk stops again. That makes two commits, two trees and four blobs.

Eight objects are in the crate. What number will Git print after “Enumerating objects”?

Git prints ten, not eight. For each file it’s about to send, the server also finds the version in 4bc5d89: the old tree 87ccc5e and the old numbers.txt, 5bd1145. Carol already has both, so they stay out of the crate. The server lines them up because the new versions are almost the same, and the next step uses them to shrink the download. README.md didn’t exist back then, so it has no old version.

Your turn to pack. Carol’s Git will check the crate the same way the server walked: from 3e48d8d, through every parent, tree and file, until it reaches something she already has. A missing object breaks her pull, and an object she already had is wasted download.

Stage 5 of 8

How do eight objects get small?

The server now knows the eight objects Carol needs: two commits, two trees, two READMEs and two versions of numbers.txt. Git stores every one of them whole, as a full snapshot. So the obvious pack is all eight, laid end to end. On this bench each object is a strip of tape as long as the bytes it takes on the wire. Send them all whole.

That pack was 4348 bytes, and most of it was lines Carol already had. So before it packs anything, the server takes a closer look. Counting objects: 100% (10/10) is that look: it finds the ten objects from the last step, the eight to send and the two of Carol’s it will lean on, and reads how big each one is and how it is stored. Alice’s push arrived as loose objects, one whole file each.

Compressing objects: 100% (6/6) is the search for a cheaper way to send them. It skips the two READMEs, which are too small to bother with, and tries the other six. The commits don’t get any smaller. But the new numbers.txt, 4af46a1, is the 600 lines Carol already has in 5bd1145 plus 40 new ones. So instead of the whole file, the server sends a note: start from 5bd1145 and add these 40 lines. That note is a delta. It costs 118 bytes instead of 1156. Both trees get the same deal against 87ccc5e, the tree Carol has from commit 30.

The older numbers.txt, 2e51520, is last. What will the server build it from?

For the older numbers.txt, Git measured two bases. Against Carol’s copy it costs 86 bytes. Against 4af46a1, the newer file in this same pack, it costs 36, and Git keeps the cheaper one. The older file is just the first 620 lines of the newer one, so its delta only says copy the start. An older version stored as changes to a newer one. That is Total 8 (delta 4): eight objects, four of them sent as changes, 705 bytes instead of 4348. On the real wire it is 687, because the one base inside the pack is named by its position instead of its 20-byte ID, and Carol’s terminal counts 667, because its count stops before the pack’s closing 20-byte checksum. Reused 0 means none of them was copied from a pack the server already had, so the server did this work fresh. On a bigger pull, from a server that kept its objects packed, Git reused 198 of 200. Notice that three of the four deltas lean on objects that aren’t in the pack at all.

Now you choose. Each tree and each numbers.txt can go whole, as changes against something Carol has, or as changes against another object in the pack. Git got the pack down to 705 bytes. Match it, and make sure Carol can rebuild every object from what she has and what arrives.

Stage 6 of 8

What if a delta’s base isn’t in the download?

The eight objects come down the wire as one strip, the pack. Four of them are deltas: not whole files, just the changes that rebuild a file from another object, called its base. c4ef794 is the new version of the project’s top folder, stored as changes to 87ccc5e. Try the obvious move: find 87ccc5e in the download.

It isn’t there, and that’s on purpose. One line of the request Carol’s Git sent said thin-pack, which means “you may send me changes against objects I already have.” The server knew from the commits Carol’s Git named that she has commit 30, so she has its folder, 87ccc5e. Carol keeps her objects in two places: loose files in 256 drawers, sorted by the first two characters of each ID, and the pack her clone came in. Her Git looks 87ccc5e up by its ID in both, finds it in the clone pack, copies it out, and applies the changes.

If the server had sent 87ccc5e and 5bd1145 as well, how much bigger would the 667-byte download have been?

Now the whole download. It has 8 objects, which is under Git’s limit of 100, so Carol’s Git unpacks it: each object becomes its own loose file in its drawer. Each delta is rebuilt on the way in. 4af46a1, the new numbers.txt, comes from 5bd1145 in her clone pack. 2e51520 is stored as changes to 4af46a1, so it waits until 4af46a1 is in its drawer, then uses that. This is the line Carol saw: Unpacking objects, 8 of 8, 667 bytes.

A big pull ends differently. Switch it to 200 objects, the run where Alice pushed 40 commits. At 100 objects or more, unpacking would mean hundreds of small files, so Git keeps the pack as one file and indexes it. A pack kept on disk has to be complete by itself, so her Git copies the two missing bases out of her clone pack and adds them to the end of the new one. That’s the other line you may have seen: Resolving deltas, completed with 2 local objects.

Now you finish one. Here are the same eight objects, kept as a pack the way a big pull is. Pick a delta, then say where its base is: in Carol’s objects, or in this pack. The server left out only what Carol already had, and a base has to be whole before anything can be rebuilt from it. The terminal counts 687 bytes this time, not 667: it is the same pack, but Receiving counts its 20-byte closing checksum, and Unpacking stops just before it.

Stage 7 of 8

Carol has the objects. Why hasn’t her code changed?

Every object is on Carol’s laptop now, the eight new ones each in its own drawer. Here her history is a path of stepping stones, and a branch is a flag planted on one stone. The fetch does one last thing: it moves origin/main, her Git’s copy of where the server’s main ends, from 4bc5d89 to 3e48d8d. That’s the line “4bc5d89..3e48d8d main -> origin/main”. Alice’s new numbers.txt, 4af46a1, is on Carol’s laptop too. Try the obvious check: open her numbers.txt.

It’s still 5bd1145. Carol’s files are the files of the stone her own branch, main, points at, and main is still on L3, her newest commit. So a pull has a second job: move main. When you have no commits of your own, a pull seems to do just that, slide your branch forward. Try it here: move main to where origin/main is.

Carol would get Alice’s work, but walking back from 32 never reaches L1, L2 or L3, because the two paths split at 30. Git never moves main like that. A pull is really two commands: git fetch, which you just watched, then git merge, since Carol’s Git is set to merge (pull.rebase=false). Because the paths have split, the merge sets down a new commit with two parents, L3 and 32. main moves onto it, and only now do Carol’s files change: README.md arrives with 2 lines and numbers.txt gains 40. That’s “Merge made by the 'ort' strategy.” ort is the name of the method Git uses to combine the two sides.

Most pulls look simpler. Here Carol has no commits of her own, so main sits on 30 and 32 is straight ahead on the same path. There is nothing to join, so Git just slides main forward and makes no new commit. That’s the other ending you may have seen: “Updating 4bc5d89..3e48d8d” and “Fast-forward”. Switch Carol’s commits to L1–L3 to compare.

You can tell pull how to finish. Every setting fetches first; they differ in what happens to main. Carol already shared her three commits with her teammate Bob, so he is standing on 1be0836 too, and it has to stay in main. Tap a setting to pull with it. The goal is Alice’s work in Carol’s files, with 1be0836 still in main.

Stage 8 of 8

What did Carol’s Git and the server agree on?

Here is Carol’s pull again, top to bottom, with what each stop answered. The server listed one ref, not 501. Carol’s Git named commits until one matched, and 4bc5d89 stood for every commit behind it. The server walked from 3e48d8d back to that commit and packed 8 objects, lining up 2 Carol already had. Deltas against those shrank 4348 bytes to 705, which her terminal counted as 667, and her Git rebuilt the rest from her own copies. Only then did the fetch move origin/main, and the merge move main and change her files.

The two stops that printed nothing did the real work. Because one ID matched, the server knew exactly where Carol’s history and its own split, so it could stop its walk there and send 8 objects instead of the whole history. The next time a pull sits silent before its first line, that conversation is what you are waiting on.

Dev has 500 old local branches, and git fetch sits silent for a long time before its first line. Which stop is slow?