dotflow is a small, public MIT-licensed Rust CLI for safely operating a cloned,
Git-backed mise dotfiles repository. It coordinates Git,
mise environments, target sets, and explicit argv hooks without evaluating a shell.
Rust 1.85 or newer is required when building from source:
cargo install --locked --path .
# After a release exists:
cargo install --locked dotflowRelease archives are produced for x86-64 Linux, Apple Silicon macOS, and x86-64 Windows. Crates.io publication is deliberately manual.
Clone your dotfiles yourself, then run:
dotflow init ~/dotfilesinit only registers an existing Git worktree. It never clones, initializes Git,
scaffolds the repository, or overwrites existing configuration. Repository lookup
uses --repo, then DOTFLOW_REPO, then the user locator, then ~/dotfiles. The
locator is $XDG_CONFIG_HOME/dotflow/config.toml (normally
~/.config/dotflow/config.toml) or %APPDATA%\dotflow\config.toml on Windows.
The repository contains .dotflow.toml:
schema_version = 1
minimum_dotflow_version = "0.1.0"
[repository]
branch = "master"
origin = "git@github.com:owner/dotfiles.git"
mise_dir = "mise"
[common]
targets = ["~/.gitconfig"]
[profiles.work-wsl]
platform = "wsl"
arch = "x86_64" # optional
add_environment = "linux"
[[profiles.work-wsl.environments]]
mise_environment = "linux"
targets = ["~/.zshrc"]
[[profiles.work-wsl.after_sync]]
program = "home-manager"
args = ["switch", "--flake", "{repo}#me@wsl"]
cwd = "{repo}"
[[profiles.work-wsl.before_install]]
program = "tool"
args = ["--backend", "{backend}"]
[profiles.work-wsl.before_install.env]
DOTFILES_HOME = "{home}"
[profiles.windows-desktop]
platform = "windows"
add_environment = "windows"
[[profiles.windows-desktop.environments]]
mise_environment = "windows"
targets = ["~/AppData/Roaming/tool/config.toml"]
[[profiles.windows-desktop.environments]]
mise_environment = "pwsh"
targets = ["~/Documents/PowerShell/Microsoft.PowerShell_profile.ps1"]Profile names are arbitrary. Auto-selection compares the required platform and
optional arch fields and requires exactly one match; use --profile to override.
A profile may contain any number of environment tables. With none, update/apply
use [common] once. Only an environment's targets are applied and checked;
dotfiles that the merged mise config manages but no environment lists are left
alone. Use mise -C <mise_dir> -E <env> dotfiles status for the full merged view.
add without an explicitly supplied --profile always uses
the base mise config; explicit profile add and edit use add_environment.
Only the selected profile's hooks run. Only {repo}, {home}, and {backend} are expanded in hook
arguments, cwd, environment values, and program; strings are passed directly as
argv and are never shell-evaluated. {backend} is the absolute repository
mise_dir. Schema and minimum-version checks fail early.
dotflow [--repo PATH] [--profile PROFILE] init [PATH]
dotflow root print the repository root
dotflow cd print the root (use shell integration to cd)
dotflow [--profile PROFILE] add TARGET...
dotflow edit TARGET
dotflow status [--check]
dotflow apply [TARGET...]
dotflow update [--dry-run]
dotflow doctor
dotflow check report upstream commits (used at shell start)
dotflow completion <bash|zsh|fish|powershell>
dotflow shell-init [--no-check] <bash|zsh|fish|powershell>
add streams mise output and never stages Git. edit, apply/install operations,
and hooks inherit the terminal, including stdin. apply trusts the base config and
each selected environment config exactly once, applies the selected profile's
environments, then shows the status of the same targets it applied. It never pulls
or installs. status displays mise status output for each selected environment's
targets; status --check returns nonzero for a dirty repository or mise drift in
those targets (mise dotfiles status --missing).
For parent-shell cd, source the generated wrapper, for example:
eval "$(dotflow shell-init zsh)"
# bash: eval "$(dotflow shell-init bash)"
# fish: dotflow shell-init fish | sourcePowerShell:
Invoke-Expression (& dotflow shell-init powershell | Out-String)The generated script also runs dotflow check once at shell start; pass
--no-check to omit it. check never touches the network in the foreground: it
counts the commits that the local origin/<branch> ref has beyond HEAD and, if
any, prints dotfiles: origin/<branch> is N commit(s) ahead — run "dotflow update"
to stderr. When FETCH_HEAD is missing or older than six hours, it starts a detached
background git fetch of that branch, so the result appears in the next shell.
GitHub origins are fetched anonymously over HTTPS so no SSH key, agent, or hardware
token is involved (this works for public repositories); other origins are fetched
from origin. Fetching never prompts (GIT_TERMINAL_PROMPT=0), and check prints
nothing on errors and always exits 0.
Generate static completion with dotflow completion zsh (or another shell).
dotflow update validates the exact repository root, configured branch, normalized
GitHub origin, clean tracked and untracked state, and dependencies before pulling.
It then performs an exact git pull --ff-only origin <branch>, reloads and validates
configuration, revalidates the selected profile, runs that profile's after_sync
hooks, trusts configs, applies targets and checks missing then normal status, runs
that profile's before_install hooks, and installs each selected mise environment.
Applying before installing lets a dotfile-distributed mise config define the toolset
that install then resolves, which is what copy distribution otherwise breaks.
Failures stop immediately. It never commits, pushes, stashes, resets,
rebases, or deletes files.
dotflow update --dry-run performs local validation, makes no network or filesystem
changes, and prints the ordered argv that would run. GitHub SSH, ssh://, and HTTPS
origins are compared after normalization, with an optional .git suffix accepted.
Typical workflow:
dotflow doctor
dotflow status --check
dotflow update --dry-run
dotflow update
dotflow add ~/.config/tool/config.toml
git -C "$(dotflow root)" diff