Git Remotes and Pull Requests Explained
Understand Git remotes, the difference between origin and upstream, fetch vs pull, the forking workflow, and how to open a pull request that is easy to review.
What you'll learn
- ✓What a Git remote is and how to add and inspect them
- ✓The conventional names "origin" and "upstream"
- ✓The difference between git fetch and git pull
- ✓How git push -u sets up branch tracking
- ✓The forking workflow on GitHub from clone to merge
- ✓Code review etiquette that makes PRs easier to ship
Prerequisites
- •Comfortable with branches — see Git Branching Basics
- •Familiar with undo tools — see Git Stash and Undoing Changes
- •An account on GitHub (or GitLab/Bitbucket)
A Git repository on your laptop is useful by itself. A Git repository connected to a remote is how teams actually ship software. This post covers the bits of Git that suddenly involve other humans: remotes, fetching, pulling, pushing, forks, and pull requests.
What a remote actually is
A remote is just a named URL pointing at another copy of the repository — usually on a server like GitHub. You can have as many as you want. Each one is a label like origin or upstream that you use in commands instead of typing the URL every time.
List the remotes of a repository:
git remote -v
# origin git@github.com:yash/codeloom.git (fetch)
# origin git@github.com:yash/codeloom.git (push)
The -v (verbose) flag shows the URLs. Each remote has a separate fetch URL and push URL — they are almost always the same.
When you git clone <url>, Git automatically adds a remote called origin pointing at the URL you cloned from. That is the only reason origin is special — it is a default name, not a Git concept.
Adding and removing remotes
git remote add upstream git@github.com:original-author/codeloom.git
git remote -v
# origin git@github.com:yash/codeloom.git (fetch)
# origin git@github.com:yash/codeloom.git (push)
# upstream git@github.com:original-author/codeloom.git (fetch)
# upstream git@github.com:original-author/codeloom.git (push)
Rename or remove:
git remote rename origin github
git remote remove upstream
origin vs upstream — the convention
Two names show up everywhere:
origin— your own copy of the repository. The remote you push to.upstream— the original project you forked from. The remote you pull updates from but generally don’t push to.
If you only ever work on your own repos, you only need origin. The moment you fork someone else’s project and want to keep up with their changes, you add upstream.
This is just convention. Git does not care what you call your remotes — but every tutorial and contributor guide uses these names, so use them.
fetch vs pull
This is the question every Git learner eventually has to answer.
git fetch — download without merging
git fetch origin
fetch downloads new commits, branches, and tags from the remote into your remote-tracking branches (origin/main, origin/feature-foo, etc). It does not touch your local branches. Your main is still where it was; origin/main may have moved.
This is safe — you can run git fetch any time without affecting your working tree.
To see what changed:
git log main..origin/main --oneline # commits on origin/main not in main
git log origin/main..main --oneline # commits on main not in origin/main
git pull — fetch and merge
git pull origin main
pull is fetch followed by an integration of the remote branch into your current local branch. By default the integration is a merge — if the histories diverged, you get a merge commit.
Many teams prefer rebasing on pull instead:
git pull --rebase origin main
This replays your local commits on top of origin/main, producing a linear history. Configure it as the default for a branch:
git config branch.main.rebase true
Or for every branch you ever create:
git config --global pull.rebase true
The mental model: fetch is safe, pull modifies your branch. When in doubt, fetch first, look at what changed, then decide whether to merge or rebase.
git push -u and tracking
The first time you push a new branch:
git switch -c feature-auth
git commit -am "Add login flow"
git push -u origin feature-auth
The -u (short for --set-upstream) does two things:
- Pushes the branch to
origin. - Records that this local branch tracks
origin/feature-auth.
From now on, you can just type:
git push # pushes to origin/feature-auth automatically
git pull # pulls from origin/feature-auth automatically
Without -u, every push needs the explicit remote and branch name. Always pass -u on the first push of a branch.
To see what each local branch is tracking:
git branch -vv
# * feature-auth c3d4e5f [origin/feature-auth] Add login flow
# main b2c3d4e [origin/main] Initial commit
The bracketed part is the tracked remote branch.
Try it yourself. In a repo with a remote, run git fetch origin and then git log --oneline --all --graph. Look for branches with origin/ prefixes — those are remote-tracking branches. Compare with git branch -vv to see which local branches track which.
The forking workflow
The pattern used by most open-source projects, and many companies:
- Fork the project’s repository on GitHub. You now have your own copy at
github.com/you/project. - Clone your fork locally:
Git automatically setsgit clone git@github.com:you/project.git cd projectoriginto your fork. - Add the upstream remote so you can pull updates from the real project:
git remote add upstream git@github.com:original/project.git - Create a feature branch:
git switch -c add-cool-feature - Commit your changes, then push to your fork:
git push -u origin add-cool-feature - Open a pull request from
you/project:add-cool-featuretooriginal/project:main.
To stay up to date with the original project:
git fetch upstream
git switch main
git merge upstream/main # or: git rebase upstream/main
git push origin main
Many teams use a single shared repo instead of forks. In that case there is no upstream — just origin, and you push feature branches there directly. The PR is then origin:feature-x -> origin:main. Everything else is the same.
Opening a pull request
A pull request (or “merge request” on GitLab) is a request to merge one branch into another, with a discussion attached. On GitHub:
- Push your branch (
git push -u origin feature-x). - Visit the repo on GitHub. There is usually a “Compare & pull request” banner — click it.
- Pick the base branch (usually
main) and your feature branch. - Write a description.
- Click Create pull request.
A good PR description has four sections:
## What
One sentence on what changed.
## Why
The reason this change exists — link to the issue or ticket.
## How
The interesting implementation choices a reviewer should know.
## Test plan
The steps you took to verify it works.
You will not regret writing this every time. Reviewers will thank you.
Keeping the PR up to date
If main moves while your PR is open and you need to integrate the changes:
git fetch origin
git switch feature-x
git rebase origin/main # or: git merge origin/main
git push --force-with-lease # because rebase rewrote history
Warning. Plain
git push --forcecan overwrite a teammate’s work pushed to the same branch. Always prefergit push --force-with-lease— it refuses to overwrite changes you have not seen yet.
Responding to review
A few things that make code review go well — for both sides:
- Address comments rather than dismissing them. “Done in commit abc123” closes the loop better than just resolving.
- Don’t squash mid-review. Reviewers re-reading the diff after a force-push lose their place. Squash at the end, or let the merge do it.
- Treat nits seriously but proportionally. Don’t block on style; do fix the broken thing.
- Be specific in comments. “This will break when X is empty” is more actionable than “Are you sure about this?”
A complete worked example
You want to contribute a small typo fix to an open-source project.
# 1. Fork on GitHub, then clone your fork
git clone git@github.com:yash/cool-project.git
cd cool-project
# 2. Add upstream
git remote add upstream git@github.com:original-author/cool-project.git
git remote -v
# 3. Make sure your main is fresh
git fetch upstream
git switch main
git merge upstream/main
git push origin main
# 4. Branch and fix
git switch -c fix-readme-typo
sed -i '' 's/teh/the/' README.md # macOS sed
git add README.md
git commit -m "Fix typo in README"
# 5. Push and open a PR
git push -u origin fix-readme-typo
# Open the GitHub URL the push command prints
That is the entire flow. Every contribution to every open-source project on GitHub uses some variant of these steps.
Try it yourself. Fork any small repository you find on GitHub (your own old project works). Clone it, add an upstream remote, create a branch, change a file, push, and open a PR from the GitHub UI. You don’t need to merge it — close the PR when done. The point is the muscle memory.
Recap
You now know:
- A remote is a named URL pointing at another copy of the repo.
git remote -vlists them. originis your copy;upstreamis the original (convention only).git fetchis safe — it downloads but does not modify local branches.git pull= fetch + merge (or rebase, if configured).git push -u origin <branch>pushes and sets up tracking on the first push.- The forking workflow: fork on GitHub, clone, add upstream, branch, commit, push, open PR.
- Use
--force-with-leaseinstead of--forcewhen rewriting pushed history. - A clear PR description (what, why, how, test plan) gets reviews faster.
Next steps
The next post returns to Docker and tackles one of the most impactful image-size optimizations: multi-stage builds. You will see how to ship a 20MB image from a Dockerfile that needed 800MB to build.
→ Next: Docker Multi-Stage Builds for Smaller Images
Questions or feedback? Email codeloomdevv@gmail.com.