DEV Community

Dean Hamstead
Dean Hamstead

Posted on

Making Git tolerable

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
Enter fullscreen mode Exit fullscreen mode

Now let's walk through it.


Silencing Noise

[advice]
    defaultBranchName = false
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

The classics — ci, co, st — need no explanation. The interesting ones:

  • lg gives you a compact, visual branch graph of your entire repo. It's the first thing I run when switching context to a project.
  • checkout-remote creates a local tracking branch in one command: git checkout-remote feature-x.
  • resync is 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.
  • stashed lists your stashes with color-coded hashes, relative timestamps, and messages. Far more readable than the default.
  • wip is a quick checkpoint. Stage everything, commit with "WIP", keep moving. I autosquash these away later, cherry-pick or amend them.
  • patch outputs a clean diff with no pager or color, ready for piping to a file or another tool.

Branch Behaviour

[branch]
    autosetuprebase = always
    sort = -committerdate
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

This is where I've spent the most time tuning.

  • histogram produces cleaner, more readable diffs than the default Myers algorithm.
  • colorMoved = dimmed_zebra highlights lines that were moved (not added/removed) with alternating dimmed colors. Combined with colorMovedWS = 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 = 10 shows 10 lines of surrounding context instead of the default 3. More context means fewer trips back to the source while reviewing.
  • noprefix removes the a/ and b/ path prefixes so file paths are directly copy-pasteable. mnemonicPrefix replaces them with meaningful labels (i/ for index, w/ for working tree) when prefixes are shown.
  • renames = copy detects both file renames and copies.

Fetch: Integrity and Cleanliness

[fetch]
    fsckobjects = true
    parallel = 0
    prune = true
    pruneTags = true
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

This is a rebase-centric workflow, and these settings make it seamless:

  • autoStash stashes uncommitted changes before rebase and restores them after. No more "cannot rebase: you have unstaged changes."
  • autosquash automatically processes fixup! and squash! commits during interactive rebase. Combined with the wip alias, this is how I keep a clean history.
  • missingCommitsCheck = error is a safety net — if you accidentally delete a line during interactive rebase (dropping a commit), Git aborts instead of silently losing work.
  • updateRefs automatically updates stacked branch pointers during rebase. Essential if you work with stacked PRs.

Object Integrity Everywhere

[receive]
    fsckObjects = true
[transfer]
    fsckobjects = true
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)