Team training · Block 4 of 6

Remotes

Where other people's copies come in

4.1

What a remote is

Every clone is a complete repository

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

Which is why git log works on a plane.

origin is just a name

$ git remote -v
origin  git@github.com:team/pricing.git (fetch)
origin  git@github.com:team/pricing.git (push)

A remote is a nickname for a location. origin is the default name clone gives it — nothing more.

And that location can be almost anything git knows how to reach:

Looks likeIs
/srv/git/pricing.gita directory on this machine
https://github.com/team/pricing.gitan HTTPS URL
git@github.com:team/pricing.gitSSH, scp-style
ssh://git@host:22/team/pricing.gitSSH, full form

Same git underneath — only the transport differs.

4.2

Remote-tracking branches

Three pointers, not two

Your branch Your note about the server The actual server main origin/main main (on origin) moves when you commit moves when you fetch moves when anyone pushes both of these are on your machine

Watch the note go stale, then catch up

origin — the server main main fetch your repo main main origin/main origin/main

1 · Alice pushes. The server moves to the new commit. Your note still points at the old one — git told you nothing.

2 · You fetch. origin/main catches up; main and your files stay put. Only now can git say “behind by 1”.

3 · You integrate. git pull (fetch + merge/rebase) finally moves main to catch up.

kata · katas/remotes — Tasks 1–3

pull is fetch plus a merge

fetch

Updates your notes about the server. Always safe.

pull

Fetch, then merge or rebase that into your branch. Moves your work.

$ git pull                  # fetch + merge (default)
$ git pull --rebase         # fetch + rebase

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/discount
branch '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.

Both developers commit

alice — pushed bob — local only where both started

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 refs

hint: Updates were rejected because the remote contains work
hint: that you do not have locally.
bob$ git pull --rebase
bob$ git push origin main
kata · katas/remotes — Task 4
4.4

The shared history rule

What force push actually does

--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.

Where we got to

ThingWhat it actually is
originA nickname for a URL
origin/mainYour local note about the server
fetchRefresh the note. Always safe
pullFetch, then integrate. Moves your work
pushMove the server's label forward
Rejected pushYour commit isn't a descendant
--force-with-leaseOverride, but only from a known state