Remote repositories: a Git & GitHub guide for developers
Most of us work with remote repositories every single day, yet many developers never fully grasp what's happening behind the scenes when they push or pull. Understanding the mechanics of fetch, pull, and push—and how tracking branches fit into the picture—is the difference between confident deployment workflows and mysterious merge conflicts at 2 AM. In this guide, we'll break down remote repositories through the lens of practical production scenarios you'll actually encounter.
Why remote repositories matter
A remote repository is a version of your project stored on a server (GitHub, GitLab, Bitbucket, or your own infrastructure). It serves as the single source of truth for your team. Your local repository is a complete copy of the project history, but your teammates' changes live remotely. To synchronize, you need operations that safely move commits between your machine and the server. This is where fetch, pull, and push come in.
The key insight: these three operations are distinct. Fetch downloads. Pull downloads and merges. Push uploads. Conflating them causes real problems in team environments.
Fetch — Download remote changes without touching your branch
Fetch is the safest of the three operations. It downloads commits from the remote repository and updates your remote-tracking branches, but it does not modify your working directory or current branch. This makes it ideal for checking what your teammates have done without risking your local work.
Consider this scenario: you're on a feature branch called feature/checkout-redesign. Your teammate pushed updates to the main branch an hour ago, and you want to see what changed before deciding whether to rebase. You'd run:
git fetch originThis downloads all new commits from the remote and updates your local representation of origin/main, origin/develop, and any other branches. Your current branch remains exactly where it was. You can now inspect what's on origin/main safely:
git log origin/main --oneline -5Or compare it to your feature branch:
git diff feature/checkout-redesign origin/mainFetch is especially useful in CI/CD pipelines and before rebase operations. Many teams run git fetch as part of their pre-deployment checks to ensure they're aware of all remote changes.
You can also fetch from specific remotes or branches:
git fetch origin mainThis fetches only the main branch from the origin remote, which is faster if you're on a large monorepo and only care about one branch's changes.
Pull — Fetch plus merge or rebase
Pull is a convenience command that combines fetch and then either a merge or rebase. It downloads remote changes and immediately integrates them into your current branch. This is where things get tricky, because the integration step can create merge commits or rewrite history depending on your configuration.
The default behavior is merge. If you run git pull on a branch that has diverged from its remote tracking branch, Git will create a merge commit. Here's a real example from an e-commerce team:
You've been working on feature/payment-retry locally. Meanwhile, a teammate merged their changes to main and you want the latest. You'd normally run:
git pull origin mainGit will fetch the remote main and then merge it into your current branch. If there are no conflicts, you'll get a new merge commit. If there are conflicts, Git pauses and asks you to resolve them.
Many production teams prefer rebase over merge for pull operations, because it keeps history linear and avoids the "merge commit clutter." To use rebase instead:
git pull --rebase origin mainOr globally configure your repository:
git config pull.rebase trueWith rebase enabled, git pull will replay your local commits on top of the remote branch, resulting in a cleaner history with fewer merge commits. This is the standard in teams we've worked with across ride-hailing and SaaS platforms.
There's also git pull --rebase=interactive if you want to edit or squash commits during the pull, though that's usually reserved for detailed history cleanup.
The critical distinction: fetch is read-only; pull modifies your history. Always fetch first if you're uncertain.
Push — Upload your commits
Push sends your local commits to the remote repository. This is how your team accesses your work. Push is where many conflicts surface, because Git won't let you push if your local branch is behind the remote.
Simple case: you've made three commits on feature/auth-flow and want to share them:
git push origin feature/auth-flowGit uploads your commits to the remote and updates origin/feature/auth-flow. Your teammates can now see your work.
Common issue: the remote has commits you don't have locally. For example, a teammate pushed while you were offline. Git rejects the push:
error: failed to push some refs to 'origin'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart.You need to fetch and merge (or rebase) those remote commits first:
git fetch origin
git rebase origin/feature/auth-flow
# or: git merge origin/feature/auth-flow
git push origin feature/auth-flowPush is idempotent if no one else has committed. Push again and nothing happens (Git sees your commits are already there). Push is destructive if you force it: git push --force rewrites remote history and can erase teammates' work. Force push is sometimes necessary in production (e.g., squashing commits before merge), but it requires coordination and strong team practices. Most platforms now support "force-with-lease" as a safer alternative:
git push --force-with-lease origin feature/auth-flowThis only succeeds if no one else has pushed since your last fetch, preventing accidental history obliteration.
Tracking branches — git push -u origin feature sets upstream once
Tracking branches link your local branch to a remote branch. Once linked, you can push and pull without specifying the remote and branch name each time. This is set up with the -u flag (short for --set-upstream).
First push of a feature branch:
git push -u origin feature/recommendation-engineThe -u flag tells Git: "Push my commits and remember that feature/recommendation-engine tracks origin/feature/recommendation-engine." After this, you can simply run:
git push
git pullGit knows where to send your commits and fetch from. This saves dozens of keystrokes and mental effort over a long development session.
You can view your tracking setup:
git branch -vvOutput might look like:
develop abc1234 [origin/develop] Latest deployment
* feature/checkout def5678 [origin/feature/checkout] Refactor payment UIThe text in brackets shows which remote branch is tracked. If a branch has no upstream, it shows nothing.
To change the tracked branch later (rare but sometimes necessary in reorg scenarios):
git branch -u origin/mainThis sets the current branch to track origin/main.
Teams benefit from consistent tracking because it reduces the surface for mistakes. When everyone pushes with -u on their first push, pull requests and code reviews become predictable. The remote branch exists exactly when you need it.
Common mistakes
Mistake 1: Pulling without understanding merge vs. rebase. Many developers pull and end up with "accidental" merge commits that clutter history. Set git config pull.rebase true once, and you'll get cleaner history by default. Communicate this preference with your team so everyone's pull behavior is aligned.
Mistake 2: Pushing before pulling. A developer commits locally, tries to push, gets rejected because the remote has new commits, and panics. Always pull (or fetch and rebase) before your first push of the day. Make it a habit.
Mistake 3: Using git push --force carelessly. Force push can destroy teammates' commits if you're not careful. Use --force-with-lease instead, or discuss with your team before force pushing anything on shared branches like main.
Mistake 4: Forgetting to use -u on first push. You push a new branch without -u, then next time you try to pull, Git doesn't know which remote branch to pull from and asks for clarification. Using -u the first time prevents this entirely.
Mistake 5: Fetching and then immediately merging everything. Fetch is meant to be safe and informational. Don't automatically merge everything you fetch. Check what changed, test locally if needed, then decide on merge or rebase.
Key takeaways
- Fetch downloads remote commits and updates remote-tracking branches without touching your working directory. Use it to inspect changes safely.
- Pull is fetch plus merge (or rebase if configured). It integrates remote changes into your current branch. Set
pull.rebase trueto avoid merge commits by default. - Push uploads your local commits to the remote. It fails if your local branch is behind; always pull or rebase first.
- Tracking branches link local and remote branches. Use
git push -uon first push to set up tracking automatically, thenpushandpullwork without specifying the branch. - Understand the difference between these three operations and your workflow becomes more deliberate and less error-prone. Fetch to inspect. Pull to integrate. Push to share. Set upstream to reduce friction.
- Configure your team's preferences for merge vs. rebase pull behavior early. Consistency prevents surprises and makes history readable.






Comments
Loading comments…