Documentation
¶
Overview ¶
Package drift compares a resolved plan against the live system. All checks are read-only probes; nothing is installed, written, or ensured.
Index ¶
- func ColorDiff(diff string, color bool) string
- func ReferencedPaths(layers []ModuleLayer) map[string]bool
- func Render(w io.Writer, findings []Finding, renderOptions ...RenderOption)
- func UnifiedDiff(targetName, targetContent, sourceName, sourceContent string) string
- type CheckOptions
- type Finding
- type ModuleLayer
- type Probes
- type RenderOption
- type Status
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ColorDiff ¶ added in v0.16.0
ColorDiff adds ANSI colors to unified diff output. When color is false the input is returned unchanged. + lines (not +++) get green, - lines (not ---) get red, @@ hunk headers get cyan.
func ReferencedPaths ¶ added in v0.25.0
func ReferencedPaths(layers []ModuleLayer) map[string]bool
ReferencedPaths maps every profile-side file some [dotfiles] declaration uses to true, across every layer given. Orphans are profile-content drift, so references cannot come from a resolved plan (one plan covers one machine's view); they come from what the layers declare, evaluated per host/user view:
- A view's effective declarations merge its layers whole-entry by precedence (user > host > base) — an overridden target's old source is stranded in that view, exactly like resolve.
- A declaration whose source is a DIRECTORY at the declaring layer references that whole subtree (mise links/copies directory trees wholesale). The declaring layer is the anchor, not whichever layer resolves first: an overlay holding a dir at the same rel-path wins deployment, but the declaring layer's tree stays the authored reference and extra overlay files remain orphans.
- A FILE source references the file the view resolves it to (user > host > base, first existing). A base copy shadowed on every host is dead content (orphan); one that still resolves for a host without an overlay copy deploys there and stays referenced.
Layers are grouped by module directory name; a layer without a readable module.toml declares nothing but still shapes every view's resolution order (its files shadow base copies). An invalid module.toml fails every command at load, so decode errors here mean "absent".
func Render ¶
func Render(w io.Writer, findings []Finding, renderOptions ...RenderOption)
Render writes the drift report. Sections appear in fixed order and only when they have ≥1 finding. Non-OK findings print as ` <module>: <item> - <detail>`; the owning module replaces the status prefix (drift is implied for listed items; unknown items get a (?) suffix). A section whose findings are all OK prints ` ok: all N checks passed`. The orphans section groups its findings under layer-root headings (` base:` / ` hosts/<hostname>:` / ` users/<username>:`), one ` <module>: <file> - <detail>` line each. On a TTY, finding lines are colored by issue type: orange (missing/version drift), red (not-a-symlink/unknown), yellow (content differs), and magenta (orphans). The final line is `no drift` when nothing drifted, otherwise `drift: N item(s)` with `, K unknown` appended when there are unknowns.
func UnifiedDiff ¶ added in v0.16.0
UnifiedDiff computes a unified diff between current (live) and desired (profile) file content. Returns empty string when content is identical. targetName/sourceName label the --- and +++ header lines.
Types ¶
type CheckOptions ¶
type CheckOptions struct {
// Jobs bounds concurrent probe workers; <= 0 uses runtime.NumCPU().
Jobs int
// Verbose, when non-nil, receives one "checking <section>: <item>" line
// per probe as it starts. Writes are serialized internally.
Verbose io.Writer
}
CheckOptions controls how Check runs its probes.
type Finding ¶
type Finding struct {
Section string // "packages" | "tools" | "dotfiles" | "mounts" | "smb" | "orphans"
Item string // package name, tool name, target path, unit name, share name
Status Status
Detail string
Module string // owning module ID (empty if unknown)
// Group is the orphans-only layer-root heading the finding renders
// under: "base", "hosts/<hostname>", or "users/<username>". Empty for
// every other section (they render flat).
Group string
}
Finding is one checked item. Detail is empty for OK.
func Check ¶
func Check(ctx context.Context, plan *resolve.Plan, profileRoot string, pr Probes, opts CheckOptions) []Finding
Check runs every read-only probe for the plan concurrently (bounded by opts.Jobs; <= 0 → runtime.NumCPU) and returns findings in fixed section order (packages → tools → dotfiles → mounts → smb). Each worker writes only its own indexed result slot, so output order is deterministic regardless of completion order. opts.Verbose, when non-nil, receives one "checking <section>: <item>" line per probe as it starts. A nil plan returns nil.
func CheckOrphans ¶ added in v0.24.0
func CheckOrphans(layers []ModuleLayer) []Finding
CheckOrphans reports files inside the given module layer directories that no [dotfiles] declaration of that module references. References come from the layer declarations themselves (see ReferencedPaths), not a resolved plan, so the scan checks every host/user layer of the profile from any machine: a leftover under hosts/<other> or users/<other> is reported here, now. module.toml is the manifest itself and never an orphan.
type ModuleLayer ¶ added in v0.24.0
type ModuleLayer struct {
Dir string // module directory name (dotfile-entry Module key)
Layer string // "base" | "host" | "user"
Owner string // hostname (host layer) or username (user layer); empty for base
Path string // absolute layer module directory
}
ModuleLayer names one layer directory of one module: the module's directory name (the layer-merge key), the layer it lives in, and the absolute path. For host/user layers Owner names the layer's hostname or username so orphan group headings read "hosts/<hostname>" / "users/<username>". Callers derive these from the profile layout (modules/<dir>, hosts/<hostname>/modules/<dir>, users/<username>/modules/<dir>).
type Probes ¶
type Probes struct {
IsInstalled func(ctx context.Context, pkg string) (bool, error)
ToolCurrent func(ctx context.Context, tool string) (string, error) // err or "" → Unknown
Run func(ctx context.Context, name string, args ...string) (string, error)
HomeDir string
Readlink func(path string) (string, error)
ReadFile func(path string) ([]byte, error)
StatDir func(path string) (bool, error) // exists and is a directory
Stat func(path string) (bool, error) // exists (file or dir) — source validity
ListDir func(path string) ([]string, error) // direct child names — stale symlink-each scan
}
Probes are the side-effect seams. DefaultProbes fills OS-backed defaults; tests override individual fields. IsInstalled and ToolCurrent have no generic OS default (they need a package backend / mise) and must be set by the caller before Check.
func DefaultProbes ¶
func DefaultProbes() Probes
DefaultProbes returns OS-backed probes for Run, Readlink, ReadFile, StatDir, Stat, ListDir, and HomeDir. IsInstalled and ToolCurrent are left nil — wire them to a package backend and mise before calling Check.
type RenderOption ¶ added in v0.24.0
type RenderOption func(*renderOpts)
RenderOption configures one Render call.
func WithPalette ¶ added in v0.24.0
func WithPalette(p *palette.Palette) RenderOption
WithPalette selects the color palette for the report (per-role overrides from dotdrift.toml [colors]); nil falls back to the default.