Remote Repositories
Before you start
You need: local commits (Article 01), and a GitHub account.
Time: about 50 minutes, at the keyboard.
Learning objective
Connect a local repository to a remote, push and pull your work, and understand why fetch and pull are different commands.
Topics
- What a remote is
- Authentication
remote add,push,clone- Tracking branches
fetchvspull- Merge and rebase on pull
- Rejected pushes
- Force pushing
What a remote is
A remote is a named URL pointing at another copy of the repository — usually on GitHub, GitLab, Azure DevOps or Bitbucket.
Every clone is a complete repository, with the full history, all branches and every commit. This is why Git is called distributed: you can commit, branch, merge, view history and revert with no network at all. The remote is a shared meeting point, not the authority your local copy depends on.
The practical consequence: network problems never block your work. Commit locally, push when the connection returns.
git remote -v
origin https://github.com/TeamSahasra/school-portal.git (fetch)
origin https://github.com/TeamSahasra/school-portal.git (push)
origin is a convention, not a keyword — the default name Git gives the remote you cloned from.
Authentication
HTTPS with a personal access token:
git remote add origin https://github.com/TeamSahasra/school-portal.git
GitHub stopped accepting account passwords for Git operations in 2021. Generate a personal access token (Settings → Developer settings → Personal access tokens) and use it as the password. Scope it to repo and give it an expiry.
git config --global credential.helper manager # Windows
git config --global credential.helper osxkeychain # macOS
Without a credential helper you are pasting the token on every push.
SSH with a key pair:
ssh-keygen -t ed25519 -C "ravi.kumar@nexcoding.in"
cat ~/.ssh/id_ed25519.pub # add this to GitHub → SSH keys
ssh -T git@github.com # verify
git remote add origin git@github.com:TeamSahasra/school-portal.git
Never share or commit the private key (id_ed25519, no .pub). Only the .pub file goes to GitHub.
SSH is worth the ten minutes of setup: no expiring tokens, no re-authentication.
Connecting and pushing
git remote add origin https://github.com/TeamSahasra/school-portal.git
git branch -M main
git push -u origin main
-M renames the current branch to main. -u sets upstream tracking, which is why later pushes can be a bare git push.
git remote -v
git remote rename origin upstream
git remote set-url origin git@github.com:TeamSahasra/school-portal.git
git remote remove origin
set-url is how you switch a repository from HTTPS to SSH without re-cloning.
Cloning
git clone https://github.com/TeamSahasra/school-portal.git
git clone <url> school-portal-fix # into a named folder
git clone -b develop <url> # a specific branch
git clone --depth 1 <url> # shallow, latest commit only
clone creates the folder, downloads the history, sets up origin and checks out the default branch. A shallow clone is faster for CI but cannot show history — do not use it for work you will commit.
Tracking branches
git branch -vv
* main a3f9c1d [origin/main] Add student search
develop 8c2e4f1 [origin/develop: ahead 2] Fix fee rounding
spike 1a9b3c5 Experiment with caching
spike has no upstream, so git push on it fails until you set one:
git push -u origin spike
git branch --set-upstream-to=origin/spike spike
origin/main is a local reference, not the remote. It records what main looked like on the remote the last time you contacted it. It goes stale, and git fetch is what updates it.
That distinction explains most confusion in this article.
fetch vs pull
git fetch origin # download; change nothing in your working directory
git pull origin main # fetch, then merge into the current branch
git pull is git fetch followed by git merge.
git fetch origin
git log HEAD..origin/main --oneline # what is coming
git diff HEAD origin/main # what will change
git merge origin/main # merge when ready
Fetch first when you have uncommitted work or a big change is expected. Pull can produce a conflict at a moment you did not choose. Fetch never touches your working directory, so you can look before deciding.
git fetch --all
git fetch --prune # delete local refs to branches deleted on the remote
--prune matters after a few months: without it, git branch -r lists dozens of origin/feature/... branches that were merged and deleted long ago.
Merge or rebase on pull
git pull # merge (default)
git pull --rebase # replay your commits on top
git pull --ff-only # fail rather than create a merge
merge:
A---B---C (origin/main)
\ \
D---M (main)
rebase:
A---B---C---D' (main)
| Merge | Rebase | |
|---|---|---|
| History | Preserves what happened | Linear |
| Merge commits | Yes | No |
| Rewrites hashes | No | Yes |
| Safe on shared branches | Yes | No |
Rebase rewrites commits. D becomes D' — a different hash. Harmless for commits only you have; destructive for commits others have pulled.
Rule: rebase your own unpushed commits, merge everything else.
git config --global pull.rebase false # merge
git config --global pull.ff only # refuse implicit merges
pull.ff only is a good default for beginners: it fails instead of creating a surprise merge, forcing you to decide.
Rejected pushes
! [rejected] main -> main (fetch first)
error: failed to push some refs
hint: Updates were rejected because the remote contains work that you do
hint: not have locally.
Someone pushed since you last pulled.
git pull # or --rebase
# resolve any conflict
git push
This is Git protecting you. Accepting the push would discard their commits. The rejection is correct behaviour, not an error to work around.
The wrong fix is git push --force, which does exactly the discarding Git just prevented.
Force pushing
git push --force # dangerous
git push --force-with-lease # safer
--force overwrites the remote branch with yours, deleting any commit you do not have. If a colleague pushed an hour ago and you force push, their work is gone from the branch.
--force-with-lease refuses if the remote moved since your last fetch. It force pushes only when you are overwriting exactly what you thought you were. If you must force, use this.
Legitimate uses are narrow:
- Your own feature branch, after an intentional rebase
- Removing a secret from a branch nobody else uses
- A branch you own, after
--amend
Never force push main, develop, or any shared branch. Most teams protect them at the server so this cannot happen.
Diagnosing
| Symptom | Cause |
|---|---|
Authentication failed | Password used instead of a token, or an expired token |
Permission denied (publickey) | SSH key not added to GitHub, or agent not running |
failed to push (fetch first) | Remote has commits you lack — pull |
no upstream branch | Branch never pushed — git push -u origin <branch> |
origin/main looks old | Not fetched — origin/main is a cached reference |
| Remote branches that no longer exist | Never pruned — git fetch --prune |
| A colleague's commits disappeared | Someone force pushed |
git remote -v
git branch -vv
git fetch --prune
ssh -T git@github.com
Errors you will hit
| Message | Cause | Fix |
|---|---|---|
Authentication failed | Used a password instead of a token | Personal access token, or SSH |
Permission denied (publickey) | SSH key not added to GitHub | Add the .pub key |
failed to push some refs (fetch first) | The remote has commits you lack | git pull, then push |
no upstream branch | Branch never pushed | git push -u origin <branch> |
origin/main looks out of date | It is a cached reference | git fetch |
| A colleague's commits disappeared | Someone force pushed | Recover from reflog; talk to the team |
A rejected push is Git protecting you. Accepting it would discard someone else's work — --force is not the fix.
Common mistakes
- Using an account password instead of a token
- Committing a private SSH key
git pullwith uncommitted work, then a conflict at a bad moment- Assuming
origin/mainis live --forceto escape a rejected push- Force pushing a shared branch
- Rebasing commits others have pulled
- Never pruning
- Shallow-cloning a repository you will commit to
- Pushing without
-uand repeating the full command forever
Practice
The course exercise is push and pull between local and remote.
- Create a GitHub repository, connect a local one, push
main. - Set up SSH keys and switch the remote with
set-url. Verify withssh -T. - Run
git branch -vvand identify which branches track a remote. - Create a branch and push without
-u. Read the error, then fix it. - Change a file on GitHub's web editor. Run
git fetch, thengit log HEAD..origin/mainandgit diff HEAD origin/mainbefore merging. - Prove
origin/mainis stale: change the file on GitHub, rungit log origin/main, then fetch and run it again. - Create a rejected push deliberately — commit on GitHub and locally, then push. Fix it by pulling.
- Do the same again and fix it with
git pull --rebase. Compare the graphs withgit log --oneline --graph --all. - Clone the same repository into a second folder and simulate two developers pushing and pulling.
- In the second clone, commit and push. In the first, commit a different change and force push. Confirm the second clone's commit is gone.
- Repeat with
--force-with-leaseand confirm it refuses. - Delete a branch on GitHub, then run
git branch -rbefore and aftergit fetch --prune. - Set
pull.ff onlyand see what happens when histories diverge.
Exercises 10 and 11 are the pair that make force pushing feel real. Do them on a throwaway repository.
You can now
- Connect a local repository to a remote
- Authenticate with a token or SSH
- Read a rejected push and respond correctly
- Say what
origin/mainis and when it updates - Use
--force-with-leaserather than--force
Review questions
- What does
git fetchdo thatgit pulldoes not? - What is
origin/main, and when does it change? - Why does Git reject a push, and what is the correct response?
- When is a force push acceptable, and which flag should you use?
Next: Branching