Git Fetch vs Pull — 47 Merge Commits on Shared Branches
47 merge commits in 3 weeks from git pull on shared branches? Git fetch avoids them.
- git fetch = reconnaissance only — inspect before integrating
- git pull = fetch + merge in one step — convenient but skips inspection
- git pull --rebase = fetch + rebase — linear history, no merge commit
git fetch downloads changes from the remote but doesn't touch your working directory — it's reconnaissance. git pull does the same download and then immediately merges or rebases them into your current branch. Fetch first, merge when ready. Pull is fetch + merge in one step. Knowing this distinction lets you inspect what's changed before applying it.
git fetch and git pull both download changes from a remote repository, but they differ in what happens next. Fetch stops after the download — your local branches and working directory are untouched. Pull continues by merging or rebasing the downloaded changes into your current branch immediately.
The distinction matters in production. Pulling on a shared branch creates unnecessary merge commits. Pulling without inspecting first means you merge blind — you do not see what is coming until it conflicts. Fetch gives you the inspection window. Pull removes it.
Common misconceptions: that fetch modifies your working directory (it does not), that pull is always safe (it can create merge commits and conflicts), and that origin/main updates automatically after fetch (your local main branch stays put — only the remote-tracking ref moves).
What Each Command Actually Does
Understanding what goes where in your local repo clears up the confusion immediately.
git fetch origin downloads objects and refs from the remote and updates your remote-tracking refs (origin/main, origin/feature/x). Your local branches and working directory are completely untouched. You can now inspect what changed, compare branches, or decide your integration strategy.
git pull runs git fetch then immediately runs git merge (or git rebase with --rebase) to integrate the fetched changes into your current branch. Convenient, but removes the inspection step.
The thing that trips people up: after git fetch, your local main branch hasn't moved. origin/main has. They're now two different things pointing at potentially different commits.
- git fetch: downloads objects, updates origin/main — your local main stays put
- git pull: fetches then merges/rebases — your local main moves to match origin/main
- After fetch: git log main..origin/main shows what you are missing
- After pull: your local branch is synchronized — no gap to inspect
When to Use Fetch Over Pull
Fetch is the safer professional habit for a few specific scenarios:
Before merging a PR locally: fetch first, inspect the diff against your branch, verify there are no conflicts, then merge with confidence.
On shared branches: before pushing to main or develop, always git fetch origin main first to check if the remote has moved. If it has, rebase or merge before pushing.
In CI/CD scripts: pipelines should git fetch and compare refs programmatically rather than blindly pulling, which might trigger merge commits that pollute the history being built.
After a long coding session: fetch before you push to understand what changed while you were working. git log HEAD..origin/main tells you exactly what to expect before you integrate.
- Fetch before every push to check if the remote has moved
- Fetch before merging a PR to inspect the diff for conflicts
- Fetch in CI/CD scripts to compare refs programmatically — never pull blindly
- Pull only after you have fetched and inspected with git log
Configuring Pull Behavior for Your Team
The default git pull behavior (fetch + merge) creates merge commits when your local branch has diverged from the remote. For feature branches and most development workflows, rebase is preferred because it produces linear history.
git config --global pull.rebase true makes every git pull rebase by default. This is the single most impactful git configuration change for team history cleanliness. Once set, git pull behaves like git fetch + git rebase instead of git fetch + git merge.
The trade-off: rebasing changes commit hashes. If you have local commits that have not been pushed, rebasing rewrites them with new hashes. This is fine for local-only commits but problematic if you have already shared those commits with others (they will see conflicts when they pull your rebased commits).
For teams that use merge commits intentionally (to mark integration points), pull.rebase merges preserves merge commits while rebasing regular commits. This is the middle ground.
- pull.rebase true: linear history, no merge commits on pull
- pull.rebase merges: rebase regular commits, keep intentional merge commits
- Set in onboarding scripts so every developer has the same behavior
- Per-repo override: git config pull.rebase false for repos that need merge behavior
git config --global pull.rebase true to eliminate unnecessary merge commits on pull. For teams that use intentional merge commits, use pull.rebase merges instead. Add to onboarding scripts so every developer has consistent behavior. This is the single most impactful git configuration change for history cleanliness.git pull on Shared Feature Branch: 47 Merge Commits in 3 Weeks
git log --oneline showed a tangled graph of merge nodes. git bisect could not isolate bugs because merge commits changed the bisect path. Code review was painful — PRs showed merge commit diffs alongside actual code changes.git pull origin feature/payment-v2 every morning.
3. Because each developer had local commits that were not on the remote, git pull created a merge commit every time.
4. With 6 developers pulling once per day for 3 weeks: 6 x 1 x 21 = 126 potential merge commits. Some pulls were clean (no local commits yet), resulting in 47 actual merge commits.
5. The merge commits added no value — they were mechanical synchronization artifacts.
6. Nobody noticed until a git bisect session failed because the bisect landed on a merge commit that did not represent a logical code change.git checkout -b feature/payment-v2-clean main && git merge --squash feature/payment-v2 && git commit.
2. Team-wide: configured git config --global pull.rebase true on every developer's machine.
3. Added to onboarding documentation: 'Always use git pull --rebase on shared branches.'
4. Added a pre-push hook that warns if the current branch has more than 2 merge commits: git log --merges --oneline origin/main..HEAD | wc -l.
5. Considered using short-lived feature branches with one developer per branch instead of shared long-lived branches.- git pull creates merge commits when your local branch has diverged from the remote. On shared branches with multiple committers, this multiplies rapidly.
- git config --global pull.rebase true eliminates unnecessary merge commits by rebasing instead of merging during pull.
- Shared feature branches with multiple developers are an anti-pattern when used long-term. Short-lived branches with one developer per branch avoid the problem entirely.
- git bisect is useless on branches with merge commit pollution. Clean history is a debugging tool, not just aesthetics.
git log --oneline --merges origin/main..HEAD — shows merge commits on your branch.
3. Fix: configure git config --global pull.rebase true to rebase instead of merge.
4. Clean up existing merge commits: git rebase -i HEAD~N to squash them.git fetch origin to see what changed.
3. Inspect: git log origin/main..HEAD --oneline to see your commits.
4. Rebase: git rebase origin/main to put your commits on top of the latest remote.
5. Push: git push origin — now it is a fast-forward.git add <file> && git rebase --continue.
3. If the conflict is too complex: git rebase --abort to return to pre-rebase state.
4. Alternative: git pull (merge) instead of rebase — merge conflicts are sometimes easier to resolve because you see both sides at once.git remote -v and git branch -vv.
2. You may be tracking a different branch than you think.
3. Fix: git branch -u origin/main main to set the upstream tracking branch.
4. Verify: git branch -vv now shows [origin/main] next to your branch.git checkout -b temp-branch.
4. Then pull: git pull origin main.
5. Or switch to the intended branch: git checkout main && git pull.Key takeaways
Interview Questions on This Topic
Frequently Asked Questions
That's Git. Mark it forged?
3 min read · try the examples if you haven't