My Dotfiles, Annotated


There’s a version of this post that’s just a link to a GitHub repo. This isn’t that version.

What I care about more than the files themselves is the reasoning behind them — why a setting exists, what it replaced, and what I’d do differently. Most dotfile repos are config without commentary. Here’s mine with the commentary.

Git

Git config has the highest return on investment of anything in my dotfiles. It’s the tool I use most, and the defaults are bad.

[core]
  editor = nvim
  pager = delta

[delta]
  navigate = true
  side-by-side = true
  syntax-theme = base16

[diff]
  colorMoved = default

[merge]
  conflictstyle = diff3

[push]
  autoSetupRemote = true
  default = current

[pull]
  rebase = true

[rerere]
  enabled = true

delta is a diff viewer that makes git diff and git log -p actually readable. If you’re still reading diffs in the default unified format, install it.

conflictstyle = diff3 adds a third section to merge conflicts showing the original base content. Without it, you see what both branches want — with it, you also see what both branches changed from. Much easier to resolve.

rerere (Reuse Recorded Resolution) remembers how you resolved a merge conflict and applies the same resolution automatically next time. Invaluable on long-running branches.

push.autoSetupRemote = true means git push on a new branch doesn’t fail with “no tracking branch” — it just sets one up. This should be the default.

Aliases

[alias]
  st  = status -sb
  co  = checkout
  br  = branch
  undo = reset HEAD~1 --mixed
  lg  = log --oneline --graph --decorate --all
  who = shortlog -sn --no-merges
  wip = !git add -A && git commit -m "wip"
  unwip = reset HEAD~1 --mixed

st with -sb gives you a compact status that’s easier to scan than the default. lg gives you a visual branch graph that actually shows you what’s going on. undo is the one I use most — it removes the last commit but keeps the changes staged.

wip and unwip are for context-switching. When I need to switch branches without thinking, I commit everything with a wip message and unwip it later.

Conditional Includes

[includeIf "gitdir:~/work/"]
  path = ~/.config/git/work.inc

Where work.inc is just:

[user]
  email = fraser@company.com
  signingkey = ABCD1234

One config file for personal work, a separate one that overrides it for any repo inside ~/work/. No manual switching, no environment variables.

Zsh

I don’t use Oh My Zsh or Prezto. The value proposition made sense when I started — a curated bundle of plugins, a nice default prompt, active maintenance. But it also means a 500ms shell startup and a pile of code I don’t understand.

My .zshrc is about 120 lines. Here are the parts that earn their place.

# History
HISTSIZE=100000
SAVEHIST=100000
HISTFILE="$HOME/.zsh_history"
setopt HIST_IGNORE_DUPS
setopt HIST_IGNORE_SPACE
setopt SHARE_HISTORY
setopt EXTENDED_HISTORY

HIST_IGNORE_SPACE is the one people don’t know about. Commands prefixed with a space are excluded from history. Useful for one-off commands with secrets, or anything you never want to accidentally re-run.

SHARE_HISTORY means history is shared across all open terminals in real time. I turned this on once and now can’t imagine it off.

# Options
setopt AUTO_CD
setopt CORRECT
setopt NO_BEEP
setopt GLOB_DOTS

AUTO_CD lets you type a directory name without cd. GLOB_DOTS means * matches dotfiles, so ls * doesn’t silently skip your config files.

# Functions
mkcd() { mkdir -p "$1" && cd "$1" }
up()   { cd $(printf '../%.0s' $(seq 1 "${1:-1}")) }

mkcd is the one I reach for every day. up 3 is faster than typing ../../...

Neovim

My Neovim config is Lua, uses lazy.nvim for plugins, and is split across about 15 files. I’m not going to dump the whole thing here — that’s what the repo is for. But a few specific decisions are worth explaining.

Mappings I can’t live without

-- Leader key
vim.g.mapleader = " "

-- Faster buffer navigation
vim.keymap.set("n", "<C-h>", "<C-w>h")
vim.keymap.set("n", "<C-l>", "<C-w>l")
vim.keymap.set("n", "<C-j>", "<C-w>j")
vim.keymap.set("n", "<C-k>", "<C-w>k")

-- Clear search highlight on Escape
vim.keymap.set("n", "<Esc>", "<cmd>nohlsearch<CR>")

-- Keep cursor centered when jumping
vim.keymap.set("n", "<C-d>", "<C-d>zz")
vim.keymap.set("n", "<C-u>", "<C-u>zz")

-- Move selected lines up/down in visual mode
vim.keymap.set("v", "J", ":m '>+1<CR>gv=gv")
vim.keymap.set("v", "K", ":m '<-2<CR>gv=gv")

The <C-d>zz and <C-u>zz ones — centering the cursor after half-page jumps — are the kind of mapping you use for a week and then can’t use a machine without.

Options that matter

vim.opt.scrolloff     = 8       -- Lines of context around cursor
vim.opt.signcolumn    = "yes"   -- Always show sign column (no layout shift)
vim.opt.updatetime    = 50      -- Faster CursorHold triggers
vim.opt.undofile      = true    -- Persistent undo across sessions
vim.opt.splitright    = true    -- Vertical splits go right
vim.opt.splitbelow    = true    -- Horizontal splits go below

undofile = true is the one that surprises people. Neovim writes undo history to disk so you can close a file, reopen it tomorrow, and still undo changes. It’s a genuine superpower.

Plugin philosophy

I keep plugins to a minimum. The ones that have lasted:

  • nvim-lspconfig + mason.nvim — language servers
  • nvim-cmp — completion
  • telescope.nvim — fuzzy finding
  • nvim-treesitter — syntax and text objects
  • mini.surround and mini.comment — editing primitives

I’ve removed lualine, nvim-tree, trouble.nvim, and a few others that I thought I needed. Neovim’s built-in statusline, netrw, and quickfix do the job with less overhead.

Tmux

I use tmux on every remote machine and when I need more than two splits in a terminal.

# Sensible prefix
unbind C-b
set -g prefix C-a
bind C-a send-prefix

# Reload config
bind r source-file ~/.tmux.conf \; display "Reloaded"

# Mouse support
set -g mouse on

# Vim-like pane navigation
bind h select-pane -L
bind j select-pane -D
bind k select-pane -U
bind l select-pane -R

# Keep windows numbered from 1
set -g base-index 1
setw -g pane-base-index 1
set -g renumber-windows on

mouse on is controversial. The argument against it is that it trains you to reach for the mouse. The argument for it is that scrolling through output with a trackpad is just faster than typing [ and navigating. I’ve been on both sides and I’m leaving it on.

renumber-windows on keeps your window list from developing gaps. Closing window 3 of [1, 2, 3, 4] reindexes 4 to 3. Small thing, matters after a long session.

What’s Not In Here

A few deliberate omissions:

Oh My Zsh or any plugin framework. I’ve removed it from every machine and not missed it.

Prettier defaults or editor config files. These belong in projects, not dotfiles. Dotfiles are about your environment; .editorconfig is about the codebase.

Anything in ~/.profile. I use .zshenv for environment variables that need to be available everywhere (like $EDITOR and $PATH). .profile is a POSIX compatibility shim that I’ve never needed.

Automated Homebrew installs. My install.sh symlinks config. It doesn’t install software. A machine that clones my dotfiles should be able to use whatever package manager it has — that’s separate from config.


The full repo is on GitHub. What’s here changes slowly, which I take as a sign it’s mostly right.