twpayne / chezmoi

Manage your dotfiles across multiple diverse machines, securely.
https://www.chezmoi.io/
MIT License
12.85k stars 477 forks source link

Precedence of .chezmoiremove vs. exact_ attributes? #3821

Closed rlue closed 2 months ago

rlue commented 2 months ago

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:

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

```console $ 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

twpayne commented 2 months ago

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.

twpayne commented 2 months ago

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