Configuration file synchronization across multiple machines using git branches
Drifters helps you keep your application configurations synchronized across all your machines (macOS, Linux, Windows) using Git as the transport layer. Each machine gets its own git branch, giving you full control over when and how configs are merged.
- 🌿 Branch-per-machine - Each machine has its own git branch; merge to main explicitly
- 🔀 Git-native merging - Uses
git merge+git mergetoolfor real conflict resolution - 🎯 Selective Sync - Use glob patterns and section tags for fine-grained control
- 🖥️ Multi-OS Support - OS-specific configurations with fallbacks
- 🔒 Per-Machine Overrides - Exclude files or sections on specific machines
- 🚫 Singular Machines - Machines that never merge to main (private configs)
- 📦 No Daemon - Manual control over when configs sync (with optional auto-pull hook)
- 🚀 Fast & Safe - Written in Rust with built-in safety checks
- 📝 Section Tags - Exclude sensitive or machine-specific sections within files
- ⚡ Ephemeral Repository - Cloned fresh on each command, zero persistence
# From GitHub releases (recommended)
curl -sSL https://github.com/tjirsch/drifters/releases/latest/download/drifters-installer.sh | sh
# From source
git clone https://github.com/tjirsch/drifters
cd drifters
cargo install --path .
# From crates.io (coming soon)
cargo install drifters# Check for updates
drifters self-update --check-only
# Install latest release
drifters self-updateDrifters automatically checks for updates on most commands (configurable). Updates are installed via the same installer script used for initial installation.
- Git repository for storing configs (e.g., GitHub, GitLab)
- SSH keys configured for your Git hosting service
# Create a GitHub repo first, then:
drifters init git@github.com:username/my-configs.git
# Add an app to sync (interactive)
drifters add-app zed
# Enter file patterns: ~/.config/zed/settings.json
# Push configs to your machine's branch
drifters push-app
# Merge your branch into main (so other machines can pull)
drifters merge-app# Clone and set up (creates a new machine branch)
drifters init git@github.com:username/my-configs.git
# Pull configs from main
drifters pull-app
# Push your configs to your machine's branch
drifters push-appMark sections of files that should NOT be synced:
// ~/.config/zed/settings.json
{
"theme": "One Dark",
"vim_mode": true,
// drifters::exclude::start
"working_directory": "/Users/thomas/projects",
"recently_opened": [...]
// drifters::exclude::stop
}Everything outside the exclude tags gets synced. The exclude sections stay local to each machine.
Tag placement rules:
- Tags must be on their own line — inline tags (after other content) are not recognized
- Leading whitespace before the comment character is allowed:
# drifters::exclude::start✅ - The comment character must match the file type (auto-detected from extension; see Supported Comment Styles)
Rules are resolved in this order:
- Machine-specific overrides (highest priority)
- OS-specific rules
- App defaults (base configuration)
# .drifters/sync-rules.toml
[apps.zed]
# Base rules (all platforms)
include = ["~/.config/zed/settings.json"]
# macOS-specific
include-macos = ["~/Library/Application Support/Zed/settings.json"]
# Machine override
[apps.zed.machines.laptop]
exclude = ["**/keymap.json"] # Different keyboard
singular = true # Never merge this machine's branch into main
[apps.claude-code]
include = ["~/.claude/*"]
no_merge = true # This app stays on machine branches, never merged to mainEach machine operates on its own git branch (machines/<machine_id>):
- push-app — pushes local configs to your machine's branch
- merge-app — merges your branch into main (uses git's native merge; launches mergetool on conflicts)
- pull-app — pulls from main (or
--from <machine>for a specific machine's branch)
Machines marked singular: true in sync-rules.toml can push and pull but merge-app refuses to merge them into main — useful for private/experimental configs.
Apps marked no_merge = true in sync-rules.toml are automatically excluded from full-branch merges. When you specify an app name (merge-app zed), only that app's files are merged selectively.
| Command | Description |
|---|---|
drifters init <repo-url> |
Initialize drifters on a machine |
| App management | |
drifters add-app <app> |
Add an app to sync (interactive) |
drifters remove-app <app> |
Remove this machine's configs for an app |
drifters remove-app <app> --machine <id> |
Remove a specific machine's configs |
drifters remove-app <app> --all |
Remove an app from all machines entirely |
drifters rename-app <old> <new> |
Rename an app everywhere in the repo |
| Sync | |
drifters push-app [app] |
Push local configs to your machine's branch |
drifters pull-app [app] |
Pull configs from main |
drifters pull-app [app] --from <machine> |
Pull from a specific machine's branch |
drifters pull-app [app] --dry-run |
Show what would change without applying |
drifters merge-app [app] |
Merge your machine branch into main (selective if app specified) |
drifters merge-app --from <machine> |
Merge another machine's branch into main |
drifters merge-app --dry-run |
Preview merge without applying |
| Config | |
drifters edit-config |
Open local drifters config file in your editor |
drifters edit-app-files <app> |
Open one of an app's config files in your editor |
drifters diff-app [app] |
Show diff against main |
drifters diff-app [app] --against <branch> |
Show diff against a specific branch |
drifters status |
Show per-file sync status |
drifters exclude-app <app> <file> |
Exclude a file on this machine |
| Listing | |
drifters list-app [app] |
List all configured apps (or details for one) |
drifters list-rules |
Print current sync-rules.toml |
| Machine management | |
drifters rename-machine <old> <new> |
Rename a machine everywhere in the repo |
drifters remove-machine <id> |
Remove a machine and delete its configs |
| Import/Export | |
drifters import-app <name> [--file <path>] |
Import app from file (defaults to ./.toml) |
drifters export-app <name> [--file <path>] |
Export app to file (defaults to ./.toml) |
drifters import-rules [--file <path>] |
Import rules (defaults to ./sync-rules.toml) |
drifters export-rules [--file <path>] |
Export rules (defaults to ./sync-rules.toml) |
| Presets | |
drifters list-presets |
List available presets from GitHub |
drifters load-preset <name> |
Load preset from GitHub repo |
drifters discover-presets |
Auto-detect installed apps and offer to add matching presets |
| History | |
drifters history rules |
Show history of sync rules |
drifters history app <name> |
Show history of app definition |
drifters restore rules --commit <hash> |
Restore previous rules version |
drifters restore app <name> --commit <hash> |
Restore previous app version |
| Automation | |
drifters hook |
Generate shell hook for auto-pull |
drifters self-update |
Check for and install updates from GitHub |
drifters self-update --check-only |
Check for updates without installing |
drifters self-update --no-download-readme |
Install without downloading README |
drifters self-update --no-open-readme |
Download README but do not open it |
drifters open-readme |
Download latest README and open it |
drifters completion <shell> |
Print shell completion script to stdout |
drifters completion <shell> --install |
Install completion script to default location |
drifters set-editor <editor> |
Set preferred editor in drifters.toml |
drifters set-editor --clear |
Clear the preferred editor setting |
drifters set-editor |
Show current preferred editor setting |
drifters edit-rules |
Open sync-rules.toml in your editor and optionally save to the repository |
drifters unlock |
Force-remove a stale lock file left behind after a crash or Ctrl-C |
Merges a machine branch into main:
# Merge all apps (full git merge)
drifters merge-app
# Merge only a specific app (selective — copies that app's files from your branch)
drifters merge-app zed
# Preview merge without applying
drifters merge-app --dry-run
# Merge a specific machine's branch
drifters merge-app --from mac01On full merges, drifters uses git merge and launches git mergetool on conflicts. On selective merges (with app name), the app's files are copied from the machine branch wholesale.
Apps with no_merge = true in sync-rules.toml are automatically excluded from full-branch merges. When no_merge apps exist, merge-app (without an app name) merges only the remaining apps selectively.
-v, --verbose- Show detailed logging-V, --version- Print version and exit
User-level parameters live in ~/.config/drifters/drifters.toml. This file is created automatically on first run with default values. If it is missing it is recreated with defaults.
| Option | Default | Description |
|---|---|---|
self_update_frequency |
"always" |
When to auto-check for updates: never, always, or daily (at most once per 24 hours). The check is check-only — no install, no README. |
editor |
(none) | Editor command used to open files (e.g. "zed", "code", "vim"). Falls back to $EDITOR env var, then the OS default app. |
Example (optional; the file is created automatically):
self_update_frequency = "daily"
editor = "zed"Use drifters set-editor <editor> to set the editor from the command line instead of editing the file directly.
Generate tab-completion for your shell (bash, zsh, fish, powershell):
# On macOS: no arguments needed — defaults to zsh + --install
drifters completion
# → installs to ~/.zsh/completions/_drifters
# Print to stdout and add manually
drifters completion bash >> ~/.bash_completion
drifters completion zsh >> ~/.zshrc
# Auto-install to the canonical shell location
drifters completion zsh --install
# → installs to ~/.zsh/completions/_drifters
# → prints fpath setup instructions
drifters completion fish --install
# → installs to ~/.config/fish/completions/drifters.fishInstall locations for --install:
| Shell | Path |
|---|---|
| bash | ~/.local/share/bash-completion/completions/drifters |
| zsh | ~/.zsh/completions/_drifters |
| fish | ~/.config/fish/completions/drifters.fish |
| powershell | %USERPROFILE%\Documents\PowerShell\Completions\drifters.ps1 |
For zsh, add this to ~/.zshrc if not already present:
fpath=(~/.zsh/completions $fpath)
autoload -Uz compinit && compinit# .drifters/sync-rules.toml
[apps.zed]
# Glob patterns supported
include = [
"~/.config/zed/settings.json",
"~/.config/zed/keymap.json",
"~/.config/zed/**/*.json",
]
exclude = [
"~/.config/zed/workspace-*.json", # Session-specific
"~/.config/zed/cache/**", # Temporary files
]
# macOS-specific paths
include-macos = [
"~/Library/Application Support/Zed/settings.json",
]
# Per-machine overrides
[apps.zed.machines.laptop]
exclude = ["**/keymap.json"] # Laptop has different keyboard" ~/.vimrc
set number
set expandtab
" drifters::exclude::start
" Machine-specific plugins
call plug#begin('~/.local/share/nvim/plugged')
Plug 'local-only-plugin'
call plug#end()
" drifters::exclude::stop# ~/.zshrc
alias g="git"
alias d="docker"
# drifters::exclude::start
export PROJECT_PATH="/Users/thomas/dev"
export LOCAL_BIN="/usr/local/custom/bin"
# drifters::exclude::stop[apps.zed.machines.laptop]
exclude = ["**/keymap.json"] # Laptop uses built-in keyboard
[apps.zed.machines.desktop]
# Desktop uses external mechanical keyboard - sync keymapsAdd to your .zshrc or .bashrc:
eval "$(drifters hook)"Runs drifters pull-app in background on shell startup. Changes applied silently.
Note: Pushes are always manual for safety.
Check out presets/ for pre-configured definitions:
- Claude Code - Anthropic's CLI for Claude (global instructions, rules, commands)
- Cursor - AI-powered code editor built on VS Code
- Visual Studio Code - Popular code editor with settings, keybindings, and snippets
- Windsurf - Codeium's agentic code editor
- Zed Editor - Modern code editor
- More coming soon!
Recommended method (from GitHub):
# List available presets
drifters list-presets
# Load a preset from GitHub
drifters load-preset vscode
# Apply on this machine
drifters merge-app vscode
# On other machines, just pull and merge
drifters pull-app
drifters merge-app vscodeUsing local preset files:
# Import VS Code preset from local file
drifters import-app vscode --file presets/vscode.toml
# Or let it import from config repo (if already exported there)
drifters import-app vscodeCustomize and re-import:
# Export current config, edit, re-import
drifters export-app vscode --file ~/vscode-custom.toml
vim ~/vscode-custom.toml
drifters import-app vscode --file ~/vscode-custom.tomlSee docs/IMPORT_EXPORT.md for complete guide.
Submit your own app definitions via Pull Request! See CONTRIBUTING.md.
Each machine has its own branch (machines/<machine_id>). On each branch:
machines/mac01 branch:
├── .drifters/
│ ├── sync-rules.toml # Central configuration (on main)
│ └── machines.toml # Machine registry (on main)
└── apps/
└── zed/
├── settings.json
└── keymap.json
machines/linux02 branch:
└── apps/
└── zed/
└── settings.json
main contains the merged state after running drifters merge-app.
| Feature | Drifters | chezmoi | yadm | Dotbot | Bare Git |
|---|---|---|---|---|---|
| Branch-per-machine with git-native merge | ✅ | ❌ | ❌ | ❌ | ❌ |
| Section-level control | ✅ | ❌ | ❌ | ❌ | |
| Glob patterns | ✅ | ✅ | ✅ | ❌ | |
| OS-specific rules | ✅ | ✅ | ✅ | ❌ | |
| Per-machine exceptions | ✅ | ❌ | ❌ | ||
| Git-based | ✅ | ✅ | ✅ | ✅ | ✅ |
| No templating needed | ✅ | ❌ | ✅ | ✅ | ✅ |
| No-sync (singular) machines | ✅ | ❌ | ❌ | ❌ | ❌ |
Drifters is ideal if you:
- Work across multiple machines regularly
- Want explicit control over when configs merge (not automatic)
- Need per-machine branches with real git conflict resolution
- Need to exclude specific sections without complex templating
- Want fine-grained control with simple configuration
On every command:
- Clone/pull repo to
~/.config/drifters/tmp-repo - Perform operation
- Commit and push changes
- Delete temporary repo
Benefits:
- No persistent repo taking up space
- Always starts fresh from remote
- No stale state issues
Drifters uses git's native merge:
push-apppushes your configs to your machine's branch (machines/<machine_id>)merge-appmerges your branch intomainusinggit merge- On conflicts,
git mergetoolis launched for interactive resolution pull-appreads frommainand applies content while preserving local exclude sections
Section tags work with any comment syntax:
- Shell/Python/YAML:
# drifters::exclude::start - JavaScript/Rust/C++:
// drifters::exclude::start - Vim:
" drifters::exclude::start - Lua:
-- drifters::exclude::start - SQL:
-- drifters::exclude::start
Drifters uses your own private Git repository as storage. Never commit secrets to it. Use drifters::exclude sections to keep sensitive values (API keys, tokens, passwords) local to each machine.
drifters self-update downloads an installer shell script from GitHub releases over HTTPS and verifies it against a SHA-256 sidecar file before executing. The sidecar is automatically generated by the CI workflow on every release. If the sidecar is missing or the checksum does not match, the update is aborted.
- Only use
drifters self-updateagainst the official repository (github.com/tjirsch/drifters) - Alternatively, install updates manually via
cargo install driftersor by downloading a release binary directly - You can disable automatic update checks entirely: set
self_update_frequency = "never"in~/.config/drifters/drifters.toml - Use
--skip-checksumonly if you are installing from a release that predates sidecar support
The optional shell hook (eval "$(drifters hook)") runs drifters pull-app on every new shell session. This means remote config changes are applied automatically. Only enable it if you fully trust the machines pushing to your config repo.
Q: Does drifters require a daemon? A: No. All operations are manual unless you add the optional shell hook.
Q: What if two machines have conflicting changes?
A: When you run merge-app, git's native merge handles it. If there are conflicts, git mergetool is launched for interactive resolution.
Q: Can I sync secrets?
A: No. Drifters repos are Git repos - never commit secrets. Use drifters::exclude sections for sensitive data, or use a proper secret manager.
Q: Does it work with private repos? A: Yes. Drifters uses your system's Git with SSH. Set up SSH keys as normal.
Q: Can I use it without section tags? A: Yes. Section tags are optional. Without them, entire files are synced (with glob-based exclusions).
Q: What happens if I edit sync-rules.toml on multiple machines?
A: Run drifters pull on each machine to get the latest rules, or use drifters merge to re-apply current rules.
Q: How do I use community app presets?
A: Use drifters list-presets to see available presets, then drifters load-preset <name> to import from GitHub. Or use drifters import-app <name> --file <path> for local files. See docs/IMPORT_EXPORT.md.
Q: How do I disable update checks?
A: Edit ~/.config/drifters/drifters.toml and set self_update_frequency = "never". Options are: "never", "daily", "always" (default).
Ensure SSH works:
ssh -T git@github.comClean up and retry:
rm -rf ~/.config/drifters/tmp-repo
drifters <command># View local config
cat ~/.config/drifters/drifters.toml
# View sync rules
drifters list-app
# Verbose output
drifters -v push-app# Clone
git clone https://github.com/tjirsch/drifters
cd drifters
# Build
cargo build
# Test
cargo test
# Install locally
cargo install --path .We welcome contributions! See CONTRIBUTING.md for guidelines.
- App Presets - Add definitions for popular apps
- Documentation - Improve guides and examples
- Features - See roadmap below
- Bug Fixes - Help squash bugs
- Core sync operations (
init,add-app,push-app,pull-app) - Branch-per-machine architecture with git-native merge + mergetool
- Selective
merge-app <app>andno_mergeflag for per-app merge control - Section tags with exclude-only logic; error on unclosed blocks
- Glob pattern support
- OS-specific rules
- Per-machine overrides with
singularflag for no-merge machines - Ephemeral repository strategy
- Import/export commands for app definitions and rules
- Version history and restore commands
diff-appcommand for previewing changes- Self-update with SHA-256 checksum verification
rename-machine/remove-machinewith cross-machine stale-ID detectionrename-app/ extendedremove-appwith--machineand--allscoping- Consistent verb-object CLI naming convention
- Shell completion (
bash,zsh,fish,powershell) viacompletion [shell] [--install]; defaults to zsh + --install on macOS open-readmecommand to download and open latest READMEedit-config,edit-rules,edit-app-fileseditor commandseditorconfig option withset-editorcommanddiscover-presetsfor auto-detecting installed apps-V / --versionflag- No C library dependencies — git operations shell out to system
git
- TUI diff viewer with syntax highlighting (currently text-based)
- Improved conflict resolution UI
- Community preset registry
- Config validation and linting
- Integration tests with real repos
See CHANGELOG.md for a full history of changes, including new commands, bug fixes, and the pre-OSS review improvements.
MIT License - see LICENSE
Built with ❤️ using Rust
- clap - CLI parsing
- serde - Serialization
- glob - Pattern matching
- similar - Diff generation
- reqwest - HTTP client for updates
- cargo-dist - Release distribution
⭐ Star this repo if you find it useful!
Questions? Open an Issue Want to contribute? Check out CONTRIBUTING.md