Working with Remotes and GitHub
Back your work up and share it. Clone versus remote add, SSH keys and tokens, upstream tracking, what origin/main really is, fetch versus pull, and the rejected push.
Back your work up and share it. Clone versus remote add, SSH keys and tokens, upstream tracking, what origin/main really is, fetch versus pull, and the rejected push.
Everything you have built so far lives in one folder, on one machine. Six months of commits, a full history you can search and undo and branch — and all of it inside a laptop that can be dropped, stolen, or wiped by a bad update.
Remotes are Git's answer. By the end of this lesson your project
is backed up on GitHub, you can push and pull without holding
your breath, and you will know exactly what origin/main is —
the one piece most people fake their way past for years.
A remote is a named URL pointing at another copy of your
repository. That is the entire definition. It is not a special
kind of repo, not a server mode, not a setting you turn on — it
is a nickname stored in your project's config so you can type
origin instead of a long URL every single time.
Think of the contacts list on your phone. "Mum" is not a phone number; it is a label you attached to one. Rename the contact and the number still works. Remotes behave the same way.
Ask any repo which remotes it knows about:
git remote -vYou get one line for fetching and one for pushing, because Git allows them to differ. Renaming, removing, and repointing are all cheap and local — none of them touch the server:
git remote rename origin upstream # change the nickname
git remote remove upstream # forget this remote
git remote set-url origin <new-url> # same name, new addressYou will arrive here from one of two directions, and they need different commands.
If the project already exists on GitHub, you clone it:
git clone https://github.com/you/weather-app.git
cd weather-appOne command does a surprising amount for you: it creates the
weather-app folder, initialises a repo inside it, downloads
the complete history (not just the latest files), adds a remote
called origin pointing at that URL, and checks out the default
branch with tracking already configured. You can start working
immediately.
If instead you already have a local repo — the one you built in earlier lessons — create an empty repository on GitHub with no README and no licence, then attach it:
Nothing has been sent yet. You have only written an address into your config.
The server will not accept commits from a stranger, so you need credentials. There are two routes, and the choice shows up in the URL shape:
With HTTPS, Git asks for a username and password — but your GitHub account password will not work. Password authentication for Git operations was removed years ago. You generate a personal access token in GitHub's settings, under Developer settings, and paste that token where the password is requested. Treat it like a password, because it is one.
With SSH, you prove identity with a key pair instead. Create one, hand the public half to GitHub, and test it:
Paste that public key into GitHub under Settings → SSH and GPG
keys → New SSH key. Never paste the other file, id_ed25519
with no extension — that is the private half and it stays on
your machine. Then check the handshake:
A greeting with your username means it worked, even though it also says shell access is not provided. That part is normal.
Already cloned over HTTPS? You do not need to re-clone — repoint the same remote:
With a remote set and credentials working, send your history up:
Read it as three parts: push, to the remote called origin, the
branch called main. The -u flag (short for --set-upstream)
adds one extra thing — it records that your local main
tracks origin/main.
That recorded link is why later pushes can be bare:
It also teaches git status to tell you things like "Your
branch is ahead of 'origin/main' by 2 commits." Without an
upstream, Git has nothing to compare you against. You only need
-u once per branch.
Here is the idea that makes everything else stop being
mysterious. origin/main is a branch on your own computer. It
is not the server. It is your local record of where main was
on the server the last time you talked to it.
It is a photograph of a noticeboard. The photo is accurate for the moment you took it, and it does not update itself when someone pins a new notice. Only walking back to the board — a fetch, pull, or push — gives you a new photo.
You cannot commit to origin/main and you never check it out
directly. It moves only when Git communicates with the server.
That is precisely what makes it useful: it is a stable marker of
"what I last knew," so Git can tell you how far you have drifted.
Three different things are called main here, and treating them
as one is where nearly all remote confusion comes from. When a
command surprises you, ask which of these it actually touched.
Yours. You commit to it.
The branch you are standing on. Moves every time you commit.
A note about the server, stored locally.
Moves only when you fetch, pull, or push. You cannot commit to it and you never check it out.
The one you cannot see from here.
Anyone on the team can move it at any moment, and your copy will not notice until you go and ask.
Two commands bring news down from the server, and the difference between them is worth learning properly.
git fetch downloads new commits and updates your origin/*
branches. It changes nothing else — not your branch, not your
files, not your staged changes:
git pull is git fetch followed immediately by a merge into
the branch you are standing on. It is convenient, and it is also
how people get surprised by a merge they did not expect in the
middle of unrelated work.
The habit worth building is fetch, look, then integrate. Ten extra seconds tells you whether you are absorbing one tidy commit or forty from a rewritten branch.
When you do pull, git pull --rebase is a common alternative.
Instead of creating a merge commit, it replays your local
commits on top of what it just downloaded, giving a straight
line of history. It is a reasonable default for a solo branch,
but rebase has real edge cases, so treat this as a recipe for
now — the intermediate course covers what it actually does.
Sooner or later a push comes back like this:
Nothing is broken. Git is telling you that the server's main
has commits yours does not, so accepting your push would mean
dropping theirs. Git refuses to lose commits on its own.
The fix is to bring their work in and try again:
Feature branches work exactly like main, they just need the
same one-time introduction:
Coming the other way, if a teammate pushed a branch, fetch and then switch to it by name:
Git sees no local branch by that name, finds exactly one remote
that has it, and creates a local branch tracking it. This is one
reason to prefer git switch over the older git checkout —
switch does only branch work, so its behaviour is predictable.
Merged and finished? Delete both copies:
Branches other people delete leave stale entries in your
origin/* list until you clear them:
Put together, a normal working day has a shape. Five steps, and you will run them so often they stop being steps.
Start from what is current
git switch main && git pull. Branching from a stale main
is how you end up merging things you never touched.
Branch for the work
git switch -c feature/signup. Costs 40 bytes, so do it for
everything.
Edit and commit, as often as you like
All local. Nothing leaves your machine yet, so commit in small pieces without worrying about how it looks.
Publish the branch
git push -u origin feature/signup. The -u is needed once;
after that plain git push knows where to go.
Open a pull request and let someone read it
A page where teammates comment and approve before the branch
is merged into main.
Worth knowing: pull requests are not a Git feature. Git has no idea they exist. They are something GitHub built on top, which is why the details differ between hosts. GitLab calls them merge requests; Bitbucket, Azure DevOps and the rest have their own flavours, but the Git half — remotes, push, fetch, pull — is identical everywhere.
Every command from this lesson, in one place:
That is the beginner course complete. You can create history, read it, shape it, undo it, branch it, and now share it — which covers the great majority of what daily Git asks of you.
The follow-on course, Git in Practice, picks up where this
stops: merging and conflicts in depth, git rebase and when
rewriting history is appropriate, pull requests and review as a
real workflow, tags and releases, and git reflog — the safety
net that can recover work you thought was gone for good.
Before that, spend a week with what you have. Push an existing
project up, break the push on purpose by editing a file in
GitHub's web editor and committing there, then fix the rejection
from your terminal. Watching origin/main move when you fetch,
and not move when you do not, is the moment the whole model
clicks into place.
HTTPS https://github.com/you/weather-app.git
SSH git@github.com:you/weather-app.git local main A──B──C──D──E your work; D and E are yours
origin/main A──B──C the photo you took last time
really on the A──B──C──F a colleague pushed F an hour
server ago; you have not looked yet ! [rejected] main -> main (fetch first)
error: failed to push some refs to 'github.com:you/weather-app.git'
hint: Updates were rejected because the remote contains work that
hint: you do not have locally. This is usually caused by another
hint: repository pushing to the same ref.git remote add origin https://github.com/you/weather-app.gitssh-keygen -t ed25519 -C "you@example.com" # press Enter for
# the default path
cat ~/.ssh/id_ed25519.pub # copy this linessh -T git@github.comgit remote set-url origin git@github.com:you/weather-app.gitgit push -u origin maingit push # Git already knows: origin, main
git pull # same link, other directiongit fetch origin
git log --oneline main..origin/main # what arrived that I lack
git status # "behind by 1 commit"git fetch origin # see what actually landed
git pull # merge it into your branch
# resolve conflicts if Git asks
git push # now a clean fast-forwardgit switch -c feature/dark-mode
git push -u origin feature/dark-modegit fetch origin
git switch feature/dark-modegit switch main
git branch -d feature/dark-mode # local
git push origin --delete feature/dark-mode # on the servergit fetch --prune # fetch and tidy in one step
git remote prune origin # tidy onlygit clone <url> # copy a repo + set up origin
git remote -v # list remotes and their URLs
git remote add origin <url> # attach a remote to a local repo
git remote rename <old> <new> # change a remote's nickname
git remote remove <name> # forget a remote
git remote set-url origin <url> # repoint (e.g. HTTPS -> SSH)
ssh-keygen -t ed25519 -C "you@example.com" # make a key pair
cat ~/.ssh/id_ed25519.pub # public half, paste to GitHub
ssh -T git@github.com # test the SSH connection
git push -u origin main # push and set upstream
git push # push, upstream already known
git fetch origin # update origin/* only, safely
git log --oneline main..origin/main # what came in
git pull # fetch + merge
git pull --rebase # fetch + replay your commits
git push -u origin feature/x # publish a feature branch
git switch feature/x # track a teammate's branch
git push origin --delete <name> # delete the branch on GitHub
git fetch --prune # drop stale origin/* entries