My Dotfiles Repo Has Saved Me More Time Than Any Framework I've Ever Learned
I've rewritten entire side projects from scratch more times than I can count, thrown away frameworks after a weekend of frustration, abandoned half-finished tools when the excitement wore off. But there's one repository that's been alive, continuously, for almost six years now, getting a commit or two nearly every month, and it's not a product, it's not open source in any meaningful sense — it's my dotfiles. It's the least glamorous project I own and easily the one with the best return on time invested.
How it started, which is to say, badly
My first "dotfiles setup" wasn't a repo at all. It was a single .zshrc file I'd manually scp'd between machines whenever I remembered to, usually right after losing three hours re-configuring a new laptop and swearing I'd never let that happen again. I'd forget an alias I depended on daily, forget which npm globals I actually needed, forget that one git config flag that made rebases sane. Every new machine was an archaeology project into my own past habits.
The turning point was setting up a machine for a contract gig with a hard deadline the same day. I had exactly zero patience for reconstructing my environment from memory, and that frustration is what pushed me to actually put everything in a git repo with a real install script instead of a folder of loose files I copied by hand.
What's actually in there
The repo is structured pretty plainly — a directory per tool, plus a bootstrap script:
dotfiles/
├── zsh/
│ ├── .zshrc
│ ├── aliases.zsh
│ └── functions.zsh
├── git/
│ └── .gitconfig
├── nvim/
│ └── init.lua
├── vscode/
│ └── settings.json
├── install.sh
└── Brewfile
The install.sh script is deliberately boring. It checks for Homebrew, installs it if missing, runs brew bundle against the Brewfile to pull every CLI tool and app I use, then symlinks everything into place:
#!/usr/bin/env bash
set -euo pipefail
DOTFILES_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
command -v brew >/dev/null 2>&1 || \
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew bundle --file="$DOTFILES_DIR/Brewfile"
link() {
local src="$DOTFILES_DIR/$1"
local dest="$HOME/$2"
mkdir -p "$(dirname "$dest")"
ln -sfn "$src" "$dest"
echo "linked $dest -> $src"
}
link "zsh/.zshrc" ".zshrc"
link "git/.gitconfig" ".gitconfig"
link "nvim/init.lua" ".config/nvim/init.lua"
link "vscode/settings.json" "Library/Application Support/Code/User/settings.json"
echo "done. restart your shell."
Symlinks, not copies, are the whole trick here. If I edit ~/.zshrc, I'm actually editing the file inside the repo, because it's just a link pointing there. That means changes I make in the moment — half-distracted, trying to fix a broken PATH at 11pm — get captured automatically the next time I run git status in the dotfiles directory, instead of living only on that one machine until I remember to manually port them over.
The aliases and functions that actually earn their keep
Most alias lists you find online are full of stuff nobody actually uses day to day — alias ll='ls -la' and not much else of substance. The ones that have survived years of pruning in my file are much more specific to how I actually work. A few examples that get used dozens of times a day:
jump to the git root, regardless of how deep I am in a monorepo
alias root='cd $(git rev-parse --show-toplevel)'
clean up merged local branches
alias gclean='git branch --merged | grep -v "\*\|main\|master" | xargs -n 1 git branch -d'
open the current dir's package.json scripts as a fzf picker
run() {
local script
script=$(jq -r '.scripts | keys[]' package.json | fzf --prompt="script> ")
[ -n "$script" ] && npm run "$script"
}
quick worktree creation for parallel PR review
gwt() {
git worktree add "../$(basename "$PWD")-$1" -b "$1"
cd "../$(basename "$PWD")-$1"
}
That run function alone probably saves me a minute a dozen times a day — no more cat package.json | grep scripts followed by squinting at a wall of JSON. The gwt function came directly out of reviewing pull requests: instead of stashing my work-in-progress to check out a colleague's branch, I spin up a worktree in a sibling directory and open it in a new Cursor window, leaving my actual working directory untouched.
My .gitconfig carries its own small history of pain points solved:
[alias]
lg = log --graph --pretty=format:'%C(yellow)%h%Creset -%C(red)%d%Creset %s %C(green)(%cr) %C(bold blue)<%an>%Creset'
amend = commit --amend --no-edit
undo = reset --soft HEAD~1
[pull]
rebase = true
[rerere]
enabled = true
That rerere.enabled = true line is easy to overlook but has saved me from re-resolving the same merge conflict twice more times than I'd like to admit, especially on long-lived feature branches that get rebased repeatedly against main.
What I deliberately keep out
The temptation with a dotfiles repo is to dump everything into it, and I've learned the hard way that's a mistake. Anything with a secret in it — API tokens, SSH private keys, npm auth tokens — stays out entirely, obviously, but it's worth saying explicitly because it's an easy slip when you're moving fast and just want a working environment. I use a .zshrc.local file that's git-ignored for anything machine-specific or sensitive, and my main .zshrc sources it conditionally at the end:
[ -f "$HOME/.zshrc.local" ] && source "$HOME/.zshrc.local"
I also stopped tracking full application state — things like VS Code's entire extensions list synced automatically through the built-in Settings Sync now, so duplicating that in my dotfiles was redundant and just another thing to keep from drifting out of sync. And I gave up on tracking anything related to a specific client or employer; that stuff belongs in that org's own onboarding docs, not my personal history that follows me between jobs.
The actual payoff
The real test came last year when I bought a new machine and needed to be productive by that afternoon. Homebrew install, clone the dotfiles repo, run ./install.sh, restart the terminal. Fifteen minutes, and my shell prompt, my git aliases, my Neovim config, and my editor settings were all exactly how I left them, because they always were how I left them — there was no "setting up," there was just symlinking the same files back into a new location.
What I didn't expect when I started this was how much the repo would function as a diary of how I actually work, not how I think I work. Looking at the commit history is genuinely interesting — I can see exactly when I switched from Oh My Zsh to a plain .zshrc because I got tired of debugging someone else's framework on top of my shell, when I picked up fzf and how many aliases got rewritten to pipe through it afterward, when I finally admitted vim muscle memory wasn't going anywhere and moved fully into Neovim instead of fighting it. If you don't have a dotfiles repo yet, my honest suggestion isn't to go copy someone else's elaborate 400-line .zshrc off GitHub. Start with the five aliases you type most often today, put them in a repo, symlink them, and let it grow slowly and honestly from there. The value isn't in having a comprehensive setup on day one, it's in never having to rebuild the same five minutes of muscle memory from scratch again.
Written by AIDToolStack Editorial
The AIDToolStack editorial team reviews developer tools, AI products, and MCP servers with hands-on testing and honest assessments. We use the tools we review every day in production.
References & Further Reading
Related Posts
Sponsor Our Newsletter
Reach thousands of developers who are actively evaluating AI tools, MCP servers, and dev infrastructure. Our weekly newsletter goes to engaged technical decision-makers.
All sponsored content is clearly labeled per our editorial policy.