← back to the section

Locally, Git is self-sufficient, but the team synchronizes through a remote repository — a shared copy of the project on a server (GitHub, GitLab, Bitbucket). Three commands — push, pull, fetch — are all the traffic between you and the server. This is also where the two biggest fears live: "push rejected" and "I overwrote someone's work". Let's break it down so no fear remains.

server (origin) origin/main — your copy your branch main yours teammate copy is stale git push → ! [rejected]the server has a committhat you do not have git fetchonly the copy is updated,your files stay untouched git pulltheir commit arrivesin your branch merge git push — acceptedyour branch now growsout of the server tip

A teammate pushed a commit: the server has it, your copy of origin/main does not — Git never goes to the network on its own, so the copy goes stale. That is why the push is rejected: you are asking the server to replace its history with yours, and yours has no such commit. fetch refreshes only the copy and leaves your files alone; pull also brings that commit into your branch — and now the push goes through.

origin and tracking branches

On git clone, the server's address is saved under the name origin — it is just an alias for the URL (there can be several servers, but usually there is one). A local branch tracks the server branch of the same name: main knows about origin/main, and git status reports the divergence: "your branch is behind by 2 commits".

An important detail: origin/main is a local copy of the server's state, taken at the last synchronization. Between synchronizations it goes stale — Git does not go to the network on its own.

git push -u origin fix-cart   # first push of a new branch: -u links it to the server branch
git push                      # after that, just push

fetch and pull: look or take

  • git fetch — download what is new from the server without touching your files: only the origin/* copies are updated. After that you can calmly look at what your colleagues brought: git log main..origin/main.
  • git pull = fetch + merging the tracked branch (for main that is origin/main) into yours. The files are updated immediately.

Pull is the everyday routine, fetch is the careful option: for when you want to see others' changes first and decide afterwards. A useful setting, git pull --rebase (or pull.rebase=true in the config), replays your local commits on top of the fresh server ones instead of creating small "Merge branch main" merge commits — the history is noticeably cleaner; why this works is covered in the rebase article.

"Push rejected": what is happening and what to do

! [rejected]  main -> main (fetch first)

This is not breakage but protection: the server has commits that you don't (a colleague pushed first). Git refuses to overwrite them with your version of history. The standard remedy:

git pull          # take the others' work and combine (a conflict is possible — resolved as usual)
git push          # now it goes through

What you should not do is git push --force to a shared branch: it overwrites the server's history with yours, and other people's commits disappear. Force-push is legitimate in one case: your personal branch whose history you deliberately rewrote before review (and preferably as --force-with-lease — it refuses to overwrite what you haven't seen). On main/develop, force-push is usually forbidden by server settings — and rightly so.

The daily cycle

A typical day with a remote repository:

git switch main && git pull        # morning: a fresh main
git switch -c feature-export      # a branch for the task
# ...work, commits...
git push -u origin feature-export # send the branch
# then — pull request, review, and merging on the server
git switch main && git pull        # after the merge: update
git branch -d feature-export       # tidy up the branch

The single habit that makes all of this smooth: start from a fresh main and push your branch early, without hoarding a week of local commits. An early push is also a backup: a burned-out laptop does not take your work with it.

In short

  • origin is an alias for the server; origin/main is a local snapshot of the server's state that goes stale between synchronizations.
  • fetch downloads without touching files, pull = fetch + merge. pull --rebase keeps history clean.
  • "Push rejected" = the server has something new: pull, then push. It is protection, not an error.
  • Force-push — only to your own branch, and preferably --force-with-lease; to shared branches — never.
  • The rhythm: from a fresh main, a branch per task, an early push.