twpayne / chezmoi

Manage your dotfiles across multiple diverse machines, securely.

Home Page:https://www.chezmoi.io/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Precedence of .chezmoiremove vs. exact_ attributes?

rlue opened this issue · comments

Describe the bug

This is not a bug in a strict sense; chezmoi is not behaving the way I expect it to, and I hope to make the case here that my expectations agree with how it should work.

I would like to:

  • keep an exact_ directory in my source dir
  • conditionally ensure the absence of that directory on certain systems via .chezmoiremove

chezmoi diff shows that it would add the directory to my target dir, but I would expect .chezmoiremove to take precedence over a file attribute, since the former offers more expressive and fine-grained control than the latter.

It appears that adding the file to .chezmoiignore as well resolves this problem—but leaves us having to duplicate entries between the two files. So perhaps another way of stating this "bug" report is that, IMO, entries in .chezmoiremove should be implicitly ignored, as well.

To reproduce

$ chezmoi cd
$ mkdir exact_foo
$ touch exact_foo/bar
$ echo "foo" >> .chezmoiremove
$ chezmoi diff

Expected behavior

foo should not appear in the diff.

Why?

Of course, there are more complex cases where the correct behavior is not so clear. What if we list a file in .chezmoiremove that is within an exact_ directory?

# .chezmoiremove

foo/bar

but

chezmoi
└── exact_foo
    ├── bar
    └── baz

IMO, the behavior of .chezmoiremove here should mirror that of .chezmoiignore—which currently ignores the listed file and generates a non-exact copy of the directory foo.

Output of command with the --verbose flag

$ chezmoi --verbose diff
diff --git a/foo b/foo
new file mode 40755
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
--- /dev/null
+++ b/foo

Output of chezmoi doctor

$ chezmoi doctor
RESULT    CHECK                       MESSAGE
ok        version                     v2.49.0, commit 2e0573779db0c42717585ac8abc4ad1ab814dfb2, built at 2024-06-10T20:04:26Z, built by goreleaser
ok        latest-version              v2.49.0
ok        os-arch                     linux/amd64 (Debian GNU/Linux 12 (bookworm))
ok        uname                       Linux gossamer 6.1.0-18-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.76-1 (2024-02-01) x86_64 GNU/Linux
ok        go-version                  go1.22.4 (gc)
ok        executable                  /usr/bin/chezmoi
ok        upgrade-method              sudo-upgrade-package
ok        config-file                 ~/.config/chezmoi/chezmoi.toml, last modified 2024-06-18T09:46:03-07:00
warning   source-dir                  ~/.local/share/chezmoi is a git working tree (dirty)
ok        suspicious-entries          no suspicious entries
warning   working-tree                ~/.local/share/chezmoi is a git working tree (dirty)
ok        dest-dir                    ~ is a directory
ok        umask                       022
ok        cd-command                  found /bin/bash
ok        cd-args                     /bin/bash
info      diff-command                not set
ok        edit-command                found /usr/bin/nvim
ok        edit-args                   /usr/bin/nvim
ok        git-command                 found /usr/bin/git, version 2.39.2
ok        merge-command               found /usr/bin/vimdiff
ok        shell-command               found /bin/bash
ok        shell-args                  /bin/bash
info      age-command                 age not found in $PATH
ok        gpg-command                 found /usr/bin/gpg, version 2.2.40
info      pinentry-command            not set
info      1password-command           op not found in $PATH
info      bitwarden-command           bw not found in $PATH
info      bitwarden-secrets-command   bws not found in $PATH
info      dashlane-command            dcli not found in $PATH
info      doppler-command             doppler not found in $PATH
info      gopass-command              gopass not found in $PATH
info      keepassxc-command           keepassxc-cli not found in $PATH
info      keepassxc-db                not set
info      keeper-command              keeper not found in $PATH
info      lastpass-command            lpass not found in $PATH
ok        pass-command                found /usr/bin/pass, version 1.7.4
info      passhole-command            ph not found in $PATH
info      rbw-command                 rbw not found in $PATH
info      vault-command               vault not found in $PATH
info      vlt-command                 vlt not found in $PATH
info      secret-command              not set

Additional context

N/A

Thanks for spotting this. This is an interesting case of non-orthogonality in chezmoi.

The tl;dr is that .chezmoiremove is a separate system to chezmoi's source state and they don't interact well. Use the remove_ attribute on the directory instead.

Note that this is not specific to exact_ directories, so I'll ignore exact_.

In a sense, the configuration that you have is inconsistent: it states that foo and foo/bar should exist (because they are present in the source state) and also that foo should be removes. The reason that chezmoi does not detect the inconsistency is because chezmoi's order of operations is roughly:

  1. Read the source state.
  2. Make a list of files and directories that match any .chezmoiremove lines.
  3. Apply the source state.
  4. Remove the files and directories found in stage 2.

In your case, foo does not exist at stage 2, so it is not added to the list, and therefore created in stage 3 and not removed in stage 4.

Instead, use the remove_ attribute on foo, which will tell chezmoi to remove the directory if it is empty.

Hopefully this is now resolved. Please re-open if needed.