Not a checkout. Not a working copy of a central server.
When you clone, you get the entire commit graph — every commit, every branch, all of history — plus a set of extra pointers recording what the server looked like when you last checked.
$ git clone git@github.com:team/pricing.git
Cloning into 'pricing'...remote: Enumerating objects: 1284, done.Receiving objects: 100% (1284/1284), 412 KiB | 6 MiB/s, done.$ cd pricing && git log --oneline | wc -l
327 # all 327 commits are already on your disk
If you're unsure what state you're in, fetch first and look. Then decide.
Worst case for a blind pull: you have a stray commit on main that shouldn't be there. Pull silently makes a merge commit to reconcile it — now main and origin/main have diverged further, and you're carrying a merge you never meant to create. fetch first and you'd have seen it coming.
4.3
push, and why it gets rejected
Getting your branch onto the server
$ git switch -c feature/discount
$ git push -u origin feature/discount # -u = --set-upstream * [new branch] feature/discount -> feature/discountbranch 'feature/discount' set up to track 'origin/feature/discount'.
-u does two things, once: it creates the branch on the server, and it records origin/feature/discount as this branch's upstream. After that, bare git push and git pull already know where to go.
git branch -vv shows the link; git status can now say "ahead 2, behind 1". When the PR is merged, clean up the server side too: git push origin --delete feature/discount.
Bob's commit is not a descendant of where the server now is.
A normal push asks the server to move its label forward. Forward means "to a commit that has the current one as an ancestor". Bob's doesn't.
The rejection, decoded
bob$ git push origin main
! [rejected] main -> main (fetch first)error: failed to push some refshint: Updates were rejected because the remote contains workhint: that you do not have locally.
--force tells the server to move its label to your commit regardless of ancestry.
That is precisely the operation git refused to perform two slides ago.
Alice's commit becomes unreferenced on the server. She still has it locally — until she cleans up, or until someone clones fresh and it's simply not there.
Why rewriting shared history hurts
Every rewrite in Block 3 — amend, rebase, interactive rebase — produces new hashes.
If a colleague holds the originals, your histories have genuinely diverged. Their next pull produces duplicated commits and conflicts that look impossible.
Commandment 04
Never rewrite history someone else has pulled. Once it's shared, it's not yours to edit.
The lease
$ git push --force-with-lease origin my-branch
Move the label to my commit — but only if the server is still where I last saw it.
If anyone has pushed since your last fetch, the lease fails and the push is rejected. You find out instead of overwriting them.
Commandment 05
Force push only your own branch, and only with --force-with-lease.
Keeping junk out — .gitignore
$ cat .gitignore
node_modules/dist/*.env.DS_Store
A tracked list of path patterns git treats as invisible — build output, dependencies, local secrets, editor cruft. Matching files stop appearing as untracked, so a reflexive git add -A can't sweep them in.
.gitignore only affects untracked files. Something already committed stays tracked no matter what you add here — you have to stop tracking it first: git rm --cached secrets.env, then commit.
One more thing that leaves your machine
Commandment 08
Never commit secrets, build output, or dependencies.
Once a secret is pushed, treat it as compromised. Removing it from history means rewriting every commit since — and everyone re-cloning.
Rotate the credential first. Cleaning history is damage limitation, not a fix.