- Introduction
- Directory structure
- Key concepts
internal/chezmoi/
internal/cmd/
- Path handling
- Persistent state
- Encryption
run_once_
scripts- Testing
This document gives a high-level overview of chezmoi's source code for anyone interested in contributing to chezmoi.
You can generate Go documentation for chezmoi's source code with go doc
, for
example:
$ go doc -all -u github.com/twpayne/chezmoi/v2/internal/chezmoi
You can also browse chezmoi's generated documentation online but this only includes exported symbols.
The important directories in chezmoi are:
Directory | Contents |
---|---|
docs/ |
The documentation single source of truth. Help text, examples, and the chezmoi.io website are generated from the files in this directory, particularly docs/REFERENCE.md . |
internal/chezmoi/ |
chezmoi's core functionality. |
internal/cmd/ |
Code for the chezmoi command. |
internal/cmd/testdata/scripts/ |
High-level tests of chezmoi's commands using testscript . |
As described in the reference manual, chezmoi evaluates the source state to compute a target state for the destination directory (typically your home directory). It then compares the target state to the actual state of the destination directory and performs any changes necessary to update the destination directory to match the target state. The concepts are represented directly in chezmoi's code.
chezmoi uses the generic term entry to describe something that it manages. Entries can be files, directories, symlinks, scripts, amongst other things.
All of chezmoi's interaction with the operating system is abstracted through the
System
interface. A System
includes functionality to read and write files
and directories and execute commands. chezmoi makes a distinction between
idempotent commands that can be run multiple times without modifying the
underlying system and arbitrary commands that may modify the underlying system.
The real underlying system is implemented via a RealSystem
struct. Other
System
s are composed on top of this to provide further functionality. For
example, the --debug
flag is implemented by wrapping the RealSystem
with a
DebugSystem
that logs all calls to the underlying RealSystem
. --dry-run
is
implemented by wrapping the RealSystem
with a DryRunSystem
that allows reads
to pass through but silently discards all writes.
The SourceState
struct represents a source state, including reading a source
state from the source directory, executing templates, applying the source state
(i.e. updating a System
to match the desired source state), and adding more
entries to the source state.
Entries in the source state are abstracted by the SourceStateEntry
interface
implemented by the SourceStateFile
and SourceStateDir
structs, as the source
state only consists of regular files and directories.
A SourceStateFile
includes a FileAttr
struct describing the attributes
parsed from its file name. Similarly, a SourceStateDir
includes a DirAttr
struct describing the directory attributes parsed from a directory name.
SourceStateEntry
s can compute their target state entries, i.e. what the
equivalent entry should be in the target state, abstracted by the
TargetStateEntry
interface.
Actual target state entries include TargetStateFile
structs, representing a
file with contents and permissions, TargetStateDir
structs, representing a
directory, TargetStateSymlink
for symlinks, TargetStateRemove
for entries
that should be removed, and TargetStateScript
for scripts that should be run.
The actual state of an entry in the target state is abstracted via the
ActualStateEntry
interface, with ActualStateAbsent
, ActualStateDir
,
ActualStateFile
, ActualStateSymlink
structs implementing this interface.
Finally, an EntryState
struct represents a serialization of an
ActualEntryState
for storage in and retrieval from chezmoi's persistent state.
It stores a SHA256 of the entry's contents, rather than the full contents, to
avoid storing secrets in the persistent state.
With these concepts, chezmoi's apply command is effectively:
- Read the source state from the source directory.
- For each entry in the source state (
SourceStateEntry
), compute itsTargetStateEntry
and read its actual state in the destination state (ActualStateEntry
). - If the
ActualStateEntry
is not equivalent to theTargetStateEntry
then apply the minimal set of changes to theActualStateEntry
so that they are equivalent.
Furthermore, chezmoi stores the EntryState
of each entry that it writes in its
persistent state. chezmoi can then detect if a third party has updated a target
since chezmoi last wrote it by comparing the actual state entry in the target
state with the entry state in the persistent state.
internal/cmd/*cmd.go
contains the code for each individual command and
internal/cmd/*templatefuncs.go
contain the template functions.
Commands are defined as methods on the Config
struct. The Config
struct is
large, containing all configuration values read from the config file, command
line arguments, and computed and cached values.
The Config.persistentPreRunRootE
and Config.persistentPostRunRootE
methods
set up and tear down state for individual commands based on the command's
Annotations
field.
chezmoi uses separate types for absolute paths (AbsPath
) and relative paths
(RelPath
) to avoid errors where paths are combined (e.g. joining two absolute
paths). A further type SourceRelPath
is a relative path within the source
directory and handles file and directory attributes.
Internally, chezmoi normalizes all paths to use forward slashes with an optional
upper-cased Windows volume so they can be compared with string comparisons.
Paths read from the user may include tilde (~
) to represent the user's home
directory, use forward or backward slashes, and are treated as external paths
(ExtPath
). These are normalized to absolute paths. chezmoi is case-sensitive
internally and makes no attempt to handle case-insensitive or case-preserving
filesystems.
Persistent state is treated as a two-level key-value store with the
pseudo-structure map[Bucket]map[Key]Value
, where Bucket
, Key
, and Value
are all []byte
s. The PersistentState
interface defines interaction with
them. Sometimes temporary persistent states are used. For example, in dry run
mode (--dry-run
) the actual persistent state is copied into a temporary
persistent state in memory which remembers writes but does not persist them to
disk.
Encryption tools are abstracted by the Encryption
interface that contains
methods of encrypting and decrypting files and []byte
s. Implementations are
the AGEEncryption
and GPGEncryption
structs. A DebugEncryption
struct
wraps an Encryption
interface and logs the methods called.
The execution of run_once_
scripts is recorded by storing the SHA256 of their
contents in the persistent state. run_once_
scripts are only run if they are
new or if their contents have not changed.
chezmoi has a mix of, unit, integration, and end-to-end tests. Unit and
integration tests use the
github.com/stretchr/testify
framework. End-to-end tests use
github.com/rogpeppe/go-internal/testscript
with the test scripts themselves in testdata/scripts
. You can run individual
end-to-end tests with
$ go test ./internal/cmd -run=TestScript/<name>
where <name>
is the basename of file in testdata/scripts
.
Tests should, if at all possible, run unmodified on all operating systems tested in CI (Linux, macOS, Windows, and FreeBSD). Windows will sometimes need special handling due to its path separator and lack of POSIX-style file permissions.