Default Git ships with settings optimized for backward compatibility across two decades of workflows. That means no commit signing, a raw less pager for diffs, manual upstream tracking on every new branch, and verbose commands for routine operations. This guide walks through a full ~/.gitconfig overhaul — aliases, delta pager, SSH signing, conditional identity, and every advanced toggle worth enabling — so your terminal matches how you actually work.
The Complete ~/.gitconfig
Here is the full configuration file. Every section is explained in detail below. You can copy this entire block into ~/.gitconfig (or $XDG_CONFIG_HOME/git/config if you prefer XDG paths) and adjust the values marked with angle brackets.
[user]
name = <Your Name>
email = <your@personal-email.com>
signingkey = ~/.ssh/id_ed25519.pub
[core]
editor = nvim
autocrlf = input
pager = delta
excludesfile = ~/.gitignore_global
fsmonitor = true
untrackedcache = true
[init]
defaultBranch = main
[commit]
gpgsign = true
verbose = true
[gpg]
format = ssh
[gpg "ssh"]
allowedSignersFile = ~/.ssh/allowed_signers
[push]
autoSetupRemote = true
default = current
followTags = true
[pull]
rebase = true
[fetch]
prune = true
prunetags = true
parallel = 0
[merge]
conflictstyle = zdiff3
tool = nvim
[diff]
algorithm = histogram
colorMoved = default
renames = copies
[rebase]
autosquash = true
autostash = true
updateRefs = true
[rerere]
enabled = true
[column]
ui = auto
[branch]
sort = -committerdate
[tag]
sort = -version:refname
[interactive]
diffFilter = delta --color-only
[delta]
navigate = true
light = false
side-by-side = true
line-numbers = true
syntax-theme = Dracula
file-style = bold yellow ul
hunk-header-style = omit
[alias]
co = checkout
br = branch
ci = commit
st = status -sb
last = log -1 HEAD --stat
lg = log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit
unstage = reset HEAD --
amend = commit --amend --no-edit
wip = !git add -A && git commit -m 'wip: checkpoint [skip ci]'
nuke = !git clean -fd && git checkout -- .
aliases = config --get-regexp alias
branches = branch -a --sort=-committerdate --format='%(color:yellow)%(refname:short)%(color:reset) %(color:green)%(committerdate:relative)%(color:reset) %(color:blue)%(authorname)%(color:reset)'
contributors = shortlog -sn --no-merges
undo = reset --soft HEAD~1
find = log --all --pretty=format:'%C(yellow)%h %Cgreen%ad %Cblue%an%Creset %s' --date=short --grep
what = show --stat --format='%C(yellow)%h %Cgreen%ci%Creset %s%n%b'
sync = !git fetch --all --prune && git rebase origin/main
gone = !git branch -vv | grep ': gone]' | awk '{print $1}'
prune-gone = !git gone | xargs git branch -D
[includeIf "gitdir:~/work/"]
path = ~/.gitconfig-work
[includeIf "gitdir:~/projects/"]
path = ~/.gitconfig-personal
That is roughly 100 lines. The rest of this article breaks down every section so you understand what each setting does and why it is there.
Git Aliases: 18 Commands Worth Memorizing
Aliases eliminate the repetitive typing that slows down terminal workflows. Here is what each one does:
Basic Shortcuts
| Alias | Expands To | What It Does |
|---|---|---|
co | checkout | Switch branches or restore files |
br | branch | List, create, or delete branches |
ci | commit | Record changes to the repository |
st | status -sb | Short-format status with branch info — two columns instead of verbose paragraphs |
Log and History
| Alias | Expands To | What It Does |
|---|---|---|
last | log -1 HEAD --stat | Shows the most recent commit with file-level diff stats |
lg | log --graph --pretty=format:... | Compact, colorized branch graph — the single most useful alias in this list |
what | show --stat --format=... | Displays a specific commit’s message body plus changed files |
find | log --all --pretty=... --grep | Searches all branches for commits matching a keyword in the message |
contributors | shortlog -sn --no-merges | Ranks authors by commit count, excluding merge commits |
Staging and Undo
| Alias | Expands To | What It Does |
|---|---|---|
unstage | reset HEAD -- | Removes files from the staging area without discarding changes |
amend | commit --amend --no-edit | Adds staged changes to the previous commit, keeping the same message |
undo | reset --soft HEAD~1 | Undoes the last commit but keeps all changes staged — safe undo |
wip | add -A && commit -m 'wip: checkpoint [skip ci]' | Stages everything and commits with a throwaway message. The [skip ci] tag prevents CI pipelines from triggering on work-in-progress pushes |
Cleanup
| Alias | Expands To | What It Does |
|---|---|---|
nuke | clean -fd && checkout -- . | Destroys all untracked files and reverts all modifications — nuclear reset to HEAD |
sync | fetch --all --prune && rebase origin/main | Pulls the latest remote state and rebases your current branch on top of main |
gone | branch -vv | grep ': gone]' | awk '{print $1}' | Lists local branches whose upstream has been deleted |
prune-gone | gone | xargs branch -D | Deletes every local branch returned by gone — cleans up stale feature branches after merge |
aliases | config --get-regexp alias | Prints all your configured aliases — useful when you forget what you set up |
branches | branch -a --sort=-committerdate --format=... | Lists all branches sorted by last commit date with author and relative time |
Installing and Configuring Delta
The built-in Git diff output renders additions and deletions as raw +/- lines in monochrome green and red. Delta replaces this with syntax-highlighted, side-by-side diffs directly in your terminal. It is written in Rust and adds no measurable latency to git diff or git log -p.
Installation
# macOS (Homebrew)
brew install git-delta
# Ubuntu / Debian / WSL2
sudo apt-get install git-delta
# If your distro packages an old version, install via cargo
cargo install git-delta
# Windows (Scoop)
scoop install delta
# Verify installation
delta --version
On Ubuntu 22.04+, apt ships delta 0.16+. On older Ubuntu releases, the apt package may be outdated or missing entirely — use cargo install git-delta in that case. You need the Rust toolchain installed (curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh).
The Delta Config Block
The full [delta] section in your gitconfig:
[core]
pager = delta
[interactive]
diffFilter = delta --color-only
[delta]
navigate = true
light = false
side-by-side = true
line-numbers = true
syntax-theme = Dracula
file-style = bold yellow ul
hunk-header-style = omit
What each option does:
- navigate: Enables
n/Nkey bindings to jump between diff hunks inside the pager. This is the single biggest quality-of-life improvement over plainless. - light: Set to
trueif your terminal uses a light background. Keepfalsefor dark themes. - side-by-side: Splits the terminal into left (old) and right (new) columns. Requires a terminal width of at least 120 columns to be useful. If your terminal is narrower, Delta falls back to unified view automatically.
- line-numbers: Displays line numbers in the gutter of each side.
- syntax-theme: Any theme supported by
bat. Rundelta --list-syntax-themesto see all options.Dracula,Nord,gruvbox-dark, andMonokai Extendedare popular choices. - file-style: Formats the filename header —
bold yellow ulgives you bold, yellow, underlined filenames separating each file’s diff. - hunk-header-style: Set to
omitto remove the@@ ... @@hunk headers, which are noise when you already have line numbers enabled.
SSH Commit Signing
GPG signing works but comes with real operational friction: separate key generation, expiration management, keyserver uploads, and a gpg-agent daemon that occasionally locks up. SSH signing, available since Git 2.34, replaces all of that with the key pair you already use for repository access.
Step 1: Generate an Ed25519 Key (If You Do Not Have One)
ssh-keygen -t ed25519 -C "your@email.com"
Accept the default path (~/.ssh/id_ed25519). Set a passphrase — your SSH agent will cache it so you are not prompted on every commit.
Step 2: Create the Allowed Signers File
Git needs a list of trusted public keys to verify signatures. Create ~/.ssh/allowed_signers:
echo "your@email.com $(cat ~/.ssh/id_ed25519.pub)" > ~/.ssh/allowed_signers
This file maps email addresses to public keys. When you run git log --show-signature, Git checks this file to validate each commit’s signer.
Step 3: Configure Git
These three sections in your gitconfig enable signing:
[user]
signingkey = ~/.ssh/id_ed25519.pub
[gpg]
format = ssh
[gpg "ssh"]
allowedSignersFile = ~/.ssh/allowed_signers
[commit]
gpgsign = true
Step 4: Register Your Key on GitHub / GitLab
On GitHub: Go to Settings → SSH and GPG keys → New SSH key. Change the key type dropdown from “Authentication Key” to “Signing Key”. Paste the contents of ~/.ssh/id_ed25519.pub. You need to add the key as both an authentication key and a signing key — they are registered separately even if they use the same public key.
On GitLab: Go to Preferences → SSH Keys. GitLab does not distinguish between authentication and signing keys — a single entry covers both.
After pushing a signed commit, your commits will display a “Verified” badge in the web UI.
Advanced Git Settings Explained
Beyond aliases and pagers, these global settings change how Git handles branching, merging, and remote tracking.
fetch.prune and fetch.prunetags
[fetch]
prune = true
prunetags = true
parallel = 0
When a teammate deletes a remote branch after merging a PR, your local copy of that remote-tracking reference (origin/feature-xyz) sticks around until you manually run git fetch --prune. Setting fetch.prune = true makes every git fetch and git pull automatically clean up stale references. prunetags does the same for deleted remote tags. parallel = 0 tells Git to use as many parallel connections as your system supports when fetching from multiple remotes.
pull.rebase
[pull]
rebase = true
By default, git pull creates a merge commit every time your local branch has diverged from the remote. Over weeks, this produces a commit graph full of meaningless “Merge branch ‘main’ into feature-x” entries. Setting pull.rebase = true replays your local commits on top of the fetched remote state, keeping history linear.
push.autoSetupRemote
[push]
autoSetupRemote = true
default = current
followTags = true
Without this, every new branch requires git push --set-upstream origin branch-name on the first push. With autoSetupRemote = true, a plain git push automatically creates the upstream tracking relationship. followTags = true pushes annotated tags along with commits, so you do not need a separate git push --tags.
merge.conflictstyle = zdiff3
[merge]
conflictstyle = zdiff3
The default merge conflict style shows two sides: yours and theirs. diff3 adds a third section showing the common ancestor — the original code before either side changed it. This context makes it dramatically easier to resolve conflicts because you can see what both sides intended to change. zdiff3 is a refinement that reduces noise by omitting unchanged regions within the conflict block.
rerere.enabled
[rerere]
enabled = true
“Reuse Recorded Resolution.” When you resolve a merge conflict, Git records the resolution. If the same conflict appears again (common during long-running rebases or cherry-picks), Git applies your previous resolution automatically. This is especially useful when you repeatedly rebase a feature branch onto a moving main branch.
diff.algorithm = histogram
[diff]
algorithm = histogram
colorMoved = default
renames = copies
The default myers diff algorithm sometimes produces confusing diffs when code blocks are moved or when functions are reordered. The histogram algorithm (a variant of patience) produces cleaner, more readable diffs with better alignment of changed blocks. colorMoved = default highlights lines that were moved rather than changed, coloring them differently from true additions and deletions. renames = copies detects both renamed and copied files.
rebase.autosquash and rebase.updateRefs
[rebase]
autosquash = true
autostash = true
updateRefs = true
autosquash makes git rebase -i automatically reorder commits prefixed with fixup! or squash! next to their target — a clean workflow for iterative code review. autostash stashes uncommitted changes before a rebase and pops them after, so you do not need to manually stash before rebasing. updateRefs (Git 2.38+) automatically updates stacked branch pointers during an interactive rebase, which is critical if you use stacked PRs.
column.ui and branch.sort
[column]
ui = auto
[branch]
sort = -committerdate
column.ui = auto displays branch lists and tag lists in columns (like ls does for files) when your terminal is wide enough. branch.sort = -committerdate sorts branches by most recent commit first, so the branch you were just working on appears at the top instead of buried alphabetically.
Default vs Recommended: Settings Comparison
| Setting | Default Value | Recommended Value | Why Change It |
|---|---|---|---|
core.pager | less | delta | Syntax-highlighted, side-by-side diffs in the terminal |
diff.algorithm | myers | histogram | Cleaner diffs with better hunk alignment for moved code |
merge.conflictstyle | merge | zdiff3 | Shows the common ancestor in conflicts, reducing guesswork |
fetch.prune | false | true | Automatically removes stale remote-tracking branches |
pull.rebase | false (merge) | true | Keeps commit history linear, avoids noise merge commits |
push.autoSetupRemote | false | true | Eliminates --set-upstream on first push of new branches |
push.followTags | false | true | Pushes annotated tags automatically with commits |
rerere.enabled | false | true | Records and replays merge conflict resolutions |
rebase.autosquash | false | true | Auto-reorders fixup!/squash! commits during interactive rebase |
rebase.autostash | false | true | Stashes dirty working tree before rebase, pops after |
rebase.updateRefs | false | true | Updates stacked branch pointers during rebase |
commit.verbose | false | true | Shows the full diff in your commit message editor |
init.defaultBranch | master | main | Aligns with GitHub/GitLab defaults |
gpg.format | openpgp | ssh | Uses existing SSH keys for signing, no GPG tooling needed |
column.ui | never | auto | Columnar display for branch and tag lists |
branch.sort | alphabetical | -committerdate | Most recently active branches appear first |
Conditional Includes: Separate Work and Personal Identities
If you contribute to your employer’s repositories and your own side projects from the same machine, you need different user.name and user.email values depending on which repository you are in. Conditional includes solve this without requiring per-repo configuration.
The relevant block in your main ~/.gitconfig:
[includeIf "gitdir:~/work/"]
path = ~/.gitconfig-work
[includeIf "gitdir:~/projects/"]
path = ~/.gitconfig-personal
Then create each override file. For your work identity:
# ~/.gitconfig-work
[user]
name = Your Legal Name
email = you@company.com
signingkey = ~/.ssh/id_ed25519_work.pub
And your personal identity:
# ~/.gitconfig-personal
[user]
name = Your Handle
email = you@personal.com
signingkey = ~/.ssh/id_ed25519.pub
Git evaluates gitdir: against the .git directory of the repository you are currently in. Any repository cloned under ~/work/ will use your work email. Anything under ~/projects/ gets your personal email. Repositories outside both paths fall back to whatever [user] block exists in the main ~/.gitconfig.
The trailing slash on ~/work/ is important — it tells Git to match all subdirectories recursively. Without the slash, only repositories whose .git directory is exactly at ~/work/.git would match.
You can verify which identity is active in any repo by running:
# Run this from inside a repository
git config user.email
# Shows which email is active
git config --show-origin user.email
# Shows which file the value comes from
Common Pitfalls
Delta not rendering correctly over SSH: Delta requires a terminal with 256-color or truecolor support. If you SSH into a machine and diffs look garbled, make sure your SSH client forwards TERM properly. Setting COLORTERM=truecolor in your remote shell profile usually fixes this.
Rebase conflicts on shared branches: If your team pushes directly to main and you have pull.rebase = true, rebasing can rewrite commits that others have already based work on. Use rebase for your feature branches. For shared long-lived branches, either set pull.rebase = false in the repo’s local config or use git pull --no-rebase explicitly.
SSH signing fails with “error: Load key … invalid format”: This happens when user.signingkey points to the private key instead of the public key. The value must end in .pub. Git reads the public key for signing metadata and invokes ssh-keygen -Y sign with the private key via your SSH agent.
fsmonitor slows down on network-mounted drives: The core.fsmonitor = true setting uses a filesystem watcher daemon to speed up git status and git diff on large repositories. On NFS or SMB mounts, the watcher generates excessive events. Disable it per-repo with git config --local core.fsmonitor false if you notice slowdowns on network volumes.
rerere replays a bad resolution: If you resolve a conflict incorrectly and rerere records it, every future occurrence of that conflict gets the wrong resolution. Clear the bad recording with git rerere forget <pathspec> to force Git to prompt you again.
Wrapping Up
A tuned gitconfig removes friction from every interaction with version control — faster branch switching, readable diffs, automatic upstream tracking, signed commits without GPG overhead, and separate identities for work and personal projects. Copy the full config from the top of this article, adjust the angle-bracketed values, and start using it immediately. Every setting here is backward-compatible and can be overridden per-repository with git config --local when needed.