ProbableOdyssey

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 END

If you haven’t already, I highly encourage you to check out their documentation and quick start

Reply to this post by email ↪