Remotes: push, pull & fetch
So far all of Priya's work lives on her laptop. A remote is another copy of the same repository, usually on GitHub, that she can send commits to and receive commits from. Remotes are how a team shares work, and how your project survives if your laptop is stolen on the train to Lucknow.
This lesson covers the commands for working with remotes: adding one, pushing, fetching, pulling, tracking branches, and what to do when Git rejects your push.
Adding a remote
When you clone, Git adds the remote origin for you. For a project you started with git init, you add it yourself after creating an empty repository on GitHub (the next lesson shows how). The name origin is only a convention for "the main remote":
git remote add origin git@github.com:priya-sharma/sweet-shop.git
git remote -vorigin git@github.com:priya-sharma/sweet-shop.git (fetch) origin git@github.com:priya-sharma/sweet-shop.git (push)
Other remote commands: git remote rename origin github, git remote remove old-server, and git remote set-url origin <new-url> to switch between HTTPS and SSH addresses.
Pushing for the first time
git push uploads your commits to the remote. The first time, add -u (--set-upstream) so Git remembers that your local main belongs with origin's main:
git push -u origin mainEnumerating objects: 89, done. Counting objects: 100% (89/89), done. Delta compression using up to 8 threads Compressing objects: 100% (79/79), done. Writing objects: 100% (89/89), 8.83 KiB | 30.00 KiB/s, done. Total 89 (delta 34), reused 0 (delta 0), pack-reused 0 (from 0) To github.com:priya-sharma/sweet-shop.git * [new branch] main -> main branch 'main' set up to track 'origin/main'.
Git packed 89 objects (all the commits, folders and file versions) and sent them. The last line is the important one: main now tracks origin/main. Its upstream is set, so from now on a plain git push or git pull on this branch knows where to go.
git branch -vv shows each branch's upstream in square brackets:
git branch -vv* main c378c08 [origin/main] Add sweet name field
Remote-tracking branches
After a push or fetch, your repository contains remote-tracking branches such as origin/main. They are read-only bookmarks that record where the branches on the remote were the last time you talked to it. They do not update by themselves; only fetch, pull and push update them. git branch -a lists them next to your local branches.
Fetching: see what others did
Kabir cloned the repository, committed "Announce free delivery" and pushed. Priya runs git fetch, which downloads new commits and updates origin/main, but does not change her own main or her files:
git fetch
git statusFrom github.com:priya-sharma/sweet-shop c378c08..b8b1b21 main -> origin/main On branch main Your branch is behind 'origin/main' by 1 commit, and can be fast-forwarded. (use "git pull" to update your local branch) nothing to commit, working tree clean
Fetching is always safe. Before bringing the changes in, Priya can review them. main..origin/main means "commits in origin/main that are not in main":
git log --oneline main..origin/mainb8b1b21 (origin/main, origin/HEAD) Announce free delivery
Pulling: fetch and integrate
git pull is git fetch followed by integrating origin/main into your main. Here her branch is simply behind, so it fast-forwards:
git pullUpdating c378c08..b8b1b21 Fast-forward index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
When your push is rejected
Now both of them work at once. Kabir pushes a team page; Priya, unaware, commits a link colour and tries to push:
git pushTo github.com:priya-sharma/sweet-shop.git ! [rejected] main -> main (fetch first) error: failed to push some refs to 'github.com:priya-sharma/sweet-shop.git' hint: Updates were rejected because the remote contains work that you do not hint: have locally. This is usually caused by another repository pushing to hint: the same ref. If you want to integrate the remote changes, use hint: 'git pull' before pushing again. hint: See the 'Note about fast-forwards' in 'git push --help' for details.
Git refuses because accepting her push would throw away Kabir's commit. This protects everyone. She must first combine his work with hers. But git pull now stops too, because the branches have diverged and Git wants her to choose how to combine them:
hint: You have divergent branches and need to specify how to reconcile them. hint: You can do so by running one of the following commands sometime before hint: your next pull: hint: hint: git config pull.rebase false # merge hint: git config pull.rebase true # rebase hint: git config pull.ff only # fast-forward only hint: fatal: Need to specify how to reconcile divergent branches.
pull.rebase false creates a merge commit; pull.rebase true replays her local commit on top of Kabir's (see Rebasing); pull.ff only refuses unless a fast-forward is possible. Set one globally and the question never comes back. Priya rebases this time, then pushes:
git pull --rebase
git pushSuccessfully rebased and updated refs/heads/main. To github.com:priya-sharma/sweet-shop.git 64bc4ed..78e6efb main -> main
Pushing and deleting other branches
Push a new branch the same way as main, with -u the first time. Delete a branch on the remote with --delete:
git push -u origin reviews
git push origin --delete reviewsTo github.com:priya-sharma/sweet-shop.git * [new branch] reviews -> reviews branch 'reviews' set up to track 'origin/reviews'. To github.com:priya-sharma/sweet-shop.git - [deleted] reviews
Branches deleted on the remote by others stay in your list of origin/... branches until you prune them: git fetch --prune, or set fetch.prune to true once.
Force pushing safely
After you amend or rebase a branch you already pushed, its history no longer matches the remote's, and a normal push is rejected as "non-fast-forward". If the branch is yours alone, you may replace the remote copy. Always use --force-with-lease:
git push --force-with-leaseTo github.com:priya-sharma/sweet-shop.git + c4f0c1a...3248043 search-box -> search-box (forced update)
--force-with-lease overwrites only if the remote branch is still where you last saw it. If someone pushed in the meantime, it refuses instead of silently deleting their work, which plain --force would do.
Common mistakes
- Forgetting
-uon the first push. Later, plaingit pushcomplains that the branch has no upstream. Rungit push -u origin <branch>once. - Thinking
git fetchchanged your files. It only updatesorigin/...branches. Pull or merge to bring changes in. - Pulling with uncommitted changes. Commit or stash first so a merge problem does not mix with half-done work.
- Fixing a rejected push with
--force. That deletes your teammate's commits. Pull, then push.
Key points
- A remote is another copy of the repository;
originis the usual name for the main one. git push -u origin mainpublishes a branch and sets its upstream; afterwards plaingit pushworks.git fetchdownloads and updatesorigin/...without touching your files;git pullfetches and integrates.- A rejected push means the remote has commits you lack: pull (merge or rebase), then push.
- Use
git push --force-with-leaseonly on your own branches, never on shared ones.
Exercise
Simulate a team without GitHub. Create a bare repository with git init --bare team.git, clone it twice into folders aarav and isha, and set a different user.name in each. Commit and push from aarav, then commit (without pulling) and push from isha. Read the rejection, fix it with a rebasing pull, push, and finally pull in aarav.
Show solution
git init --bare team.git
git clone team.git aarav
git clone team.git isha
cd aarav
git config set user.name "Aarav Mehta"
git commit --allow-empty -m "Aarav: start project"
git push -u origin main
cd ../isha
git config set user.name "Isha Nair"
git commit --allow-empty -m "Isha: add notes"
git push -u origin main
git pull --rebase origin main
git push -u origin main
cd ../aarav
git pullA bare repository has no working tree, just the history, which is exactly what a server like GitHub stores. Both clones warn "You appear to have cloned an empty repository", which is expected. --allow-empty makes a commit without changing any file, handy for tests like this. Isha's first push is rejected because Aarav pushed first; after git pull --rebase her commit sits on top of his, and her push succeeds.
Quick Quiz
1. What does -u do in git push -u origin main?
With an upstream set, plain git push and git pull know which remote branch to use.
2. How is git fetch different from git pull?
Fetch is always safe because it never changes your branches or files.
3. Your push is rejected with "fetch first". What is the right fix?
The remote has commits you do not have; integrate them before pushing.
4. Why is --force-with-lease safer than --force?
It protects teammates' commits that you have not seen yet.
Learn Git with a mentor
Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
