Configuring git pull --rebase safely for a team Jump to heading

git pull is the most-used Git command whose behaviour nobody can predict without checking the configuration. Depending on three settings and a Git version, it merges, it rebases, it fast-forwards only, or it refuses to run and prints a paragraph of advice. That variability is the actual problem: two developers on the same repository get different histories from the same command. This recipe removes the ambiguity, applying the trade-offs from the Merge vs Rebase Decision Matrix.

When to use this approach Jump to heading

  • Your history is peppered with “Merge branch ‘main’ of …” commits that carry no information.
  • New team members hit Git’s “divergent branches” advice and each resolve it differently.
  • You have standardised on a linear history for the trunk and want local pulls to match.
  • A long-lived branch workflow already rebases deliberately, and the default should not fight it.
  • If your team merges everything and values the record of when each pull happened, set pull.rebase false explicitly instead — the point is that the setting is chosen, not left to chance.

Step 1 — See what a default pull currently does Jump to heading

# The three settings that determine pull behaviour
git config --get-regexp '^(pull|rebase|branch)\.' || echo "(nothing set — behaviour depends on Git's defaults)"

# Reproduce the divergence a pull has to resolve
git fetch origin
git rev-list --left-right --count origin/main...HEAD

What changed: nothing — but if the first command printed nothing, every developer’s pull is governed by their Git version and any global config they happen to have, which is the situation to fix.

One command, three historiesFrom the same divergent state, a merge pull creates a merge commit, a rebase pull replays local commits on top of the remote tip, and fast-forward-only refuses and leaves the developer to decide. Without configuration, which one happens depends on each machine.the same starting state on three machinespull.rebase falsea merge commit saying only "I pulled"pull.rebase trueyour commits, replayed on the new tippull.ff onlyfatal: Not possible to fast-forwardsafe, but everyone invents their own next step

Step 2 — Set the rebase default and its safety options Jump to heading

Three settings, and the second and third are what make the first safe.

# 1. Rebase local commits onto the fetched tip instead of merging
git config pull.rebase true

# 2. Stash uncommitted work automatically, and restore it afterwards
git config rebase.autoStash true

# 3. Use the reflog to tell your commits apart from ones upstream dropped
git config pull.rebase merges     # preserves merge commits within the branch
git config rebase.autoSquash true # honour fixup! commits during interactive rebase

What changed: git pull now replays local commits on top of the remote tip, does not refuse because of a dirty working tree, and preserves any merge commits inside your branch rather than flattening them.

# Verify the settings resolve as expected
git config --get-regexp '^(pull|rebase)\.'
# Then exercise it on a branch with local commits
git commit --allow-empty -m "chore: local work"
git pull
git log --oneline -3       # your commit sits on top; no merge commit appeared

rebase.autoStash deserves its reputation. Without it, a pull with a dirty working tree aborts, the developer stashes by hand, pulls, and — perhaps a third of the time — forgets to pop the stash and rewrites the same change an hour later. With it, the stash and the pop are part of the operation.

Step 3 — Ship the configuration to every clone Jump to heading

Local configuration only helps the person who set it. Two mechanisms distribute it.

# A configuration file committed to the repository
cat > .gitconfig-shared <<'EOF'
[pull]
    rebase = merges
[rebase]
    autoStash = true
    autoSquash = true
[branch]
    autoSetupRebase = always
EOF
git add .gitconfig-shared
git commit -m "chore: shared Git configuration for the repository"
# Each clone opts in once — put this in the setup instructions
git config include.path ../.gitconfig-shared

# Verify it took effect
git config pull.rebase        # expect: merges
git config --show-origin pull.rebase
Distributing configuration Git will not distribute itselfGit never copies configuration from a remote for security reasons, so a committed config file is included by each clone with a one-line opt-in. A setup check reports whether the include is present, so a clone that skipped it is detected rather than silently divergent..gitconfig-sharedcommitted, reviewedclone A — opted ininclude.path setclone B — opted ininclude.path setclone C — never ran itmerges instead of rebasingsetup checkgit config include.pathfails loudly if unsetGit deliberately never pulls config from a remote — the opt-in is the security boundary, so make it part of setup
# Add the check to the project's setup task so clone C is found
[ -n "$(git config include.path)" ] || {
  echo "run: git config include.path ../.gitconfig-shared" >&2
  exit 1
}

SAFETY WARNINGpull.rebase true rewrites your local commits every time you pull. On a branch you share with someone else, that means your next push offers a rewritten history and will be refused, or worse, accepted with --force and orphaning their work. Keep the protection against force pushes on shared branches so this configuration cannot cause damage.

Step 4 — Document the cases that still need a merge Jump to heading

Where the default is right, and where to override itA rebasing pull is right for ordinary work on your own branch and for keeping a solo branch current. An explicit merge is still needed when the branch is shared, when integrating a completed feature, and when recovering from an upstream rebase.the default is rightpulling your own branch after a colleague pushedkeeping a solo branch current with mainpicking up main before starting new workany pull where you have no local commitsgit pulloverride with an explicit mergethe branch is shared with someone elseintegrating a finished feature into mainupstream rebased and you must reconcileyou want the pull recorded as an eventgit pull --no-rebase
# The override, for the cases on the right
git pull --no-rebase

# Recovering when upstream rebased a branch you track
git fetch origin
git rebase --onto origin/feat/payments <old-upstream-tip> HEAD

Put both in the contributing guide next to the include.path instruction. A default that is documented alongside its exceptions gets followed; one that is set silently gets overridden by whoever first hits a case it does not fit.

Validation checklist Jump to heading

Frequently Asked Questions Jump to heading

Why is pull --rebase safer than a merge pull? Jump to heading

It is not universally safer — it is safer for the specific case a pull usually is: you have local commits, the remote has moved, and you want your work on top rather than a merge commit recording that you pulled. A merge pull produces a commit with no informational content that clutters history and complicates later bisects. For that ordinary case, rebase is both cleaner and closer to what the developer meant.

Does rebase.autoStash risk losing work? Jump to heading

No. It stashes uncommitted changes, performs the rebase, then reapplies them, and if reapplying conflicts the stash is retained rather than discarded — recoverable with git stash list. It is strictly better than the alternative it replaces, which is Git refusing the operation and the developer stashing by hand and occasionally forgetting to pop it.

What happens on a branch that was rebased upstream? Jump to heading

Without fork-point detection, a plain rebase can replay commits that upstream already dropped, silently resurrecting them. Setting pull.rebase to merges keeps merge commits intact, and the fork-point heuristic uses your reflog to work out which commits were genuinely yours. Together they make the common recovery case behave sensibly.