Multi-Machine Dotfiles Management with Git Worktrees
A comprehensive guide to managing dotfiles across multiple machines using git worktrees and automatic deployment hooks.
Overview
This workflow allows you to:
- Maintain separate configurations for different machines (darknova, darkstar, darkspacer, etc.)
- Work directly on your system in
$HOMEand commit changes - Automatically deploy changes when committing from worktrees
- Keep everything synced via git
- Have a clean worktree copy for each machine
Architecture
Components
- Bare Repository:
~/.dotfiles(the central git repository) - Worktrees:
~/dotfiles-{hostname}(one per machine) - Working Directory:
$HOME(where you actually use your dotfiles) - Deploy Script:
.deploy.sh(syncs worktree → $HOME) - Git Hooks:
.githooks/(triggers deployment automatically)
Directory Structure
1
2
3
4
5
6
~/.dotfiles/ # Bare repository
~/dotfiles-darknova/ # Worktree for darknova machine
~/dotfiles-darkstar/ # Worktree for darkstar machine
~/dotfiles-darkspacer/ # Worktree for darkspacer machine
~/dotfiles-darkspacer2/ # Worktree for darkspacer2 machine
~/ # Your actual working files
Initial Setup
On a New Machine
- Clone the bare repository:
1
git clone --bare git@github.com:YOUR-USERNAME/dotfiles.git ~/.dotfiles - Configure fetch to get all branches:
1 2 3
cd ~/.dotfiles git config remote.origin.fetch '+refs/heads/*:refs/remotes/origin/*' git fetch origin
- Set up git hooks path:
1
git config core.hooksPath .githooks
- Create worktree for your machine (example for darknova):
1
git worktree add ~/dotfiles-darknova darknova
- Set branch to track remote:
1
git branch --set-upstream-to=origin/darknova darknova
- Pull to trigger initial deployment:
1 2
cd ~/dotfiles-darknova git pullThis will deploy all your dotfiles to
$HOME, including the deploy script and hooks. - Set up the dotfiles command (add to
~/.config/fish/config.fishor~/.bashrc):# Fish shell function dotfiles /usr/bin/git --git-dir=$HOME/.dotfiles --work-tree=$HOME $argv end1 2
# Bash alias dotfiles='/usr/bin/git --git-dir=$HOME/.dotfiles --work-tree=$HOME'
How It Works
The Deploy Script
Each branch has a .deploy.sh script that:
- Checks if the current hostname matches the branch name
- If match: uses
rsyncto copy files from worktree to$HOME - Excludes
.git/and the worktree directory itself - Includes
.deploy.shand.githooks/(so they’re also in$HOME)
Example for darknova:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/bin/bash
INTENDED_HOSTNAME="darknova"
CURRENT_HOSTNAME=$(hostname)
WORKTREE_DIR="$HOME/dotfiles-darknova"
TARGET_DIR="$HOME"
# Only deploy if we're on the correct machine
if [[ "$CURRENT_HOSTNAME" != "$INTENDED_HOSTNAME" ]]; then
echo "Skipping deployment: This is $CURRENT_HOSTNAME, not $INTENDED_HOSTNAME"
exit 0
fi
rsync -av \
--exclude='.git' \
--exclude='dotfiles-darknova' \
"$WORKTREE_DIR/" \
"$TARGET_DIR/"
The Git Hooks
Three hooks automatically trigger the deploy script:
- post-commit: After every commit in the worktree
- post-checkout: When switching branches in the worktree
- post-merge: After pulling/merging in the worktree
Example post-commit:
1
2
3
4
5
6
7
#!/bin/bash
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
if [ -f "$SCRIPT_DIR/.deploy.sh" ]; then
echo "Deploying dotfiles..."
"$SCRIPT_DIR/.deploy.sh"
fi
Daily Workflow
Making Changes on Your Machine
You have two equivalent workflows:
Workflow A: Work in $HOME (Recommended)
- Edit files directly in $HOME:
1 2
nvim ~/.config/nvim/init.lua nvim ~/.bashrc
- Check what changed:
1
dotfiles status
- Commit changes:
1 2
dotfiles add .config/nvim/init.lua dotfiles commit -m "update nvim config"
- Push to origin:
1
dotfiles push
- Sync the worktree (optional, for clean copy):
1 2
cd ~/dotfiles-darknova git pull
Workflow B: Work in Worktree
- Edit files in the worktree:
1 2
cd ~/dotfiles-darknova nvim .config/nvim/init.lua - Commit changes:
1 2
git add .config/nvim/init.lua git commit -m "update nvim config"
The
post-commithook automatically deploys to$HOME! - Push to origin:
1
git push
Syncing Changes from Other Machines
When you push changes from another machine:
- Pull in the worktree:
1 2
cd ~/dotfiles-darknova git pullThe
post-mergehook automatically deploys to$HOME! - Verify with dotfiles command:
1
dotfiles status
Should show: “nothing to commit”
Common Operations
Check Status
1
2
3
4
5
6
# Check what's different in $HOME
dotfiles status
# Check worktree status
cd ~/dotfiles-darknova
git status
View Changes
1
2
3
4
5
6
# See what changed in $HOME
dotfiles diff
# See changes in worktree
cd ~/dotfiles-darknova
git diff
List Worktrees
1
2
cd ~/.dotfiles
git worktree list
Output:
1
2
3
/home/user/.dotfiles (bare)
/home/user/dotfiles-darknova abc1234 [darknova]
/home/user/dotfiles-darkstar def5678 [darkstar]
Create a New Machine Branch
On your main machine:
- Create new branch from existing:
1 2
cd ~/dotfiles-darknova git checkout -b newmachine
- Customize for the new machine:
1 2 3 4 5 6
# Update hostname in .deploy.sh sed -i 's/INTENDED_HOSTNAME="darknova"/INTENDED_HOSTNAME="newmachine"/' .deploy.sh sed -i 's/dotfiles-darknova/dotfiles-newmachine/' .deploy.sh git add .deploy.sh git commit -m "configure deploy for newmachine"
- Push the new branch:
1
git push -u origin newmachine - On the new machine: Follow the “Initial Setup” steps using the new branch name.
Manually Deploy
If you need to manually trigger deployment:
1
2
cd ~/dotfiles-darknova
./.deploy.sh
Remove a Worktree
1
2
3
git worktree remove dotfiles-oldmachine
git branch -d oldmachine # Delete local branch
git push origin --delete oldmachine # Delete remote branch
Troubleshooting
Hooks Not Triggering
Check if hooks path is configured:
1
2
cd ~/.dotfiles
git config --get core.hooksPath
Should output: .githooks
If not set:
1
git config core.hooksPath .githooks
Deployment Skipped (Wrong Hostname)
The deploy script checks hostname vs INTENDED_HOSTNAME. Verify:
1
2
hostname
# Should match your branch name (e.g., "darknova")
If hostname doesn’t match, either:
- Change your system hostname
- Update
INTENDED_HOSTNAMEin.deploy.sh
dotfiles status Shows Deletions
This happens if files exist in the branch but not in $HOME. Usually means deployment didn’t run. Fix:
1
2
3
cd ~/dotfiles-darknova
./.deploy.sh # Manually deploy
dotfiles status # Should be clean now
Merge Conflicts Between Machines
If you edit the same file on two machines:
- Pull changes in worktree:
1 2
cd ~/dotfiles-darknova git pull - If conflicts occur, resolve them:
1 2 3 4 5 6
# Edit conflicted files nvim .config/nvim/init.lua # Mark as resolved git add .config/nvim/init.lua git commit
- Deploy the resolved version:
1
./.deploy.sh
Advanced Tips
Ignore Machine-Specific Files
Add to .gitignore in each branch:
1
2
3
.venv/
.cache/
*.local
Backup Before Deployment
Modify .deploy.sh to create backups:
1
2
3
4
# Add before rsync
BACKUP_DIR="$HOME/.dotfiles-backup-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$BACKUP_DIR"
cp -r "$TARGET_DIR/.config" "$BACKUP_DIR/" 2>/dev/null || true
Dry-Run Deployment
Test what would be deployed:
1
2
3
4
5
rsync -avn \
--exclude='.git' \
--exclude='dotfiles-darknova' \
~/dotfiles-darknova/ \
~/
Share Common Config Between Machines
Create a “common” branch with shared configs, then merge it into machine branches:
1
2
3
cd ~/dotfiles-darknova
git merge common
git push
Summary
This workflow gives you:
- Per-machine customization: Each machine has its own branch
- Automatic deployment: Hooks deploy changes automatically
- Flexible editing: Work in
$HOMEor worktrees - Version control: Full git history for all configs
- Sync across machines: Push/pull to share changes
The key insight: The worktree holds the “source of truth” for each machine, and the deploy script keeps $HOME in sync. The dotfiles command lets you work directly in $HOME for convenience.