Git, CI/CD & GitOpsXXI · Fetch vs PullFetchVsPull
Configuring pull to rebase by default — pull.rebase and branch.<name>.rebase
What you'll learn
- Distinguish pull.rebase (repo-wide default) from branch.<name>.rebase (single-branch default) and explain the precedence rules
- Configure pull.rebase true globally and override it per-branch with branch.<name>.rebase false
- Predict the resulting behaviour of git pull for a given combination of global, repo, and branch-level settings
- Identify the team implications of a global rebase default — what it standardises and what it breaks
- Recommend a configuration strategy that encodes the team pull policy in the repo rather than in every engineer's .gitconfig
Prerequisites
Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x
git pull is configurable: the team can decide in advance
whether it should default to merge, rebase, or fast-forward
only, and that decision can be encoded in Git’s configuration
so every pull behaves the same way without the engineer
remembering to pass a flag. The two configuration keys are
pull.rebase (which sets the default for the entire
repository or the entire machine) and branch.<name>.rebase
(which sets the default for one specific branch). The
combination of the two is what most production teams use to
encode their pull policy.
pull.rebase: the repository-wide default
The pull.rebase configuration key accepts four values:
false(default) —git pullrunsgit fetchfollowed bygit merge. A merge commit is produced on divergence.true—git pullrunsgit fetchfollowed bygit rebase. Local commits are replayed on top of the fetched upstream.merges(orinteractive) —git pullrunsgit fetchfollowed bygit rebase --rebase-merges, which preserves merge nodes in the local history while replaying the linear chain.onlyis a separate key (pull.ff = only) — see the previous lesson.
# Set pull.rebase at the repo level (this clone only)
git config pull.rebase true
# Set pull.rebase at the global level (every clone on this machine)
git config --global pull.rebase true
# Verify
git config --get pull.rebase
# true
# Effective setting for the current branch
git config --get-regexp '^pull\.rebase$'
# pull.rebase true
After git config pull.rebase true, every git pull in the
affected repository defaults to rebase mode. The engineer
still sees the fetch output, but the integration step is a
rebase instead of a merge; if a local commit conflicts with
the fetched upstream, the engineer resolves the conflict at
the commit level and git rebase --continues.
branch.<name>.rebase: the per-branch override
branch.<name>.rebase configures a single branch to override
the repository-wide pull.rebase. The value true forces
rebase mode for that branch; false forces merge mode; if
unset, the repo-level setting applies.
# Override repo default for one branch
git config branch.feature.iam-rotation.rebase true
# Override repo default for another branch in the opposite direction
git config branch.main.rebase false
# (forces merge mode on main, regardless of pull.rebase)
# Verify all branch-level rebase settings
git config --get-regexp '^branch\..*\.rebase$'
# branch.feature.iam-rotation.rebase true
# branch.main.rebase false
The branch name in the config key is the local branch name,
not the remote-tracking name. A branch named release/v1.4.0
appears in the config as
branch.release/v1.4.0.rebase (the dots in the version
number are part of the branch name, not section separators).
flowchart LR
A["git pull origin main"] --> B["read configuration"]
B --> C{"branch.main.rebase\nset?"}
C -->|yes| D["use branch.main.rebase"]
C -->|no| E{"pull.rebase\nset at repo level?"}
E -->|yes| F["use pull.rebase"]
E -->|no| G{"pull.rebase\nset at global level?"}
G -->|yes| H["use global pull.rebase"]
G -->|no| I["default: merge mode"]
D --> J["rebase or merge per branch"]
F --> J
H --> J
I --> J
The precedence from most specific to least specific is:
command-line flags (--rebase, --no-rebase,
--ff-only) win over everything; then branch-level
config wins over repo-level config; then repo-level
config wins over global config; and finally the
built-in default (merge mode) applies if nothing is set.
Encoding the team policy in the repo
A team that wants every engineer to pull the same way on the
shared branches should encode the policy in the repository,
not in every engineer’s ~/.gitconfig. The mechanism is the
repository’s .git/config (set with git config from inside
the clone) or, more durably, a config file shipped with the
repository’s setup script.
# Repo-level policy for an infrastructure team
# Pull with rebase on feature branches (clean local history)
git config pull.rebase true
# Pull with merge on shared branches (preserve SHAs)
git config branch.main.rebase false
git config branch.master.rebase false
git config branch.release.rebase false
git config branch.production.rebase false
# Optional: ff-only on CI clones
git config pull.ff only
This configuration travels with the clone: a new engineer
running git clone of the repository and then git pull on
any branch gets the right behaviour automatically, because
the per-branch overrides are in the repository’s
.git/config that the clone creates.
A common pattern is to ship the configuration as a script in
the repository’s bootstrap directory (e.g.,
scripts/setup-git-config.sh) that every new engineer runs
once after cloning:
#!/usr/bin/env bash
# scripts/setup-git-config.sh
# Run once after cloning the infrastructure repository.
set -euo pipefail
# Pull with rebase by default
git config pull.rebase true
# Pull with merge on shared branches
for branch in main master release production; do
git config "branch.${branch}.rebase" false
done
# Prune on every fetch
git config fetch.prune true
# Push only the current branch, never force
git config push.default current
git config --unset push.followTags 2>/dev/null || true
echo "git config set for this clone. Verify with 'git config --get-regexp'."
The script is idempotent: running it twice produces the same configuration. It can be re-run by any engineer who clones the repository to a new machine.
Verifying the effective configuration
Three commands to verify what pull will actually do on the current branch:
# Show every config setting that affects pull, in order of precedence
git config --list --show-origin | grep -E '(pull|branch)' | sort
# Effective setting for one branch
git config --get "branch.$(git symbolic-ref --short HEAD).rebase"
# false (because main has a per-branch override)
# What pull will do (verbose output of an empty pull)
git pull --no-rebase --dry-run --verbose 2>&1 | head
The --show-origin flag in the first command tells you
whether a setting came from ~/.gitconfig (global), the
repo’s .git/config (local), or the branch-level section.
That origin is what lets you diagnose a wrong-pull surprise:
if branch.main.rebase = false is in the local config but
the engineer is still seeing rebase mode, the per-branch
override is being shadowed by a command-line flag or by a
typo in the branch name.
Production discipline
- Encode pull policy in the repo, not in
~/.gitconfig. A new engineer cloning the repository should get the right policy for free. - Override per-branch for shared branches. Repo-wide
pull.rebase = trueplus per-branchrebase = falseon shared branches is safer than repo-wide rebase. - Ship a setup script. A
scripts/setup-git-config.shin the repository is more discoverable than a wiki page that nobody reads. - Verify the effective setting with
--show-origin. When a pull behaves unexpectedly, the first diagnostic is to ask Git which config layer the setting came from. - Audit the policy periodically. A team whose members have set conflicting global configs will have inconsistent pull behaviour even if the repo config is correct. The script fixes this; the wiki page does not.
Cross-course references
- Git, CI/CD & GitOps - Part XXII (RebaseDeeper) covers rebase in detail; the configuration here is the prelude to those mechanics.
- Terraform for Production Sysadmins - Part XVIII
(TeamConfig) describes how a Terraform team encodes
shared Git policies (including
pull.rebaseandbranch.<name>.rebase) in a bootstrap script that runs on every engineer’s first clone. - Ansible for Production Sysadmins - Part XL (BranchPolicy) ships a similar setup script for Ansible playbooks, with per-branch overrides for the shared integration branch.
Quiz
Knowledge check · 4 questions
Q1. In a repository with `pull.rebase = true` set at the repo level and `branch.main.rebase = false` set per-branch, what does `git pull` do on the `main` branch?
Q2. Setting `pull.rebase = true` in `~/.gitconfig` (the global config) is not necessarily the safest way to make every pull on every clone default to rebase mode.
Q3. Describe the precedence rules for pull configuration, from most specific to least specific, and explain why a team should encode pull policy in the repo rather than in ~/.gitconfig.
Q4. A team has set `pull.rebase = true` globally on every engineer's machine. The team shares a `release/v2.0` branch. Two engineers report that their release branches have diverged and a force-push happened. Diagnose and recommend a fix.
Two engineers working on `release/v2.0` both have `pull.rebase = true` set globally. Each has local commits ahead of origin/release-v2.0. Engineer A runs `git pull` (rebase mode, because of the global setting), rewrites their local commits on top of the fetched upstream, and pushes — producing a force-push because the SHA changed. Engineer B's clone, which has the original SHAs, now reports divergence. Engineer B rebases their own work onto the new SHAs and pushes — another force-push. The history of the release branch has been rewritten twice in one afternoon.
Passing score: 75%. Answers are checked in this browser.