twpayne / chezmoi

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

`chezmoi apply` creates empty directories #2588

Closed ja1den closed 1 year ago

ja1den commented 1 year ago

Describe the bug

Directories in the source state with no content, or with all their content .chezmoiignored, are still created in the target state when running chezmoi apply.

It also seems like the empty_ attribute does nothing when applied to directories, which, in light of #754, seems to be wrong:

I propose:

Directories with the empty_ prefix

Directory is created by chezmoi apply, even if it contains no entries in the target state. chezmoi will never remove the directory, unless it is listed in .chezmoiremove.

Directories without the empty_ prefix (the new default and new behavior)

chezmoi apply will only create the directory if it contains further entries managed by chezmoi. chezmoi will never remove the directory, unless it is listed in .chezmoiremove.

Does this sound reasonable?

From this, I'd assume that directories in the source state with no content, or with all their content .chezmoiignored, would be ignored by chezmoi apply. However, I'd still expect directories with the empty_ attribute to be created.

Finally, I'd also feel like, when this is fixed, directories ignored by chezmoi apply should also be absent from chezmoi managed. However, this isn't the primary issue, just something that's worth considering later on.

To reproduce

Create the following directory structure (for the source state):

.
├── .chezmoiignore
├── directory1
├── directory2
│  └── config
├── directory3
│  └── config
└── empty_directory4

Add the following to .chezmoignore:

directory2/config

Add the following to directory2/config:

# Example

Add the following to directory3/config:

# Example

Expected behavior

Running chezmoi apply currently yields the following in the target state:

.
├── directory1
├── directory2
├── directory3
│  └── config
└── empty_directory4

Instead, I'd expect it to produce the following:

.
├── directory3
│  └── config
└── directory4

Similarly, running chezmoi managed currently yields the following:

$ chezmoi managed
directory1
directory2
directory3
directory3/config
empty_directory4

Instead, I'd expect it to produce the following:

$ chezmoi managed
directory3
directory3/config
directory4

In both cases, directory1 is empty, so it is ignored. Similarly, directory2 has all its content .chezmoiignored, so it is ignored as well.

Output of command with the --verbose flag

$ chezmoi --verbose apply
diff --git a/directory1 b/directory1
new file mode 40777
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
--- /dev/null
+++ b/directory1
diff --git a/directory2 b/directory2
new file mode 40777
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
--- /dev/null
+++ b/directory2
diff --git a/directory3 b/directory3
new file mode 40777
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
--- /dev/null
+++ b/directory3
diff --git a/directory3/config b/directory3/config
new file mode 100666
index 0000000000000000000000000000000000000000..b4f04f43841a9645a9faf665a65270ce2e8051b3
--- /dev/null
+++ b/directory3/config
@@ -0,0 +1 @@
+# Example
diff --git a/empty_directory4 b/empty_directory4
new file mode 40777
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
--- /dev/null
+++ b/empty_directory4
$ chezmoi --verbose managed
directory1
directory2
directory3
directory3/config
empty_directory4

Output of chezmoi doctor

I ran all the above commands with a pair of test directories (source and destination), which is why the paths reported by chezmoi doctor are different. Specifically, I had -S ./source -D ./destination at the end of each command.

The full paths to my test directories were ~/.local/share/chezmoi/source and ~/.local/share/chezmoi/destination (Yes, I made them within my existing config. I would have made them somewhere else, but it shouldn't have made a difference).

```console $ chezmoi doctor RESULT CHECK MESSAGE ok version v2.27.1, commit b6039e787dfffe970fec4f9165ec9ebe1b3ceaa6, built at 2022-11-13T20:30:23Z, built by goreleaser ok latest-version v2.27.1 ok os-arch windows/amd64 ok systeminfo Microsoft Windows 10 Home (10.0.19044 N/A Build 19044) ok go-version go1.19.3 (gc) ok executable C:/ProgramData/chocolatey/lib/chezmoi/tools/chezmoi.exe ok config-file ~/.config/chezmoi/chezmoi.yml, last modified 2022-11-21T23:32:29+10:30 ok source-dir ~/.local/share/chezmoi/source is a directory ok suspicious-entries no suspicious entries warning working-tree ~/.local/share/chezmoi is a git working tree (dirty) ok dest-dir ~/.local/share/chezmoi/destination is a directory ok cd-command found C:/WINDOWS/system32/cmd.exe ok cd-args 'C:\\WINDOWS\\system32\\cmd.exe' info diff-command not set ok edit-command found C:/WINDOWS/notepad.exe ok edit-args 'C:\\WINDOWS\\notepad.exe' ok git-command found C:/Program Files/Git/bin/git.exe, version 2.38.1 warning merge-command vimdiff not found in $PATH ok shell-command found C:/WINDOWS/system32/cmd.exe ok shell-args 'C:\\WINDOWS\\system32\\cmd.exe' info age-command age not found in $PATH ok gpg-command found C:/Program Files (x86)/GnuPG/bin/gpg.exe, version 2.3.8 info pinentry-command not set info 1password-command op not found in $PATH info bitwarden-command bw 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 info pass-command pass not found in $PATH info passhole-command ph not found in $PATH info vault-command vault not found in $PATH info secret-command not set ```

Additional context

I noticed this problem when I saw that chezmoi apply was still creating my .scripts directory, even when I'd .chezmoiignored all the files inside it (It contains several bash scripts, which I don't want copied when I'm on windows).

twpayne commented 1 year ago

The empty_ attribute only applies to files. Using empty_ as part of a directory name results in the literal empty_ in the target directory name. This is visible in the output of chezmoi --verbose apply and the output of chezmoi --verbose managed.

To fix this, it should be sufficient to remove the empty_ prefix from empty_directory4 and add directory4 to your .chezmoiignore file.