Dotfile management with Chezmoi and Vim
I have three computers I use on a regular basis, and my Vim-based shell workflow
is heavily dependent on my configuration. Dropbox was my first port of call,
but after some conflicts caused by automatic syncing, I learned that manual
pulling and pushing with Git is much better suited to this problem. Like other
developers, I created a dotfiles repo and used stow to symlink all the files
and folders in my configuration. I thought this was a pretty good setup, but
unresolved bugs would still sometimes appear on my systems.
I trialed chezmoi and found it to be an improvement over my old stow-based
setup. It takes a slightly more deliberate approach to managing dotfiles.
Instead of directly symlinking files from your Git repo into your home
directory, chezmoi manages that repo under the hood and gives you a controlled
“staging area” for changes. This staging area lives in your
.local/share/chezmoi directory and acts as an intermediary between your live
config files and version control. Any time you make changes to your dotfiles,
you explicitly add them to chezmoi — no more accidental commits of stray
files or awkward .gitignore rules sprinkled throughout $HOME.
Once you track your files with chezmoi add, syncing between machines is a
straightforward two-step process. Make local edits, re-add your files with
chezmoi re-add, go to the chezmoi directory with chezmoi cd and commit and
push like you would with any other Git project. Pull remote changes and apply
them with chezmoi update. Even through the “push” step feels a bit verbose,
this workflow makes it harder to overwrite good configs with bad ones and gives
you a cleaner separation between what’s on your machine and what’s stored in the
repo.
chezmoi has made my setup feel more predictable and less error-prone. The
final piece of the puzzle was integrating it with my Vim workflow. I wanted to
be able to edit tracked files, keep syntax highlighting consistent, and even
handle syncing without leaving the editor. With a simple Git alias (git sync)
and a handful of Vim commands, I’ve made chezmoi fit naturally into how I
already work.
In my ~/.gitconfig, I have a handy little alias for committing and pushing
whatever unstaged changes I have:
[alias]
sync = "!f() { \
msg=\"sync: $(whoami)@$(hostname) $(date '+%Y-%m-%d %H:%M:%S')\"; \
git add . && \
git commit -m \"$msg\" && \
git push; \
}; f"Now I can at least push my chezmoi changes with chezmoi git sync, nice!
But we can do better than that. How can we integrate this into Vim? Here’s what
I’ve written in ~/.vim/plugins/dotfile.vim:
" Mappings to jump straight into my configurations:
nnoremap <Leader>; <cmd>edit ~/.vim/vimrc <bar> lcd %:p:h<CR>
nnoremap <Leader>: <cmd>exec 'edit ' . system("chezmoi source-path") <bar> lcd %:p:h<CR>
" Commands to pull/push changes
command! DotPull !chezmoi update
command! DotPush !chezmoi git sync
"" If a tracked file is edited locally, automatically re-add it
"
function! AddChangesToDotfiles()
let current_file = expand('%')
if (system('chezmoi managed ' . current_file) !=? '') && (system('chezmoi diff ' . current_file) != '')
execute 'silent !chezmoi add ' . current_file
echo 'Added to dotfiles'
endif
endfunction
augroup add_changes_to_dotfiles
autocmd!
autocmd BufWritePost * call AddChangesToDotfiles()
augroup END
"" If a tracked file is edited in chezmoi, automatically apply it
"
function! ApplyChangesFromDotfiles()
execute 'silent !chezmoi target-path ' . expand('%') . ' | chezmoi apply'
echo 'Applied from dotfiles'
endfunction
augroup apply_changes_from_dotfiles
autocmd!
autocmd BufWritePost ~/.local/share/chezmoi/* call ApplyChangesFromDotfiles()
augroup END
"" If opening a file in chezmoi, add proper syntax highlighting
" (e.g. highlight `dot_bashrc` as `.bashrc`)
"
function! DotfilesSetFiletype()
let l:realpath = trim(system('chezmoi target-path ' . shellescape(expand('%:p'))))
execute 'setfiletype ' fnameescape(fnamemodify(l:realpath, ':t'))
endfunction
augroup dotfiles_set_filetype
autocmd!
autocmd BufRead,BufNewFile ~/.local/share/chezmoi/* call DotfilesSetFiletype()
augroup ENDIf you haven’t already, I highly encourage you to check out their documentation and quick start