What is the difference between `git fetch`, `git pull`, and `git push`? Understand local repositories, remote-tracking branches, and how changes travel between your machine and a shared Git repository.
Local vs Remote: Fetch, Pull, and Push Explained
You've learned how to create commits, work with branches, and merge changes.
So far, everything could happen on your own computer.
But software development rarely works that way. You might work with a team, use GitHub to host your repository, or move between your laptop and a development machine.
That's when three familiar commands become essential:
git fetch
git pull
git push
They all involve remote repositories, but they do very different jobs.
Let's understand what happens behind the scenes instead of memorizing three commands and hoping for the best.
1. First, what is a remote repository?
A local repository is the Git repository on your computer.
A remote repository is another repository that your local repository communicates with. It might live on GitHub, GitLab, Bitbucket, or a server you manage yourself.
Imagine you're working on a web application with a teammate.
Your local repository contains your commits. The remote repository contains the shared history that you and your teammate exchange.
Conceptually, it looks like this:
Your Computer Remote Server
+------------------+ +------------------+
| Local Repository | <--------------> | Shared Repository|
| | | |
| Commits | | Commits |
| Local branches | | Shared branches |
+------------------+ +------------------+
The remote is not a magical extension of your working directory. It is another repository that you exchange Git objects and references with.
A remote is simply a convenient name and URL for a repository you want to communicate with.
2. Meet origin
When you clone a repository, Git usually creates a remote named origin.
For example:
git clone https://github.com/example/webapp.git
Git downloads the repository and sets up origin to refer to the source repository.
You can inspect your configured remotes:
git remote -v
You might see:
origin https://github.com/example/webapp.git (fetch)
origin https://github.com/example/webapp.git (push)
origin is not a special server or a reserved Git command. It's just the conventional name Git assigns to the remote created by git clone.
You can use other names, too:
git remote add upstream https://github.com/example/original.git
This is common when contributing to an open-source project through a fork.
3. Local branches vs remote-tracking branches
Here's where things get interesting.
Suppose your local repository has a branch called main, and the remote has a branch called main too.
They are not the same branch.
Your local branch is:
main
The remote-tracking branch representing the last known state of the remote's main branch is usually:
origin/main
The distinction matters.
main is your local branch. You can create commits on it.
origin/main is your local record of the remote branch's state as last observed through Git operations.
Remote-tracking branches are updated when you fetch or perform other operations that obtain remote references. They are not live pointers that automatically change whenever someone pushes to the server.
Think of origin/main as your latest known information about the remote's main branch.
For example:
Local Repository
main A --- B
origin/main A
Your local branch has commit B, but your local knowledge of the remote branch still ends at A.
This can mean your local branch has commits that haven't been pushed yet.
It doesn't necessarily mean that the remote is unchanged right now; someone else may have pushed changes that you haven't fetched.
That brings us to our first command.
4. git fetch: Find out what's changed
Imagine your teammate has pushed a new commit to the remote repository.
Your local repository doesn't automatically know about it.
Run:
git fetch origin
Git contacts origin, downloads the necessary objects, and updates the relevant remote-tracking references.
Suppose the remote's main branch has advanced:
Before fetch
main A --- B
origin/main A
After fetching:
After fetch
main A --- B
origin/main A --- C
The diagram illustrates the idea that the remote-tracking branch now reflects the remote's updated history. In a real repository, C might be a descendant of A, or the remote might have diverged from your local branch.
The crucial point is that fetch updates your knowledge of the remote; it doesn't automatically merge the fetched changes into your current branch.
Your working directory isn't automatically rewritten just because you fetched.
You can inspect the new history:
git log --oneline --graph --decorate --all
You can also compare your current branch with the remote-tracking branch:
git diff main..origin/main
This shows the difference between the two commit snapshots, if the references exist.
To inspect commits present on the remote-tracking branch but not your local branch, use:
git log main..origin/main --oneline
Fetching is useful because it lets you inspect what changed before deciding how to integrate it.
It's the Git equivalent of checking the situation before making a decision.
5. git pull: Fetch and integrate
Suppose you've fetched the remote changes and decided to incorporate them into your current branch.
You could fetch and then merge manually:
git fetch origin
git merge origin/main
Or you could use:
git pull
Conceptually, git pull fetches from the configured upstream and then integrates the fetched changes into your current branch.
By default, the integration commonly uses a merge, but configuration and command-line options can make git pull use rebase instead.
For example:
git pull --rebase
This fetches the upstream changes and rebases your local commits on top of the updated upstream history, subject to the usual rebase rules.
The distinction matters because the resulting history can look different.
Pull with merge
Suppose the remote has a new commit, and you have also made a local commit.
L main
/
A --+
\
R origin/main
If the histories have diverged, a merge-based pull may create a merge commit:
L ------- M main
/ /
A --+ /
\ /
R ---- origin/main
Here, M records the integration of the two lines of development.
Pull with rebase
With a rebase-based pull, Git reapplies your local commits on top of the fetched upstream history.
Conceptually:
A --- R --- L' main
^
origin/main
L' is a rewritten version of your local commit L, with a different commit identity.
The resulting history is linear in this simplified example.
Neither approach is universally best. Merge preserves the topology of the integration, while rebase can keep the history more linear. Follow your team's conventions, especially when working with commits that other people already depend on.
One more practical note: git pull can encounter conflicts. If that happens, resolve them according to whether Git is performing a merge or a rebase, then complete or abort the operation as appropriate.
6. git push: Share your commits
Now let's look in the other direction.
You've created commits locally and want to share them with the remote repository.
That's what git push is for.
For example:
git push origin main
Git sends the necessary objects and updates the remote branch reference if the push is permitted and can be applied safely.
Before the push:
Local main A --- B
Remote main A
After a successful push:
Local main A --- B
Remote main A --- B
The remote's main branch now includes commit B.
This is the normal way to publish your local work to a shared repository.
What if the remote has new commits?
Suppose your teammate pushes a commit while you're working locally.
Your local branch is now behind or has diverged from the remote branch.
A normal push may be rejected because it would not be a safe fast-forward update.
You may see a message indicating that the remote contains work you do not have locally.
The usual approach is to fetch and inspect the remote changes, integrate them using your team's preferred merge or rebase workflow, and then push again.
For example:
git fetch origin
git log --oneline --graph --decorate --all
After you've integrated the remote changes and verified the result, push your branch:
git push origin main
Don't reach for a force push just because a normal push was rejected. Force pushing can replace the remote branch reference and disrupt other people's work. Use it only when you understand the implications and have permission to rewrite that branch's history.
7. A practical workflow for a feature branch
Let's put the commands together.
Imagine you're developing a new search feature.
First, make sure you know the current state of your repository:
git status
Fetch the latest remote information:
git fetch origin
Switch to your feature branch or create it from an appropriate starting point:
git switch -c feature/search
Make your changes, then commit them:
git add .
git commit -m "Add search feature"
Publish the feature branch:
git push -u origin feature/search
The -u option sets the upstream relationship for the local branch. After that, ordinary git push and git pull can often infer the intended remote branch.
In a team workflow, you might then open a pull request or merge request for review.
After the feature is integrated into main, update your local branch using your team's chosen process.
One important detail: creating a feature branch after fetching does not automatically mean you started from the latest remote main. If you want to base the feature on the latest remote state, explicitly switch to or create a branch from origin/main, or update your local main first.
For example, to create a branch from the latest fetched remote main:
git switch -c feature/search origin/main
This uses the remote-tracking branch as the starting point.
8. Common misconceptions
"Fetch and pull are the same."
Not quite. Fetch obtains remote updates and updates remote-tracking references. Pull also integrates those updates into the current branch, commonly through merge or rebase.
"origin/main is the branch on the server."
It's your local remote-tracking reference representing the last known state of the remote branch. The actual branch exists in the remote repository.
"Push downloads changes."
Push publishes local commits and updates remote references. Fetch is the operation used to obtain remote updates.
"Pull always creates a merge commit."
No. A pull may fast-forward, create a merge commit, or rebase your local commits, depending on the history, options, and configuration.
"A successful push means my code is correct."
A successful push means Git accepted the update. It doesn't guarantee your application builds, your tests pass, or the feature behaves as intended.
The takeaway
The three commands serve distinct purposes:
git fetch — Get remote updates and update your local remote-tracking references.
git pull — Fetch remote updates and integrate them into your current branch.
git push — Publish local commits to a remote repository.
A simple way to remember them:
Remote changes --fetch--> Local knowledge
Remote changes --pull---> Current local branch
Local commits --push---> Remote repository
Understanding these differences makes collaboration much less confusing. You can inspect changes before integrating them, share work deliberately, and reason about why a branch is ahead, behind, or diverged.
What's next?
We've learned how to exchange changes between local and remote repositories. Now let's learn how to investigate the history itself.
In the next article, we'll use git log, git show, and git diff to discover what changed, when it changed, and how to trace a bug through the project's history.
Next: Reading Git History Like a Detective.

