Fetching and pulling¶
Once a project lives in several places, the copies drift apart. Sam added a network printer to the inventory from the laptop and sent it to the server (how to send commits is on the next page). The desktop does not have that commit yet: its repository needs to receive changes from the remote.
Remote and local drift apart¶
Git never synchronizes on its own. The desktop only learns about new commits on the server when you ask it to communicate with the remote:
flowchart LR
subgraph server["Remote: origin"]
rm[("main<br/>... → 1b9488f")]
end
subgraph desktop["Desktop repository"]
om["origin/main<br/>remote-tracking branch"]
lm["main<br/>local branch"]
wd["Working directory"]
end
rm -- "git fetch" --> om
om -- "git merge origin/main" --> lm
lm --> wd
rm == "git pull = fetch + merge" ==> lm
There are two steps, which git pull combines:
- Fetch: download the new commits and update the remote-tracking branches (
origin/main). - Merge: integrate them into your local branch.
Fetching: git fetch¶
git fetch origin downloads everything that is new on origin:
$ git fetch origin
From /srv/git/homelab
f9731d8..1b9488f main -> origin/main
| Part | Meaning |
|---|---|
From /srv/git/homelab |
The remote that was contacted |
f9731d8..1b9488f |
origin/main moved from f9731d8 to 1b9488f |
main -> origin/main |
The remote's main was stored in the local remote-tracking branch origin/main |
Branches that exist on the remote but were never fetched before appear as * [new branch], and get their own remote-tracking branch (origin/<name>). You can then start working on one with git switch <name>, which creates a local branch from origin/<name>.
Fetching does not change your files or your local branches. It only updates the remote-tracking branches. git status now compares main with the fresh origin/main:
$ git status
On branch main
Your branch is behind 'origin/main' by 1 commit, and can be fast-forwarded.
(use "git pull" to update your local branch)
nothing to commit, working tree clean
This makes fetching completely safe, and lets you review what arrived before integrating it:
$ git log --oneline main..origin/main
1b9488f Add network printer
$ git diff main origin/main
diff --git a/inventory.csv b/inventory.csv
index d781e97..1a122e3 100644
--- a/inventory.csv
+++ b/inventory.csv
@@ -4,3 +4,4 @@ nas,192.168.1.20,storage
web01,192.168.1.30,web
mon01,192.168.1.50,monitoring
ups01,192.168.1.70,power
+printer,192.168.1.80,print
Fetching a single branch¶
To fetch only one branch of the remote, add its name:
$ git fetch origin main
From /srv/git/homelab
* branch main -> FETCH_HEAD
FETCH_HEAD is a temporary reference to what was just fetched. When the branch is already tracked locally, origin/main is updated as well.
Merging the fetched changes¶
The fetched commits are integrated like any other branch, with git merge, from the branch that should receive them:
$ git merge origin/main
Updating f9731d8..1b9488f
Fast-forward
inventory.csv | 1 +
1 file changed, 1 insertion(+)
main had no new local commit, so this is a fast-forward. The desktop is now up to date.
git merge origin also works: a remote name alone stands for its default branch, origin/HEAD, which points to origin/main here. Naming the branch explicitly is clearer.
Pulling: git pull¶
Fetching and then merging is such a common workflow that Git combines both in one command:
git pull origin main
This fetches main from origin, then merges it into the current local branch. A new commit was pushed from the laptop in the meantime:
$ git pull origin main
From /srv/git/homelab
* branch main -> FETCH_HEAD
1b9488f..7eb0ef7 main -> origin/main
Updating 1b9488f..7eb0ef7
Fast-forward
services.md | 1 +
1 file changed, 1 insertion(+)
The first three lines are the fetch, the rest is the merge.
| Command | What it fetches | Where it merges |
|---|---|---|
git pull origin main |
main from origin |
The current branch |
git pull origin dev |
dev from origin |
The current branch, even if it is main |
git pull origin |
The upstream of the current branch, if it is on origin |
The current branch |
git pull |
The upstream of the current branch, from its remote | The current branch |
The short forms rely on the upstream of the current branch: the remote branch it is configured to track (see upstream branches). After git clone, the local main already tracks origin/main, so a plain git pull is enough. On a branch without an upstream, Git refuses and asks for the branch:
You asked to pull from the remote 'origin', but did not specify
a branch. Because this is not the default configured remote
for your current branch, you must specify a branch on the command line.
git pull merges into the branch you are on
git pull origin dev run from main merges the remote dev into your local main. Switch to the right local branch before pulling.
When both sides have new commits, the merge part of git pull is a three-way merge, which may need a merge commit or end in a conflict. This case is covered on the Pushing page.
A word of caution: uncommitted changes¶
Sam starts editing README.md on the desktop without committing, while a new version of the same file is waiting on the server. Pulling would overwrite the local edits, so Git refuses:
$ git pull origin main
From /srv/git/homelab
* branch main -> FETCH_HEAD
7eb0ef7..5477908 main -> origin/main
Updating 7eb0ef7..5477908
error: Your local changes to the following files would be overwritten by merge:
README.md
Please commit your changes or stash them before you merge.
Aborting
The fetch succeeded, but the merge was aborted and nothing was changed. Before pulling, save your work locally, in one of these ways:
| Option | Command | When |
|---|---|---|
| Commit the changes | git commit -am "..." |
The edits are ready |
| Stash them | git stash, then git pull, then git stash pop |
The edits are unfinished: stash puts them aside and brings them back after the pull |
| Discard them | git restore README.md |
The edits are not needed (they are lost) |
Here the edit was only a test. After discarding it, the pull goes through:
$ git restore README.md
$ git pull origin main
From /srv/git/homelab
* branch main -> FETCH_HEAD
Updating 7eb0ef7..5477908
Fast-forward
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
Summary¶
| Command | Result |
|---|---|
git fetch origin |
Fetch all new branches and commits from origin, without merging |
git fetch origin main |
Fetch only main from origin |
git log --oneline main..origin/main |
List the fetched commits not yet in local main |
git merge origin/main |
Merge the fetched main into the current branch |
git pull |
Fetch and merge the upstream of the current branch |
git pull origin dev |
Fetch dev from origin and merge it into the current branch |
Common mistakes¶
- Expecting
git fetchto update your files. It only updatesorigin/...branches. Merge, or usegit pull. - Pulling into the wrong branch.
git pullalways merges into the current branch. - Pulling with uncommitted changes. Commit or stash first, or the merge is aborted.
- Confusing
mainandorigin/main.mainis yours;origin/mainis the last known state of the remote'smain.
Hands-on labs¶
Three labs, from guided to more autonomous. Each one has its own setup, so you can do them in any order and repeat them as often as you like. Type the commands rather than pasting them, and read every output before moving on: the goal is to build reflexes, not to reach the end.
Lab 1: receive changes from a remote¶
Objective: simulate a second computer, then fetch, inspect, merge, and pull its changes.
Prerequisites and initial state: Git installed. The setup creates a "server" repository and two clones in ~/git-practice/sync: desktop and laptop. A first commit from the laptop is already on the server.
Setup:
mkdir -p ~/git-practice/sync && cd ~/git-practice/sync
git init -q --bare -b main server.git
git clone -q server.git laptop 2>/dev/null
cd laptop
git config user.name "Practice User"
git config user.email "[email protected]"
printf 'hostname,ip,role\npi-dns,192.168.1.10,dns\n' > inventory.csv
git add . && git commit -q -m "Create inventory" && git push -q origin main
cd .. && git clone -q server.git desktop
cd laptop
echo "nas,192.168.1.20,storage" >> inventory.csv
git commit -q -am "Add NAS" && git push -q origin main
cd ../desktop
Tasks: (all from ~/git-practice/sync/desktop)
- Check that
inventory.csvdoes not contain the NAS yet. Fetch fromorigin, then rungit status. Does the file contain the NAS now? - List the commits waiting on
origin/main, and show their changes. - Merge them into your local
main. - Simulate new work on the laptop: run
(cd ../laptop && echo "web01,192.168.1.30,web" >> inventory.csv && git commit -q -am "Add web server" && git push -q origin main). - On the desktop, add a line
drafttoinventory.csvwithout committing, and pullmainfromorigin. Read the error. - Discard the draft, and pull again.
Expected result and verification:
- After the fetch,
git statussaysYour branch is behind 'origin/main' by 1 commit, andinventory.csvstill has no NAS. - Task 2 shows
Add NASand a diff addingnas,192.168.1.20,storage. - Task 5 fails with
Your local changes to the following files would be overwritten by merge. - Finally,
git log --onelineon the desktop showsAdd web server,Add NAS,Create inventory, andgit statussaysYour branch is up to date with 'origin/main'.
Solution
# 1. Fetch does not touch the files
grep nas inventory.csv # nothing
git fetch origin
git status # behind 'origin/main' by 1 commit
grep nas inventory.csv # still nothing
# 2. Inspect before merging
git log --oneline main..origin/main # Add NAS
git diff main origin/main # +nas,192.168.1.20,storage
# 3. Merge (fast-forward)
git merge origin/main
# 4. New commit pushed from the "laptop"
(cd ../laptop && echo "web01,192.168.1.30,web" >> inventory.csv && git commit -q -am "Add web server" && git push -q origin main)
# 5. Uncommitted changes block the merge part of the pull
echo "draft" >> inventory.csv
git pull origin main # error: Your local changes ... would be overwritten by merge
# 6. Discard and pull again
git restore inventory.csv
git pull origin main # Fast-forward
git log --oneline # Add web server, Add NAS, Create inventory
- In task 5, the fetch part of
git pullalready ran:origin/mainwas up to date, only the merge was refused. - To keep the draft instead of discarding it:
git stash,git pull origin main,git stash pop.
Clean up when you are done: rm -rf ~/git-practice/sync.
Lab 2: stale information and diverged branches¶
Objective: see that git status only knows what was last fetched, fetch a single branch, then pull into a main that has diverged.
Prerequisites and initial state: Git installed. The setup creates a "server" and two clones in ~/git-practice/diverged. The laptop has pushed a commit on main and a new branch docs; meanwhile, the desktop has made a local commit of its own.
Setup:
mkdir -p ~/git-practice/diverged && cd ~/git-practice/diverged
git init -q --bare -b main server.git
git clone -q server.git laptop 2>/dev/null
cd laptop
git config user.name "Laptop User"
git config user.email "[email protected]"
printf 'hostname,ip,role\npi-dns,192.168.1.10,dns\n' > inventory.csv
printf '# Services\n- pi-dns: Pi-hole\n' > services.md
git add . && git commit -q -m "Create inventory" && git push -q origin main
cd .. && git clone -q server.git desktop
cd desktop
git config user.name "Desktop User"
git config user.email "[email protected]"
git config pull.rebase false
echo "- nas: Samba shares" >> services.md
git commit -q -am "Document NAS service"
cd ../laptop
echo "nas,192.168.1.20,storage" >> inventory.csv
git commit -q -am "Add NAS"
git switch -q -c docs && echo "# Homelab" > README.md && git add . && git commit -q -m "Add README"
git push -q origin main docs
cd ../desktop
Tasks: (all from ~/git-practice/diverged/desktop)
- Run
git status. According to it, is the desktop behind the server? - Fetch only the
docsbranch fromorigin. Rungit statusagain: has the answer changed? - Now fetch everything from
origin, and rungit statusonce more. - Display the graph of
mainandorigin/main. - Pull
mainfromoriginwithout opening an editor, and display the graph again.
Expected result and verification:
- Task 1:
Your branch is ahead of 'origin/main' by 1 commit., which is out of date: the server has a new commit. - Task 2: the fetch shows
* [new branch] docs -> origin/docs, andgit statusstill saysahead ... by 1 commit. - Task 3:
Your branch and 'origin/main' have diverged, and have 1 and 1 different commits each, respectively. - Task 4: two lines of history,
Document NAS serviceandAdd NAS, starting fromCreate inventory. - Task 5:
Merge made by the 'ort' strategy., then a merge commitMerge branch 'main' of ...joining both lines.git statussaysahead of 'origin/main' by 2 commits.
Solution
# 1. Status compares with the last known state of origin
git status # ahead of 'origin/main' by 1 commit
# 2. Fetching another branch does not update origin/main
git fetch origin docs # * [new branch] docs -> origin/docs
git status # still "ahead by 1"
# 3. Fetch everything
git fetch origin
git status # have diverged, ... 1 and 1 different commits each
# 4. See the fork
git log --oneline --graph main origin/main
# 5. Pull: fetch + three-way merge
git pull --no-edit origin main # Merge made by the 'ort' strategy.
git log --oneline --graph
git status # ahead of 'origin/main' by 2 commits
git statusnever contacts the server. "Ahead", "behind", and "diverged" are computed fromorigin/mainas it was at the last fetch.git fetch origin docsonly updatesorigin/docs. Fetch everything before trustinggit status.- After the pull, the desktop is ahead by 2 commits: its own
Document NAS serviceand the merge commit. They reach the server withgit push, on the next page.
Clean up when you are done: rm -rf ~/git-practice/diverged.
Lab 3: inspect before you merge¶
Objective: fetch several commits from a teammate, review them before merging, and undo the one that is wrong.
Prerequisites and initial state: Git installed. The setup creates ~/git-practice/review with a "server" and two clones. Alex, on the laptop, has pushed three commits; one of them contains a mistake.
Setup:
mkdir -p ~/git-practice/review && cd ~/git-practice/review
git init -q --bare -b main server.git
git clone -q server.git laptop 2>/dev/null
cd laptop
git config user.name "Alex Martin"
git config user.email "[email protected]"
printf 'hostname,ip,role\npi-dns,192.168.1.10,dns\n' > inventory.csv
printf '# Services\n- pi-dns: Pi-hole\n' > services.md
git add . && git commit -q -m "Create inventory" && git push -q origin main
cd .. && git clone -q server.git desktop
cd laptop
echo "nas,192.168.1.20,storage" >> inventory.csv && git commit -q -am "Add NAS"
echo "- nas: Samba shares" >> services.md && git commit -q -am "Document NAS service"
sed -i 's/192.168.1.10/192.168.1.100/' inventory.csv && git commit -q -am "Tidy up inventory"
git push -q origin main
cd ../desktop
git config user.name "Sam Rivera"
git config user.email "[email protected]"
Tasks: (all from ~/git-practice/review/desktop)
- Fetch from
origin. How many commits are waiting? - List them, then review all their changes at once.
- One of them changes something its message does not announce. Find it, and show it alone.
- Merge the fetched commits into your
main. - Undo the faulty commit with a new commit, without opening an editor, and check the inventory.
Expected result and verification:
- Task 1:
git statussaysYour branch is behind 'origin/main' by 3 commits, and can be fast-forwarded. - Task 2:
git log --oneline main..origin/mainlistsTidy up inventory,Document NAS service,Add NAS. - Task 3:
Tidy up inventorychanges the DNS address from192.168.1.10to192.168.1.100. - Task 4 is a fast-forward.
- Task 5:
git log --onelinestarts withRevert "Tidy up inventory", andinventory.csvcontainspi-dns,192.168.1.10,dnsand the NAS.
Solution
# 1. Download, then compare
git fetch origin
git status # behind 'origin/main' by 3 commits
# 2. What is coming?
git log --oneline main..origin/main
git diff main origin/main # all the incoming changes at once
# 3. "Tidy up" should not change an address
git show origin/main # the latest one: -...1.10 / +...1.100
# 4. Bring them in
git merge origin/main # Fast-forward
# 5. Undo only the faulty commit
git revert --no-edit HEAD # Revert "Tidy up inventory"
cat inventory.csv
git fetchfollowed by a review is the careful version ofgit pull: nothing touches your files until you decide to merge.git show origin/mainworks likegit show HEAD: a remote-tracking branch can be used wherever Git expects a commit.- Reverting after the merge keeps the history honest: the mistake and its fix are both recorded, and Alex receives the fix with the next pull.
Clean up when you are done: rm -rf ~/git-practice/review.