Symlinks
By default, DotR copies files from the repository to their destination.
For configs you edit frequently and want to reflect immediately — no
update step — a package can instead be deployed as a symlink.
[packages.nvim]
src = "dotfiles/nvim"
dest = "~/.config/nvim/"
symlink = true
Importing with a symlink
dotr import ~/.config/nvim/ --symlink
--symlink sets symlink = true on the new package and immediately
deploys it (a plain dotr import only copies into the repository and
registers the package, without deploying).
Enabling symlinking globally
Setting symlink = true per package works well for a handful of them, but
if you want symlinking everywhere, set it once at the top level of
config.toml instead:
symlink = true
[packages.nvim]
src = "dotfiles/nvim"
dest = "~/.config/nvim/"
# no symlink field needed here - the global setting covers it
Every directory package deploys as a symlink, without needing
symlink = true written into each one individually.
Opting a package out
A package’s own symlink setting always wins over the global one, in
either direction — set symlink = false explicitly on a package to
exclude it from a global symlink = true:
symlink = true
[packages.dotfiles_only]
src = "dotfiles/dotfiles_only"
dest = "~/.dotfiles_only/"
symlink = false # opts out; deployed as a plain copy despite the global flag
A package that doesn’t mention symlink at all just follows whatever the
global flag says. Only an explicit true or false on the package
overrides it.
How it works
importcopies the source intodotfiles/<name>/in the repository, as normal.deploycopies those files again intodeployed/<name>/(a staging directory at the repository root —dotr initaddsdeployedto.gitignore, since it’s derived, not source, content).deploythen creates a symlink at the package’sdestpointing atdeployed/<name>/.
dotfiles/nvim/ (repository — source of truth, git-tracked)
│ deploy copies files
▼
deployed/nvim/ (staging — gitignored)
▲ symlink
│
~/.config/nvim/ (dest — symlinked to deployed/nvim/)
Editing files at ~/.config/nvim/ edits deployed/nvim/ directly (it’s a
symlink), so changes take effect immediately. Run dotr update to copy
those changes back into dotfiles/nvim/ when you’re ready to commit them.
This whole-directory symlink is called folding — the entire destination
becomes one symlink. It’s the default for any directory package with
symlink = true.
Unfolding: per-file symlinks
Folding means the destination directory doesn’t really exist on disk
anymore — it’s entirely replaced by a symlink. That’s fine as long as
dest only ever holds files DotR manages, but it can’t hold anything
else: there’s nowhere for an untracked, local-only file to live alongside
the managed ones.
Setting unfold_symlink = true on a directory package switches to the
opposite granularity: dest stays a real directory, and DotR instead
creates an individual symlink for each file inside it.
[packages.nix]
src = "dotfiles/nix"
dest = "~/.config/nix/"
symlink = true
unfold_symlink = true
~/.config/nix/ (dest — a real directory)
├── nix.conf -> ../../deployed/nix/nix.conf (managed, symlinked)
└── local-only.conf (untouched, real file)
With this, you can drop local-only.conf straight into ~/.config/nix/
and DotR will never touch it — it isn’t part of dotfiles/nix/, so it’s
simply never in scope for anything DotR does there.
A non-empty ignore list implies unfolding, even without setting
unfold_symlink explicitly. This is automatic because the two don’t make
sense apart: an ignored file has nowhere to go if
the whole directory is one symlink to a fully-populated staging copy —
unfolding is what actually makes “leave this path alone” meaningful for a
symlinked package.
[packages.nix]
src = "dotfiles/nix"
dest = "~/.config/nix/"
symlink = true
ignore = ["secrets.conf"] # implies unfold_symlink, even though it's unset
Safety guarantees
- Deploying only ever creates or replaces symlinks for files that are actually part of the package’s source tree. A path that isn’t — a foreign file — is never inspected, never removed, never overwritten.
- Cleaning (when a source file is deleted and you redeploy) only
removes symlinks it recognizes as its own — ones that resolve back into
that package’s
deployed/<name>/staging directory. A real file, or a symlink pointing anywhere else, is left alone. A directory is only removed once it’s completely empty, never forced. dotr updateonly pulls changes back from paths that are managed symlinks. A foreign file sitting in the same real directory is never copied into the tracked repository.
Redeploying
If dest already exists (as a symlink, a directory, or a plain file) when
deploy runs, it’s removed first and replaced with the fresh symlink — so
re-running dotr deploy after changing symlink = true/false correctly
switches a package between copy-mode and symlink-mode. The same applies to
switching unfold_symlink on: pre-existing content at a managed file’s
path is replaced with a symlink the same way whole-directory folding
replaces it, since that’s a one-time transition into symlink management —
after that, only foreign paths (paths outside the package entirely) are
ever left alone.