Checkpoint Lab — Remotes, Refspecs, Fetch, Pull, Push, SSH, HTTPS, and Protocol Behavior
Checkpoint remote mechanics with one bare repository and two clones: stale observations, non-fast-forward rejection, safe integration, dry-run push, explicit-lease rewrite, and stale-lease protection.
Learning objectives
- Predict and verify how live remote refs and local remote-tracking refs diverge and resynchronize.
- Trigger a non-fast-forward push rejection and repair it through fetch, graph inspection, merge, and normal push.
- Verify that fetch and local integration move different refs at different times.
- Perform one controlled rewritten-history push using an exact expected-OID lease.
- Prove the same lease rejects after a second clone advances the remote branch.
1. Checkpoint scenario — one bare remote, two independent clones
You will create remote.git, then clones named
alice and bob. The lab has three phases:
observe stale remote-tracking state, trigger and safely repair a
non-fast-forward rejection, then demonstrate both the success and
protective rejection of an explicit force-with-lease.
2. Write predictions before commands change refs
-
After Bob pushes while Alice does nothing, will Alice's
origin/trunkmove automatically? - When Alice and Bob both commit from the same old base, will Alice's normal push be accepted after Bob pushes first?
-
After
git fetch origin, which should change first in Alice:trunkororigin/trunk? - If Alice supplies an explicit lease OID and Bob moves the branch afterward, should Alice's lease-protected force push succeed?
3. Setup: seed the bare remote and make two clones
Git Bash, Bash, or zsh
mkdir git-remote-checkpoint
cd git-remote-checkpoint
git init --bare --initial-branch=trunk remote.git
git init -b trunk seed
printf "baseline
" > seed/shared.txt
git -C seed config user.name "Seed User"
git -C seed config user.email "seed@example.invalid"
git -C seed add shared.txt
git -C seed commit -m "Create shared baseline"
git -C seed remote add origin ../remote.git
git -C seed push -u origin trunk
git clone remote.git alice
git clone remote.git bob
git -C alice config user.name "Alice"
git -C alice config user.email "alice@example.invalid"
git -C bob config user.name "Bob"
git -C bob config user.email "bob@example.invalid"
PowerShell file creation
New-Item -ItemType Directory git-remote-checkpoint | Out-Null
Set-Location git-remote-checkpoint
git init --bare --initial-branch=trunk remote.git
git init -b trunk seed
Set-Content seed/shared.txt 'baseline'
# Continue with the same git -C commands shown above.
4. Preflight — prove URLs, upstreams, live remote HEAD, and local refs
git -C alice status --short --branch
git -C alice remote -v
git -C alice branch -vv
git -C alice config --get-regexp '^(remote\.origin|branch\.trunk)'
git -C alice ls-remote --symref origin HEAD refs/heads/trunk
git -C alice rev-parse trunk
git -C alice rev-parse origin/trunk
At this point Alice's local branch and remote-tracking ref should
agree with the live remote trunk.
5. Bob pushes independent work; Alice's tracking ref becomes stale
Git Bash / Bash / zsh
printf "bob change
" > bob/bob.txt
git -C bob add bob.txt
git -C bob commit -m "Add Bob operational note"
git -C bob push origin trunk
git -C alice rev-parse origin/trunk
git -C alice ls-remote origin refs/heads/trunk
PowerShell file creation
Set-Content bob/bob.txt 'bob change'
git -C bob add bob.txt
git -C bob commit -m "Add Bob operational note"
git -C bob push origin trunk
Verify prediction 1: the live remote OID changed,
but Alice's origin/trunk still records what Alice last
knew.
6. Alice creates a different commit from the old base
Git Bash / Bash / zsh
printf "alice change
" > alice/alice.txt
git -C alice add alice.txt
git -C alice commit -m "Add Alice deployment note"
git -C alice status --short --branch
git -C alice log --graph --decorate --oneline --all -6
Alice has not fetched Bob's commit, so her local graph does not yet include the remote branch's new tip.
7. Trigger the non-fast-forward rejection intentionally
git -C alice push origin trunk
Verify prediction 2: the push is rejected because
remote trunk contains Bob's commit while Alice proposes
a different descendant of the old base. Do not add
--force.
8. Fetch, inspect divergence, merge safely, then push
git -C alice fetch origin
git -C alice rev-list --left-right --count trunk...origin/trunk
git -C alice log --graph --decorate --oneline --all -8
git -C alice diff trunk...origin/trunk
git -C alice merge --no-edit origin/trunk
git -C alice log --graph --decorate --oneline --all -8
git -C alice push --dry-run origin trunk
git -C alice push origin trunk
Verify prediction 3: fetch moved
origin/trunk first; the merge then moved local
trunk to a new merge commit because the branches had
diverged; the final push advanced the remote normally.
9. Bob fetches the integrated remote without moving his local branch
git -C bob rev-parse trunk
git -C bob rev-parse origin/trunk
git -C bob fetch origin
git -C bob rev-parse trunk
git -C bob rev-parse origin/trunk
git -C bob log --graph --decorate --oneline --all -8
git -C bob merge --ff-only origin/trunk
Again, fetch updates Bob's remote-tracking ref first. Because
Alice's merge commit contains Bob's earlier commit, Bob can then
fast-forward his local trunk.
10. Controlled rewrite with a successful explicit lease
Git Bash / Bash / zsh
git -C alice switch -c rewrite-demo trunk
printf "version one
" > alice/rewrite.txt
git -C alice add rewrite.txt
git -C alice commit -m "Add lease demonstration"
git -C alice push -u origin rewrite-demo
printf "version two
" > alice/rewrite.txt
git -C alice add rewrite.txt
git -C alice commit -m "Update lease demonstration"
git -C alice push origin rewrite-demo
EXPECTED=$(git -C alice rev-parse origin/rewrite-demo)
printf "version two corrected
" > alice/rewrite.txt
git -C alice add rewrite.txt
git -C alice commit --amend -m "Correct lease demonstration"
git -C alice push --dry-run --force-with-lease=refs/heads/rewrite-demo:$EXPECTED origin HEAD:refs/heads/rewrite-demo
git -C alice push --force-with-lease=refs/heads/rewrite-demo:$EXPECTED origin HEAD:refs/heads/rewrite-demo
PowerShell lease variable
$EXPECTED = git -C alice rev-parse origin/rewrite-demo
git -C alice push --dry-run "--force-with-lease=refs/heads/rewrite-demo:$EXPECTED" origin HEAD:refs/heads/rewrite-demo
git -C alice push "--force-with-lease=refs/heads/rewrite-demo:$EXPECTED" origin HEAD:refs/heads/rewrite-demo
The force is permitted only because the remote still equals the exact OID Alice recorded. The amended commit has a new OID, so a normal push would reject the non-fast-forward replacement.
11. Prove the lease blocks Alice after Bob advances the same branch
git -C bob fetch origin
git -C bob switch -c rewrite-demo --track origin/rewrite-demo
Have Bob add bob-lease.txt, commit, and push. Alice
intentionally does not fetch. Her local
origin/rewrite-demo therefore remains the old expected
value. Then:
Git Bash / Bash / zsh
STALE_EXPECTED=$(git -C alice rev-parse origin/rewrite-demo)
git -C alice push --force-with-lease=refs/heads/rewrite-demo:$STALE_EXPECTED origin HEAD:refs/heads/rewrite-demo
PowerShell
$STALE_EXPECTED = git -C alice rev-parse origin/rewrite-demo
git -C alice push "--force-with-lease=refs/heads/rewrite-demo:$STALE_EXPECTED" origin HEAD:refs/heads/rewrite-demo
Verify prediction 4: the push is rejected because
Bob moved the remote branch after Alice's observation. A plain
--force could move the branch backward to Alice's tip
and make Bob's new commit disappear from that remote branch. The
checkpoint intentionally does not do that.
12. Refresh Alice's observation after the protected rejection
git -C alice ls-remote origin refs/heads/rewrite-demo
git -C alice rev-parse origin/rewrite-demo
git -C alice fetch origin
git -C alice rev-parse origin/rewrite-demo
git -C alice log --graph --decorate --oneline --all -12
git -C alice switch trunk
The stale remote-tracking ref now catches up to the live remote branch, preserving Bob's work.
13. Produce a compact remote-state report
git -C alice status --short --branch
git -C alice branch -vv
git -C alice remote -v
git -C alice config --get-regexp '^(remote\.origin|branch\.)'
git -C alice ls-remote --symref origin HEAD refs/heads/trunk refs/heads/rewrite-demo
git -C alice log --graph --decorate --oneline --all -12
Write beside the report which values are local refs, which are live remote observations, and which config keys determine fetch/push behavior. That annotation is part of the checkpoint.
14. Verification checklist
-
The bare remote's
HEADnamestrunk. -
Alice and Bob both began with
trunktrackingorigin/trunk. - Bob's first push changed the remote while Alice's remote-tracking ref remained stale.
- Alice's first push after divergence was rejected non-fast-forward.
- Alice fetched, inspected divergence, created a merge commit, dry-ran the push, and then pushed normally.
- Bob fetched before his local branch moved, then fast-forwarded explicitly.
- The first rewrite-demo force update used an explicit expected OID and succeeded.
- After Bob advanced rewrite-demo, Alice's stale lease rejected the overwrite.
- Plain
--forcewas never used. - No hosted account, real credential, or network server was required.
15. Cleanup
git-remote-checkpoint directory.
Git Bash / Bash / zsh
git -C alice status --short --branch
cd ..
pwd
rm -rf git-remote-checkpoint
PowerShell
git -C alice status --short --branch
Set-Location ..
Get-Location
Remove-Item -Recurse -Force git-remote-checkpoint
16. Knowledge check
Question 1. Bob pushed, but Alice's
origin/trunk did not change. Is that evidence that
Bob's push failed?
ls-remote can query live state without updating the
tracking ref.
Question 2. Why was Alice's normal push rejected after both clones committed independently?
Question 3. Why did Bob's fetch not immediately
move his local trunk?
origin/trunk observation. A
separate merge/fast-forward operation was required to move local
trunk.
Question 4. What safety property did the stale explicit lease demonstrate?
Question 5. In CI, why might git pull be less
desirable than an explicit fetch plus ref/OID selection?
17. What Chapter 07 adds to a production Git operating model
You can now reason about a remote as an independent ref/object database, distinguish live remote state from local tracking state, inspect and configure ref mappings/upstreams, choose pull behavior explicitly, validate push destinations before writes, classify SSH/HTTPS failures by trust/auth layer, and use a lease when a controlled rewrite is genuinely necessary.
18. Chapter checkpoint summary
Fetch changes local observations; integration changes your local branch/history; push asks another repository to update refs; authentication merely establishes who is communicating, while server policy decides what that principal may update. Those mechanics are the foundation for collaboration workflows in Chapter 08.
Authoritative references
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.