From 8a6d4d76487267d0c77d7ab06d532f16165d8baf Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Tue, 9 Nov 2021 22:12:35 +0100 Subject: [PATCH] feat: Add .chezmoiroot to allow source state to be subdir of source dir Co-authored-by: Felipe Santos --- docs/HOWTO.md | 32 +++++++++++++++++++++++ docs/REFERENCE.md | 10 +++++++ internal/chezmoi/chezmoi.go | 2 ++ internal/cmd/config.go | 12 ++++++++- internal/cmd/testdata/scripts/root.txt | 36 ++++++++++++++++++++++++++ 5 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 internal/cmd/testdata/scripts/root.txt diff --git a/docs/HOWTO.md b/docs/HOWTO.md index 81517220764..bff7dd1783d 100644 --- a/docs/HOWTO.md +++ b/docs/HOWTO.md @@ -54,6 +54,7 @@ * [Run a PowerShell script as admin on Windows](#run-a-powershell-script-as-admin-on-windows) * [Use chezmoi with GitHub Codespaces, Visual Studio Codespaces, or Visual Studio Code Remote - Containers](#use-chezmoi-with-github-codespaces-visual-studio-codespaces-or-visual-studio-code-remote---containers) * [Customize chezmoi](#customize-chezmoi) + * [Use a subdirectory of your dotfiles repo as the root of the source state](#use-a-subdirectory-of-your-dotfiles-repo-as-the-root-of-the-source-state) * [Don't show scripts in the diff output](#dont-show-scripts-in-the-diff-output) * [Customize the diff pager](#customize-the-diff-pager) * [Use a custom diff tool](#use-a-custom-diff-tool) @@ -1602,6 +1603,37 @@ sudo apt install -y vim-gtk --- +### Use a subdirectory of your dotfiles repo as the root of the source state + +By default, chezmoi uses the root of your dotfiles repo as the root of the +source state. If your source state contains many entries in its root, then your +target directory (usually your home directory) will in turn be filled with many +entries in its root as well. You can reduce the number of entries by keeping +`.chezmoiignore` up to date, but this can become tiresome. + +Instead, you can specify that chezmoi should read the source state from a +subdirectory of the source directory instead by creating a file called +`.chezmoiroot` containing the relative path to this subdirectory. + +For example, if `.chezmoiroot` contains: + +``` +home +``` + +Then chezmoi will read the source state from the `home` subdirectory of your +source directory, for example the desired state of `~/.gitconfig` will be read +from `~/.local/share/chezmoi/home/dot_gitconfig` (instead of +`~/.local/share/chezmoi/dot_gitconfig`). + +When migrating an existing chezmoi dotfiles repo to use `.chezmoiroot` you will +need to move the relevant files in to the new root subdirectory manually. You do +not need to move files that are ignored by chezmoi in all cases (i.e. are listed +in `.chezmoiignore` when executed as a template on all machines), and you can +afterwards remove their entries from `home/.chezmoiignore`. + +--- + ### Don't show scripts in the diff output By default, `chezmoi diff` will show all changes, including the contents of diff --git a/docs/REFERENCE.md b/docs/REFERENCE.md index 5cc13cc7420..19c8a8abe1a 100644 --- a/docs/REFERENCE.md +++ b/docs/REFERENCE.md @@ -50,6 +50,7 @@ Manage your dotfiles across multiple machines, securely. * [`.chezmoiexternal.`](#chezmoiexternalformat) * [`.chezmoiignore`](#chezmoiignore) * [`.chezmoiremove`](#chezmoiremove) + * [`.chezmoiroot`](#chezmoiroot) * [`.chezmoitemplates`](#chezmoitemplates) * [`.chezmoiversion`](#chezmoiversion) * [Commands](#commands) @@ -875,6 +876,15 @@ template. --- +### `.chezmoiroot` + +If a file called `.chezmoiroot` exists in the root of the source directory then +the source state is read from the directory specified in `.chezmoiroot` +interpreted as a relative path to the source directory. `.chezmoiroot` is read +before all other files in the source directory. + +--- + ### `.chezmoitemplates` If a directory called `.chezmoitemplates` exists, then all files in this diff --git a/internal/chezmoi/chezmoi.go b/internal/chezmoi/chezmoi.go index 7efaf5af5a4..4a2aac0d70e 100644 --- a/internal/chezmoi/chezmoi.go +++ b/internal/chezmoi/chezmoi.go @@ -50,6 +50,7 @@ const ( const ( Prefix = ".chezmoi" + RootName = Prefix + "root" dataName = Prefix + "data" externalName = Prefix + "external" ignoreName = Prefix + "ignore" @@ -69,6 +70,7 @@ var knownPrefixedFiles = newStringSet( Prefix+".json"+TemplateSuffix, Prefix+".toml"+TemplateSuffix, Prefix+".yaml"+TemplateSuffix, + RootName, dataName, externalName+".json", externalName+".toml", diff --git a/internal/cmd/config.go b/internal/cmd/config.go index b60d618c413..51ba2bc2acf 100644 --- a/internal/cmd/config.go +++ b/internal/cmd/config.go @@ -1285,6 +1285,16 @@ func (c *Config) newRootCmd() (*cobra.Command, error) { // newSourceState returns a new SourceState with options. func (c *Config) newSourceState(ctx context.Context, options ...chezmoi.SourceStateOption) (*chezmoi.SourceState, error) { sourceStateLogger := c.logger.With().Str(logComponentKey, logComponentValueSourceState).Logger() + + sourceDirAbsPath := c.SourceDirAbsPath + switch data, err := c.sourceSystem.ReadFile(c.SourceDirAbsPath.JoinString(chezmoi.RootName)); { + case errors.Is(err, fs.ErrNotExist): + case err != nil: + return nil, err + default: + sourceDirAbsPath = c.SourceDirAbsPath.JoinString(string(bytes.TrimSpace(data))) + } + s := chezmoi.NewSourceState(append([]chezmoi.SourceStateOption{ chezmoi.WithBaseSystem(c.baseSystem), chezmoi.WithCacheDir(c.CacheDirAbsPath), @@ -1295,7 +1305,7 @@ func (c *Config) newSourceState(ctx context.Context, options ...chezmoi.SourceSt chezmoi.WithLogger(&sourceStateLogger), chezmoi.WithMode(c.Mode), chezmoi.WithPriorityTemplateData(c.Data), - chezmoi.WithSourceDir(c.SourceDirAbsPath), + chezmoi.WithSourceDir(sourceDirAbsPath), chezmoi.WithSystem(c.sourceSystem), chezmoi.WithTemplateFuncs(c.templateFuncs), chezmoi.WithTemplateOptions(c.Template.Options), diff --git a/internal/cmd/testdata/scripts/root.txt b/internal/cmd/testdata/scripts/root.txt new file mode 100644 index 00000000000..0d19968de5e --- /dev/null +++ b/internal/cmd/testdata/scripts/root.txt @@ -0,0 +1,36 @@ +# test that chezmoi apply uses .chezmoiroot +chezmoi apply +cmp $HOME/.file golden/.file + +# test that chezmoi add uses .chezmoiroot +symlink $HOME/.symlink -> .file +chezmoi add $HOME${/}.symlink +cmp $CHEZMOISOURCEDIR/home/symlink_dot_symlink golden/symlink_dot_symlink + +[!exec:git] skip 'git not found in $PATH' +[windows] skip 'go-git does not support file:// URLs on windows' + +chhome home2/user +mkgitconfig + +# create a git repo in home2/user/repo +exec git -C $HOME/repo init +exec git -C $HOME/repo add . +exec git -C $HOME/repo commit -m 'Initial commit' + +# test that chezmoi init uses .chezmoiroot +chezmoi init --apply file://$HOME/repo +cmp $HOME/.file golden/.file + +-- golden/.file -- +# contents of .file +-- golden/symlink_dot_symlink -- +.file +-- home/user/.local/share/chezmoi/.chezmoiroot -- +home +-- home/user/.local/share/chezmoi/home/dot_file -- +# contents of .file +-- home2/user/repo/.chezmoiroot -- +home +-- home2/user/repo/home/dot_file -- +# contents of .file