A comprehensive solution to managing your dotfiles
Project description
dotsync
Mirror-only dotfiles manager — edit configs in $HOME, mirror them to git, restore on new machines.
Install • Quick start • Commands • Filelist • Test coverage • Docs
dotsync watches paths under your home directory, copies their content into a git repo (default ~/.dotfiles), and pushes to a remote. Config files stay as regular files in $HOME — the repo is a mirror, not a symlink farm. Use categories to sync subsets of your dotfiles per machine; use @tree lines to watch whole directories that grow over time.
Installation
Quick install (installs uv if needed; first run ~30s, then instant):
curl -fsSL https://raw.githubusercontent.com/HarveyGG/dotsync/main/install.sh | bash
Homebrew:
brew tap HarveyGG/tap
brew install dotsync
pip:
pip install dotsync-cli
Requires git. Runtime uses uv to resolve the CLI (handled by the install methods above).
Quick start
Machine that already has your configs:
dotsync track ~/.zshrc shell
dotsync track ~/.config/nvim editor
dotsync save # mirror → commit → push (prompts for remote on first push)
New machine:
dotsync restore
# or non-interactive:
dotsync restore --remote git@github.com:you/dotfiles.git --categories shell,editor --yes
track bootstraps ~/.dotfiles and git init on first use. restore clones (or uses an existing repo), pulls latest, then copies mirrors back to $HOME.
Filelist
Watch rules live in ~/.dotfiles/filelist:
# path:category[,category...] optional |encrypt
.zshrc:shell,common
.ssh/config:tools|encrypt
# whole directory — re-scanned on every save
@tree:.config/nvim:editor
@tree:.local/share/my-app/custom-*:tools
# host → category groups (optional)
macbook=shell,editor,common
- Atomic paths — single files tracked with
dotsync track <file>. @tree— whole directories;dotsync track <dir>adds a@treeline. New files under the pattern are picked up on the nextsave. Globs supported (*,?,[…]).- Symlinks inside trees — targets are materialized into the repo (internal links keep layout; external targets go under
.dotsync/materialized/).
Commands
| Command | Description |
|---|---|
init [dir] |
Create repo skeleton at ~/.dotfiles (or given path) |
track <path> [category] [--encrypt] [--no-auto-update] |
Add to filelist; mirrors immediately unless --no-auto-update |
untrack <path> [--purge-repo] |
Stop watching; home file kept |
encrypt <path> |
Convert an already-tracked plain path to encrypted |
list [categories] [--top-level] |
List watched paths; each row shows (file|tree, plain|encrypt) plus categories; @tree entries are summarized by directory, not expanded |
categories |
Show category groups from filelist |
save [categories] [-m msg] [--dry-run] [--no-push] |
Expand @tree, mirror home → repo, commit, push |
restore [categories] [--remote URL] [--yes] [--skip-pull] [--conflict …] |
Pull (unless skipped), then repo → home; diff on conflict |
update [categories] |
Mirror home → repo without commit |
diff [categories] |
Show git diff plus pending mirror changes |
commit [-m msg] |
Commit staged repo changes |
clean [categories] |
Remove repo mirrors no longer in filelist |
passwd |
Set or change encryption password |
showpw |
Print stored encryption password (local machine only) |
Common flags: --non-interactive, --dry-run, --encrypt, --keep-going.
Full reference: ReadTheDocs.
Test coverage
295 tests total (37 black-box E2E scenarios against the real CLI in an isolated sandbox). All scenarios below pass on v2.0.5.
uv run pytest tests/blackbox/ -v
uv run pytest tests/ -v
| ID | Area | What it verifies |
|---|---|---|
| L1 | Lifecycle | track → save (push) → restore on a fresh machine |
| L2 | Lifecycle | untrack removes from filelist; home file kept |
| L3 | Lifecycle | untrack --purge-repo deletes mirror |
| L4 | Lifecycle | track bootstraps repo when missing |
| L5a | Lifecycle | Default track mirrors immediately |
| L5b | Lifecycle | track --no-auto-update defers mirror until save |
| M1 | Mirror | Home paths stay regular files after save + restore |
| M2 | Mirror | Atomic file round-trip is byte-identical |
| M3 | Mirror | Nested paths restore as files, not symlinks |
| RP1 | Pull | restore pulls remote before copying to home |
| RP2 | Pull | Diverged repo aborts restore with no home writes |
| RP3 | Pull | --skip-pull uses local HEAD only |
| RP4 | Pull | No remote: restore from local HEAD |
| C1 | Conflicts | Cancel on diff aborts entire restore |
| C2 | Conflicts | Overwrite continues for remaining paths |
| C3 | Conflicts | Identical home file skipped without prompt |
| C4 | Conflicts | --conflict overwrite --non-interactive |
| C5 | Conflicts | --conflict abort --non-interactive |
| T1 | @tree |
New tree member picked up on second save |
| T2 | @tree |
Glob excludes non-matching paths |
| T3 | @tree |
Removed member pruned from repo mirror |
| T4 | @tree |
Tree save → restore on clean home |
| S1 | Symlinks | Internal symlink materialized with manifest |
| S2 | Symlinks | External symlink under .dotsync/materialized/ |
| S3 | Symlinks | Broken symlink skipped cleanly |
| S4 | Symlinks | Dedup when link target also watched |
| S5 | Symlinks | Restore recreates internal symlink |
| S6 | Symlinks | External symlink save → restore round-trip |
| E1 | Encrypt | Encrypt track → save → restore |
| E2 | Encrypt | showpw prints stored password |
| E3 | Encrypt | showpw fails when no password set |
| B1 | Boundaries | Empty filelist save is a no-op |
| B2 | Boundaries | Declined remote URL aborts save |
| B3 | Boundaries | Push failure exits non-zero |
| B4 | Boundaries | Binary conflict; cancel leaves home unchanged |
| B5 | Boundaries | --no-push durability warning |
| B6 | Boundaries | Save without remote exits quickly (no hang) |
Scenario details: tests/blackbox/test_plan.md.
Contributing
git clone https://github.com/HarveyGG/dotsync.git && cd dotsync
uv sync
uv run pytest tests/
Pull requests welcome.
License
Non-Commercial License — free for personal, educational, and non-profit use. See LICENSE. Commercial use requires permission: harvey.wanghy@gmail.com
Links
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dotsync_cli-2.0.5.tar.gz.
File metadata
- Download URL: dotsync_cli-2.0.5.tar.gz
- Upload date:
- Size: 38.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
531281616db007be6e695693a240f30268c23a1ea7de2b0d91832e1ad5b70b2a
|
|
| MD5 |
ae3e2762415d2a86fc3f0a770ad782cd
|
|
| BLAKE2b-256 |
3a699d6feb2502d6b530e764ec5cd30169d91b64040df19112d154b12ae16053
|
File details
Details for the file dotsync_cli-2.0.5-py3-none-any.whl.
File metadata
- Download URL: dotsync_cli-2.0.5-py3-none-any.whl
- Upload date:
- Size: 44.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1db9fea43ceaaa9a7cb3b26e6bba69ab853adc4ecd32484f757c578dd5ab91ab
|
|
| MD5 |
71d99921143e3501ab804ddc5fedad5a
|
|
| BLAKE2b-256 |
f951d49d158b428f8d623faf76782c1b3f669a0f16e70dbb8c942bd7404b6970
|