Skip to content

Commit

Permalink
Switch from installing dotfiles via lndir to using Git work-tree
Browse files Browse the repository at this point in the history
I've read about installing dotfiles entirely via Git using the --git-dir
and --work-tree flags and this is an experiment to try that approach to
see if I like it.

Previously, I would clone my dotfiles repository somewhere and then
symlink all the files from that directory into my home directory. E.g.,
`lndir $HOME/src/dotfiles $HOME`. The `lndir` package is available in
most distros, sometimes under one of the X11 packages, or installed
along with XQuartz on OS X. I wrote a (much) slower and simpler version
as a shell script for environments where it wasn't available (Termux,
mingw, headless boxes).

I like the symlinks approach quite a bit and I have used it for at least
the last 13 years:

*Pros*:

- It is a very minimalist solution; both simple and straightforward
  without reliance on crazy scripts, directory structures, or
  installation steps.
- I can install it on a fresh system with two commands that are easy to
  remember and type.
- It provides a separation for in-progress files or changes that can get
  symlinked only when ready.
- Working on dotfiles is the same workflow for any Git repository.
- It is not a Git-specific solution and will work with anything that
  maintains a directory of files. (My dotfiles pre-date Git by many
  years. See 882c3ac for reminiscing.)
- It's a nice way to "overlay" files and directories that I care to
  version-control with files I don't care to version-control and want to
  ignore.

*Cons*:

- There are two steps needed to create then install a file (sometimes
  a pro, sometimes a con).
- Old, broken symlinks need to be removed from time to time (easily done
  with `find . -xtype l`).
- I can only think of twice in all this time that having a symlink on
  disk rather than a real file caused a problem, but it has happened.
- While it's possible to compare/contrast the installed files I am
  version-controlling with sibling files that I am not, doing so isn't
  straightforward -- in particular, my `~/tmp` directory tends to
  accumulate cruft (and obviously the normal cruft under `~` as well).

Using a relocated Git work tree is decidedly less simple than symlinks.
That said, Git has become a staple of my daily workflow and I use it to
_think_ every bit as much as I use it manage files and versions. It is
extremely fast for inspecting and updating the file system. It means one
less dependency needed for installation. There's a slight appeal to
managing files directly instead of links to files, and Git's normal
workflows will make it straightforward to identify different kinds of
cruft that will show up as untracked or ignored files. So let's try it.

The changes below are needed as follows:

- My name choice for the global excludesfile conflicts with having
  a dotfiles-specific `.gitignore`, so I need to rename the global.
- The `.gitconfig-customize` file was intended to be per-project and
  I mistakenly anchored that to `$HOME`. This fixes that and allows for
  version-controlling the config that ignores untracked files that is
  a staple of the Git worktree approach -- no post-installation `git
  config --local` changes needed, just clone, checkout, and done.
- I'm going to experiment with ignoring specific, common files in my
  home directory. Obviously ignoring all the noise files that accumulate
  there is impossible, however ignoring some of them will remove some
  noise when manually inspecting ignored files and will help with the
  occasional cruft cleanup efforts.
- I think the usual approach of using a shell alias to access Git
  commands is clunky _at best_. I'd like to avoid it even though that
  means adding a little Zsh/tmux machinery.
- Update various hard-code paths to my dotfiles.

If this experiment doesn't work out then I can revert this commit and it
will have been fun to try something new. :)
  • Loading branch information
whiteinge committed Apr 7, 2023
1 parent 019c68f commit e4ebf44
Show file tree
Hide file tree
Showing 9 changed files with 156 additions and 22 deletions.
10 changes: 4 additions & 6 deletions .gitconfig
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
ref = rev-parse --symbolic-full-name
st = status -s -b
sti = status -s -b --ignored
stu = status -s -b --untracked
stat = diff --stat
sl = stash list --name-status --pretty='format:%gd [%ar]: %s'
uncommit = reset HEAD~1
Expand Down Expand Up @@ -139,16 +140,13 @@
verbose = 2

[core]
excludesfile = ~/.gitignore
excludesfile = ~/.gitexcludesfile

[color]
ui = auto

[init]
# Use full path to dotfiles instead of the path to the symlinks in my
# homedir. This causes the target template files to get copied into new
# repos rather than copying the symlinks to those template files.
templatedir = ~/src/dotfiles/.git_template
templatedir = ~/.git_template

[pager]
# Don't paginate the oneline log output if less than one page.
Expand Down Expand Up @@ -193,4 +191,4 @@

[include]
path = ~/.gitemailconfig
path = ~/.gitconfig-customize
path = .gitconfig-customize
11 changes: 11 additions & 0 deletions .gitconfig-customize
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# This override is specifically for dotfiles in my home directory.

[core]
logallrefupdates = true

[status]
showUntrackedFiles = no

[remote "shouse"]
url = git@github.com:whiteinge/dotfiles.git
fetch = +refs/heads/*:refs/remotes/shouse/*
16 changes: 16 additions & 0 deletions .gitexcludesfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Global ignore file for all repos.

*~
.*swp

*.pyc

.git/
.hg/
.svn/
CVS/

/.ffind

/tags
/*.tags
94 changes: 84 additions & 10 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,10 +1,84 @@
*~
*.pyc
.*swp
.git/
.hg/
.svn/
CVS/
.ffind
/tags
/*.tags
# Local ignores for my installed dotfiles.
# (Paths relative to $HOME.)

# Progs I often compile and install myself:
/bin/cwm
/bin/fzy
/bin/view
/bin/vim
/bin/vimdiff
/bin/vimtutor
/bin/xcape

# Cache files from my own scripts:
/tmp/docs/devdocs.io
/tmp/docs/mthesaur.txt
/tmp/emoji.cache
/tmp/venvs

# Config dirs I won't ever version control:
/.config/Code
/.config/GIMP
/.config/godot
/.config/Google
/.config/google-chrome
/.config/inkscape
/.config/Keybase
/.config/libreoffice
/.config/microsoft-edge
/.config/Microsoft/Microsoft Teams
/.config/mpv/watch_later
/.config/Mullvad VPN
/.config/pulse
/.config/wslu

# Vim temp files:
/.vim/swapdir
/.vim/undodir

# Misc UNIX staples I don't care about:
/.dbus
/.gnome
/.gnupg
/.java
/.pki

# Third party progs I don't care to see:
/.android
/.audacity-data
/.aws-cli
/.cache
/.cargo
/.docker
/.dotnet
/.local/share/gnome-shell
/.local/share/Steam
/.local/share/virtualenv
/.mozilla
/.nodejs
/.npm
/.slocdata
/.steam
/.var/app/us.zoom.Zoom
/.vscode-server
/.zoom

# Stupid, annoying, terrible path defaults:
/go
/snap

# Common OS dirs:
/Applications
/Desktop
/Documents
/Downloads
/Games
/Mail
/Movies
/Music
/Pictures
/Steam
/Videos

/share
/src
35 changes: 35 additions & 0 deletions .zshrc
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,11 @@ if [[ ! -n "$ZSHRUN" ]]; then
'$(test ${+HISTFILE} -eq 0 && echo !!)' \
'%{${reset_color}%}' \

# In dotfiles mode?
'%{${fg[yellow]}%}' \
'$(test -n "$GIT_WORK_TREE" && echo ..)' \
'%{${reset_color}%}' \

# Any background jobs?
'%(1j.%j .)' \

Expand Down Expand Up @@ -317,6 +322,36 @@ if [[ -n "$ZSHRUN" ]]; then
RPROMPT=""
fi

# }}}
# dotfiles Toggle Git dir and work-tree env vars {{{1
#
# I don't want the usual `--git-dir=<dir> --work-tree=<dir>` alias to work on
# my dotfiles; I'd rather stick with the vanilla `git` command for the muscle
# memory and tmux status display. This function turns "dotfiles mode" on and
# off by setting and unsetting env vars instead.
#
# Steps to move into a new machine:
# 1. git clone --bare <dotfiles-url> $HOME/src/dotfiles.git
# 2. git config remote.origin.fetch 'refs/heads/*:refs/heads/*'
# 3. git --git-dir=$HOME/src/dotfiles.git --work-tree=$HOME checkout
# 4. Run necessary bin/bootstrap-foo scripts from there.

dotfiles () {
if [[ "$GIT_WORK_TREE" = "$HOME" ]]; then
unset GIT_DIR
unset GIT_WORK_TREE
tmux set-environment -g -u GIT_DIR
tmux set-environment -g -u GIT_WORK_TREE
tmux refresh -S
else
export GIT_DIR="${HOME}/src/dotfiles.git"
export GIT_WORK_TREE="$HOME"
tmux set-environment -g GIT_DIR "$GIT_DIR"
tmux set-environment -g GIT_WORK_TREE "$GIT_WORK_TREE"
tmux refresh -S
fi
}

# }}}
# Use a fuzzy-finder for common CLI tasks {{{1

Expand Down
2 changes: 1 addition & 1 deletion bin/bootstrap-fedora
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ dnf install -y \
dnf install -y rpmfusion-free-release-tainted

# Install baseline packages.
xargs dnf install -y < $HOME/src/dotfiles/tmp/config/packages-fedora
xargs dnf install -y < $HOME/tmp/config/packages-fedora

# Enable laptop-friendly services.
systemctl enable systemd-backlight@backlight:intel_backlight.service
Expand Down
5 changes: 2 additions & 3 deletions bin/bootstrap-osx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ curl -L https://yt-dl.org/downloads/latest/youtube-dl -o /usr/local/bin/youtube-
chmod +x /usr/local/bin/youtube-dl

# Install the baseline packages.
xargs /usr/local/bin/brew install < $HOME/src/dotfiles/tmp/config/packages-brew
xargs /usr/local/bin/brew install < $HOME/tmp/config/packages-brew

printf '
android-platform-tools
Expand All @@ -33,12 +33,11 @@ printf 'Adding zsh to /etc/shells (enter password):\n'
sudo printf '/usr/local/bin/zsh\n' >> /etc/shells
chsh -s /usr/local/bin/zsh

/usr/X11/bin/lndir -silent $HOME/src/dotfiles $HOME
( cd $HOME; ln -s .zsh_customize-brew .zsh_customize; )
( cd $HOME/bin; ln -s osx-notify-send notify-send; ln -s osx-xclip xclip; )

# Install script that toggles Wifi on and off.
$HOME/src/dotfiles/bin/osx-toggle-interfaces install_plist
$HOME/bin/osx-toggle-interfaces install_plist

printf '
All set (hopefully).
Expand Down
2 changes: 1 addition & 1 deletion bin/bootstrap-wsl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Bootstrap a WSL Ubuntu install

# Install baseline packages.
xargs apt install -y < $HOME/src/dotfiles/tmp/config/packages-wsl
xargs apt install -y < $HOME/tmp/config/packages-wsl

update-locale LANG=en_US.utf8

Expand Down
3 changes: 2 additions & 1 deletion bin/genreadme
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#!/bin/sh
# Generate a README for all $DOTFILES/bin scripts

find "${HOME}/src/dotfiles/bin" -type f -print0 |
# TODO: include staged or untracked files here?
git --git-dir=$HOME/src/dotfiles.git ls-files -z -- bin |
xargs -0 -r -n1 awk '
FILENAME ~ "README" { next }
/^#?[a-zA-Z ]+/ {
Expand Down

0 comments on commit e4ebf44

Please sign in to comment.