Once you have seen better than git, you can never come back. But you can make it slightly more tolerable.
Extensions
These two extensions will substantially increase your velocity, help you flow, and keep you in the zone.
git-branchless
When forced to use Git I naturally use a stacked commit workflow to move much faster, cutting out the overhead of constant feature branching to keep my development process lean. Instead of wrestling with a web of branches, git-branchless enables a "stacked" approach where each atomic change is a single commit built directly on top of the last.
The tool acts as a high-powered coordinator for this linear progression; it provides a smartlog to visualize your local commit graph and navigation commands like next and prev to jump through your stack without manual checkouts. Most importantly, it handles the "restack" automatically—if you amend a commit in the middle of your stack, git-branchless instantly rebases all descendant commits to keep the entire chain in sync. If you’re curious about how to implement this without the usual Git friction, Ben Congdon’s article is a fantastic introduction to the philosophy and the tools that make it work.
See also the original epriestly workflow
git-autofixup
To move even faster and keep my history clean, I use git-autofixup to assist in organizing stacked commits. Instead of manually hunting for SHAs to fix up small edits or review feedback, this tool uses git blame to automatically assign unstaged changes to the correct commit in your stack. It essentially turns the tedious process of "tidying up" into a mechanical one, generating the necessary fixup! commits so that a simple git rebase --autosquash can perfectly integrate them. If you’re looking to streamline your workflow and avoid the friction of manual rebasing, Jordan Torbiak’s post on git-autofixup is a great intro to the tool.
My Opinionated .gitconfig, Explained
I've refined my ~/.gitconfig into something that is faster, safer, and produces cleaner output. Here's the whole thing, broken down section by section.
The Full Config
[advice]
defaultBranchName = false
[blame]
coloring = highlightRecent
[alias]
checkout-remote = "!f() { git checkout -b \"$1\" \"origin/$1\"; }; f"
ci = commit
co = checkout
lg = log --graph --oneline --decorate --all
ls = show --pretty= --name-only HEAD
patch = !git --no-pager diff --no-color
resync = "!f() { r=${1:-origin}; git fetch \"$r\" && git reset --hard \"$r/$(git branch --show-current)\"; }; f"
st = status
stashed = stash list --pretty=format:'%gd: %Cred%h%Creset %Cgreen[%ar]%Creset %s'
wip = commit -am 'WIP'
[branch]
autosetuprebase = always
sort = -committerdate
[color]
ui = auto
[commit]
cleanup = scissors
verbose = true
[core]
compression = 9
fsmonitor = true
pager = diff-so-fancy | less --tabs=4 -RFX
preloadindex = true
untrackedCache = true
[diff]
algorithm = histogram
colorMoved = dimmed_zebra
colorMovedWS = allow-indentation-change
context = 10
mnemonicPrefix = true
noprefix = true
renames = copy
[fetch]
fsckobjects = true
parallel = 0
prune = true
pruneTags = true
[gc]
writeCommitGraph = true
[grep]
patternType = perl
[init]
defaultBranch = master
[interactive]
diffFilter = diff-so-fancy --patch
[log]
date = iso
[merge]
conflictStyle = zdiff3
ff = only
keepbackup = false
[pack]
threads = 0
[pager]
diff = diff-so-fancy | less --tabs=1,5 -RFX
log = diff-so-fancy | less --tabs=1,5 -RFX
show = diff-so-fancy | less --tabs=1,5 -RFX
[pull]
ff = only
rebase = true
[push]
autoSetupRemote = true
default = simple
followtags = true
[rebase]
autoStash = true
autosquash = true
missingCommitsCheck = error
updateRefs = true
[receive]
fsckObjects = true
[rerere]
autoupdate = true
enabled = true
[tag]
sort = version:refname
[transfer]
fsckobjects = true
[user]
email = my@email
name = Me
Now let's walk through it.
Silencing Noise
[advice]
defaultBranchName = false
Every time you git init, Git helpfully suggests configuring a default branch name. Once you've set one, you don't need the reminder. This turns it off.
Better Blame
[blame]
coloring = highlightRecent
Recent changes glow brighter, older ones fade into the background. When you git blame a file, your eye is immediately drawn to what changed recently — which is usually what you're investigating.
Aliases That Earn Their Keep
[alias]
checkout-remote = "!f() { git checkout -b \"$1\" \"origin/$1\"; }; f"
ci = commit
co = checkout
lg = log --graph --oneline --decorate --all
ls = show --pretty= --name-only HEAD
patch = !git --no-pager diff --no-color
resync = "!f() { r=${1:-origin}; git fetch \"$r\" && git reset --hard \"$r/$(git branch --show-current)\"; }; f"
st = status
stashed = stash list --pretty=format:'%gd: %Cred%h%Creset %Cgreen[%ar]%Creset %s'
wip = commit -am 'WIP'
The classics — ci, co, st — need no explanation. The interesting ones:
-
lggives you a compact, visual branch graph of your entire repo. It's the first thing I run when switching context to a project. -
checkout-remotecreates a local tracking branch in one command:git checkout-remote feature-x. -
resyncis the nuclear option — it fetches and hard-resets your branch to match the remote. This is what you want with stacked diffs and all force pushes. -
stashedlists your stashes with color-coded hashes, relative timestamps, and messages. Far more readable than the default. -
wipis a quick checkpoint. Stage everything, commit with "WIP", keep moving. Iautosquashthese away later, cherry-pick or amend them. -
patchoutputs a clean diff with no pager or color, ready for piping to a file or another tool.
Branch Behaviour
[branch]
autosetuprebase = always
sort = -committerdate
Every new tracking branch is automatically configured for rebase on pull. Branch listings are sorted by most recently committed, not alphabetically — because "which branch did I touch last?" is almost always the question.
Commits
[commit]
cleanup = scissors
verbose = true
verbose = true shows the full diff in your commit message editor. You can review exactly what you're committing while writing the message.
cleanup = scissors is the real gem. Instead of stripping all lines starting with # (the default), Git uses a scissors marker (-- >8 --) to separate your message from the help text. This means you can freely use # in commit messages — Markdown headings, #1234 issue references at the start of a line — without Git eating them.
Core Performance
[core]
compression = 9
fsmonitor = true
pager = diff-so-fancy | less --tabs=4 -RFX
preloadindex = true
untrackedCache = true
Maximum zlib compression for objects (smaller repos, slightly slower writes — worth it). The filesystem monitor daemon, preloaded index, and untracked file cache all make git status noticeably faster on large repos.
diff-so-fancy as the pager transforms Git's output from "functional" to "beautiful."
Diff Configuration
[diff]
algorithm = histogram
colorMoved = dimmed_zebra
colorMovedWS = allow-indentation-change
context = 10
mnemonicPrefix = true
noprefix = true
renames = copy
This is where I've spent the most time tuning.
-
histogramproduces cleaner, more readable diffs than the default Myers algorithm. -
colorMoved = dimmed_zebrahighlights lines that were moved (not added/removed) with alternating dimmed colors. Combined withcolorMovedWS = allow-indentation-change, it still detects moved code even when you've re-indented it (e.g., wrapping in a new block). These two together make refactoring diffs dramatically easier to review. -
context = 10shows 10 lines of surrounding context instead of the default 3. More context means fewer trips back to the source while reviewing. -
noprefixremoves thea/andb/path prefixes so file paths are directly copy-pasteable.mnemonicPrefixreplaces them with meaningful labels (i/for index,w/for working tree) when prefixes are shown. -
renames = copydetects both file renames and copies.
Fetch: Integrity and Cleanliness
[fetch]
fsckobjects = true
parallel = 0
prune = true
pruneTags = true
Every fetched object is validated for integrity. Stale remote-tracking branches and tags are automatically cleaned up. Fetches from multiple remotes happen in parallel (0 = use all CPUs).
Keeping Things Fast
[gc]
writeCommitGraph = true
[pack]
threads = 0
The commit-graph file dramatically speeds up any operation that walks history — git log, git blame, git merge-base. Repacking uses all available CPU cores.
Grep and Interactive
[grep]
patternType = perl
[interactive]
diffFilter = diff-so-fancy --patch
Perl-compatible regexes for git grep — because life's too short for basic regex. Interactive operations like git add -p also get the diff-so-fancy treatment.
Merge Strategy
[merge]
conflictStyle = zdiff3
ff = only
keepbackup = false
zdiff3 is the best conflict style available. It shows the common ancestor alongside both sides of the conflict and cleans up redundant lines that diff3 leaves behind. It makes conflicts significantly easier to resolve.
Fast-forward only means no surprise merge commits. If the merge can't fast-forward, I want to know about it and make an explicit decision. No .orig backup files cluttering the tree after conflict resolution.
Pull and Push
[pull]
ff = only
rebase = true
[push]
autoSetupRemote = true
default = simple
followtags = true
Pulls rebase by default, and only fast-forward. Pushes automatically set up remote tracking (-u is no longer needed), only push the current branch, and automatically include relevant annotated tags.
Rebase: The Heart of the Workflow
[rebase]
autoStash = true
autosquash = true
missingCommitsCheck = error
updateRefs = true
This is a rebase-centric workflow, and these settings make it seamless:
-
autoStashstashes uncommitted changes before rebase and restores them after. No more "cannot rebase: you have unstaged changes." -
autosquashautomatically processesfixup!andsquash!commits during interactive rebase. Combined with thewipalias, this is how I keep a clean history. -
missingCommitsCheck = erroris a safety net — if you accidentally delete a line during interactive rebase (dropping a commit), Git aborts instead of silently losing work. -
updateRefsautomatically updates stacked branch pointers during rebase. Essential if you work with stacked PRs.
Object Integrity Everywhere
[receive]
fsckObjects = true
[transfer]
fsckobjects = true
Combined with fetch.fsckobjects, this validates every object during every transfer — fetch, push, clone. Belt and suspenders for repository integrity.
Rerere: Reuse Recorded Resolution
[rerere]
autoupdate = true
enabled = true
One of Git's most underrated features. When you resolve a merge conflict, Git remembers the resolution. The next time the same conflict appears (common during repeated rebases), Git applies the same fix automatically and stages it. Once you enable this, you wonder how you ever lived without it.
Tags and Logs
[tag]
sort = version:refname
[log]
date = iso
[init]
defaultBranch = master
Tags sort by semantic version (v2.10 comes after v2.9, not before v2.2). Dates display in ISO 8601 format. New repos default to master.
Pretty Pager Output
[pager]
diff = diff-so-fancy | less --tabs=1,5 -RFX
log = diff-so-fancy | less --tabs=1,5 -RFX
show = diff-so-fancy | less --tabs=1,5 -RFX
diff-so-fancy for diff, log, and show with specific tab settings. Combined with the [color] and [interactive] settings, every piece of Git output that involves diffs looks great.
Final Thoughts
A good .gitconfig is a force multiplier. Most of these settings cost nothing in terms of workflow changes — they just make the defaults smarter. The ones that do change behaviour (rebase-by-default, fast-forward-only, scissors cleanup) are deliberate choices that enforce a cleaner Git history.
Start with the defaults you have, add what makes sense, and revisit every few months. Git keeps shipping useful features that most people never discover.
Honorable Mention: Game of Trees
Check out Game of Trees - the git clone with and ISC license with a well conceived cli - which might be what the doctor orders for many people.
Top comments (0)