Manage Dotfiles Using Bare Git Repo

⚠️ Warning

Everything in this post is outdated. Look for somewhere else.

Set up a Local Bare Git Repo

# Create a bare git repo
git init --bare "$HOME"/.dotfiles

# Config
# "dcf" stanfords for "dotconfig"
dcf() {
    git --git-dir="$HOME"/.dotfiles --work-tree="$HOME" "$@"
}
# Ignore untracked files in $HOME
dcf config --local status.showUntrackedFiles no

# Move untracked dotfiles
mkdir "$HOME"/.dotfiles/zsh
cp "$ZSH_CUSTOM"/zsh/*.zsh "$HOME"/.dotfiles/zsh/
rm "$HOME"/.dotfiles/zsh/example.zsh

# Add files
dcf add "$HOME"/.*shrc
dcf add "$HOME"/.p10k.zsh
dcf add "$HOME"/.vimrc
dcf add "$HOME"/.dotfiles/zsh/*.zsh
# Check status
dcf status
# Be sure to check branch when committing
dcf commit

# For different dotfiles set (Desktop/Server/WSL/Termux)
dcf branch void
dcf checkout void
dcf rm "$HOME"/.dotfiles/zsh/deb.zsh
...
dcf commit

Push to Git Hosting Website

Create a repo in GitHub web (make sure do NOT add a README as this will create a main branch) beforehand.

# Store the key as dcf_rsa
ssh-keygen -t rsa -b 4096
# Copy the pubkey and add as deploy key in the REPO settings
cat ~/.ssh/dcf_rsa.pub | co

# gh: is an predefined alias
dcf remote add origin gh:Vinfall/dotfiles.git
# Add
dcf config core.sshcommand "ssh -i ~/.ssh/dcf_rsa"
# Should set up remote when pushing for the first time
dcf checkout void
dcf push --set-upstream origin void
dcf checkout server
dcf push --set-upstream origin server
...
# No need to set upstream next time
dcf checkout void
dcf push

Restore in a New System

Setup

[another@machine]~$ rsync -avz --ignore-existing -e ssh ~/.ssh/dcf* remote:~/.ssh/

Recover

# Setup GnuPG first, you don't need to do this
rm -rf "$HOME"/.gnupg
mkdir -p "$HOME"/.local/share/gnupg
export GNUPGHOME="$HOME"/.local/share/gnupg
timeout 5 gpg --homedir "$HOME"/.local/share/gnupg
chmod 600 ~/.local/share/gnupg/*
chmod 700 ~/.local/share/gnupg

# Add ignore to avoid weird recursion problems (No need if not created the folders before)
#echo ".dotfiles" >> .gitignore
# Clone
# No need to worry about the global flag, we'll discard .gitconfig before checkouting out the branch anyway
git config --global core.sshCommand "ssh -i ~/.ssh/dcf_rsa"
git clone --bare gh:Vinfall/dotfiles.git "$HOME"/.dotfiles
dcf() {
    git --git-dir="$HOME"/.dotfiles --work-tree="$HOME" "$@"
}

# Backup orig dotfiles in conflict
mkdir -p .config-backup && \
dcf checkout 2>&1 | grep -E "\s+\." | awk '{print $1}' | \
xargs -I{} mv {} .config-backup/{}

# Setup local git config again
dcf config --local status.showUntrackedFiles no
dcf config --local core.sshCommand "ssh -i ~/.ssh/dcf_rsa"
dcf checkout main

# Chores
omz reload
touch "$XDG_CONFIG_HOME"/wgetrc
echo hsts-file \= "$XDG_CACHE_HOME"/wget-hsts >> "$XDG_CONFIG_HOME/wgetrc"
mkdir -p "$XDG_STATE_HOME"/{bash,zsh}
touch "$XDG_STATE_HOME"/{bash,zsh}/history
mkdir -p "$XDG_DATA_HOME"/VSCodium/extensions
# Lingering useless dotfiles
rm .bash_history .gitconfig .viminfo .zshrc

# Desktop
rm -r ~/Downloads
xdg-user-dirs-update
# after reboot
ll "$HOME"/.cache/ICEauthority
rm "$HOME"/.ICEauthority

Improvement

⚠️ Warning

Deprecated.

Use $ZSH_CUSTOM

Use $ZSH_CUSTOM to avoid manually link/copy *.zsh into the custom plugin folder.

Lazy-load Custom Plugin

Merge multiple branches into main is tedious ~~which can be avoided by lazy-loading zsh custom plugins ~~. Although I do not lazy-load plugins, I do write several if statement to make sure dumb zsh doesn’t load unused custom plugins:

# slackware.zsh
# If `os-release` contains slackware
if [[ -f /etc/os-release ]]; then
if cat /etc/os-release | grep --quiet --ignore-case --extended-regexp "slackware"; then

alias new-alias="whatever-command"

fi
fi

# wsl.zsh
# If kernel name contains wsl
if uname -r | grep --quiet --ignore-case --extended-regexp "wsl"; then

alias new-alias="whatever-command"

fi

# termux.zsh
# If $TERMUX_VERSION is not null
if [[ -n $TERMUX_VERSION ]];then

alias new-alias="whatever-command"

fi

Add .gitignore

Add .gitignore to avoid accidentally adding whole $HOME to repo and (again) accidentally stashed innocent dotfiles, which would cause severe damage & take ages to recover w/o backup.

This is not as straightforward as it seems to be:

dcf() {
    git --git-dir="$HOME"/.dotfiles --work-tree="$HOME" "$@"
}

With --git-dir and --work-tree, we should put .gitignore in --work-tree aka $HOME. It’s a bit ugly and actually I wish to switch --work-tree now that I’m aware of XDG Base Directory Specification, so I won’t have yet another file in $HOME.

An example .gitignore(N.B. I don’t version control files inside $XDG_DATA_HOME due to privacy leakage concern, YMMV):

# .gitignore
.cache
.gnupg
.ssh
.config/zsh/.zcompdump*
.config/zsh/oh-my-zsh
.local/share
.local/state

Remove Unused Objects

After managing dotfiles for nearly a month, I notice a lot of orphan objects in .dotfiles/objects/* when running dcf status -u (thanks to the optimization of Lazy-load custom plugin).

Git is so complicated that it can never have too many ways to achieve this. I just cowardly run git repack && git prune-packed to remove most of the unused objects. Now dcf status -u returns few results.

One More Thing

It’s also possible to store the whole restoring commands as a code snippet. Create a short URL and make a call like:

curl -Lks https://short.link/dcf-restore | /bin/bash

Adding the respective git hosting site IP to known_hosts before git clone can have one less manual input.

The code snippet (which I did NOT use as my infrastructure is far more complicated instead of using the same distro I use it now that the methodology is significantly personalized and improved) is redacted since it’s basically a rewrite of #Recover. You just need to split the script into several functions (aka. stages) and consider carefully about variables and files available in each stage.

Reference

Vinfall's Geekademy

Sine īrā et studiō