Skip to content

Commit

Permalink
Merge pull request twpayne#1387 from twpayne/remove
Browse files Browse the repository at this point in the history
Add remove_ attribute
  • Loading branch information
twpayne committed Sep 4, 2021
2 parents 7b1988e + 767e834 commit 076e381
Show file tree
Hide file tree
Showing 9 changed files with 60 additions and 26 deletions.
15 changes: 5 additions & 10 deletions docs/HOWTO.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,18 +250,13 @@ chezmoi automatically creates `.keep` files when you add an empty directory with
### Ensure that a target is removed

Create a file called `.chezmoiremove` in the source directory containing a list
of patterns of files to remove. When you run
of patterns of files to remove. chezmoi will remove anything in the target
directory that matches the pattern. As this command is potentially dangerous,
you should run chezmoi in verbose, dry-run mode beforehand to see what would be
removed:

```console
$ chezmoi apply --remove
```

chezmoi will remove anything in the target directory that matches the pattern.
As this command is potentially dangerous, you should run chezmoi in verbose,
dry-run mode beforehand to see what would be removed:

```console
$ chezmoi apply --remove --dry-run --verbose
$ chezmoi apply --dry-run --verbose
```

`.chezmoiremove` is interpreted as a template, so you can remove different files
Expand Down
15 changes: 9 additions & 6 deletions docs/REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ Manage your dotfiles across multiple machines, securely.
* [`--no-tty`](#--no-tty)
* [`-o`, `--output` *filename*](#-o---output-filename)
* [`-R`, `--refresh-externals`](#-r---refresh-externals)
* [`-r`. `--remove`](#-r---remove)
* [`-S`, `--source` *directory*](#-s---source-directory)
* [`--use-builtin-git` *value*](#--use-builtin-git-value)
* [`-v`, `--verbose`](#-v---verbose)
Expand Down Expand Up @@ -213,10 +212,6 @@ Write the output to *filename* instead of stdout.

Refresh externals cache. See `.chezmoiexternal.<format>`.

### `-r`. `--remove`

Also remove targets according to `.chezmoiremove`.

### `-S`, `--source` *directory*

Use *directory* as the source directory.
Expand Down Expand Up @@ -313,7 +308,6 @@ The following configuration variables are available:
| | `encryption` | string | *none* | Encryption tool, either `age` or `gpg` |
| | `format` | string | `json` | Format for data output, either `json` or `yaml` |
| | `mode` | string | `file` | Mode in target dir, either `file` or `symlink` |
| | `remove` | bool | `false` | Remove targets |
| | `sourceDir` | string | `~/.local/share/chezmoi` | Source directory |
| | `pager` | string | `$PAGER` | Default pager |
| | `umask` | int | *from system* | Umask |
Expand Down Expand Up @@ -424,6 +418,7 @@ to as "attributes":
| `modify_` | Treat the contents as a script that modifies an existing file. |
| `once_` | Run script once. |
| `private_` | Remove all group and world permissions from the target file or directory. |
| `remove_` | Remove the entry if it exists. |
| `run_` | Treat the contents as a script to run. |
| `symlink_` | Create a symlink instead of a regular file. |

Expand All @@ -441,6 +436,7 @@ prefixes is important.
| Regular file | File | `encrypted_`, `private_`, `executable_`, `dot_` | `.tmpl` |
| Create file | File | `create_`, `encrypted_`, `private_`, `executable_`, `dot_` | `.tmpl` |
| Modify file | File | `modify_`, `encrypted_`, `private_`, `executable_`, `dot_` | `.tmpl` |
| Remove | File | `remove_`, `dot_` | *none* |
| Script | File | `run_`, `once_`, `before_` or `after_` | `.tmpl` |
| Symbolic link | File | `symlink_`, `dot_`, | `.tmpl` |

Expand Down Expand Up @@ -495,6 +491,13 @@ new contents are read from the scripts standard output.

---

#### Remove entry

Files with the `remove_` prefix will cause the corresponding entry (file,
directory, or symlink) to be removed in the target state.

---

### Directories

Directories are represented by regular directories in the source state. The
Expand Down
6 changes: 6 additions & 0 deletions internal/chezmoi/attr.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const (
SourceFileTypeCreate SourceFileTargetType = iota
SourceFileTypeFile
SourceFileTypeModify
SourceFileTypeRemove
SourceFileTypeScript
SourceFileTypeSymlink
)
Expand Down Expand Up @@ -125,6 +126,9 @@ func parseFileAttr(sourceName, encryptedSuffix string) FileAttr {
name = mustTrimPrefix(name, executablePrefix)
executable = true
}
case strings.HasPrefix(name, removePrefix):
sourceFileType = SourceFileTypeRemove
name = mustTrimPrefix(name, removePrefix)
case strings.HasPrefix(name, runPrefix):
sourceFileType = SourceFileTypeScript
name = mustTrimPrefix(name, runPrefix)
Expand Down Expand Up @@ -240,6 +244,8 @@ func (fa FileAttr) SourceName(encryptedSuffix string) string {
if fa.Executable {
sourceName += executablePrefix
}
case SourceFileTypeRemove:
sourceName = removePrefix
case SourceFileTypeScript:
sourceName = runPrefix
if fa.Once {
Expand Down
7 changes: 7 additions & 0 deletions internal/chezmoi/attr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,13 @@ func TestFileAttr(t *testing.T) {
Private: []bool{false, true},
Template: []bool{false, true},
}))
require.NoError(t, combinator.Generate(&fas, struct {
Type SourceFileTargetType
TargetName []string
}{
Type: SourceFileTypeRemove,
TargetName: targetNames,
}))
require.NoError(t, combinator.Generate(&fas, struct {
Type SourceFileTargetType
TargetName []string
Expand Down
3 changes: 2 additions & 1 deletion internal/chezmoi/chezmoi.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const (
modifyPrefix = "modify_"
oncePrefix = "once_"
privatePrefix = "private_"
removePrefix = "remove_"
runPrefix = "run_"
symlinkPrefix = "symlink_"
literalSuffix = ".literal"
Expand All @@ -57,7 +58,7 @@ const (

var (
dirPrefixRegexp = regexp.MustCompile(`\A(dot|exact|literal|private)_`)
filePrefixRegexp = regexp.MustCompile(`\A(after|before|create|dot|empty|encrypted|executable|literal|modify|once|private|run|symlink)_`)
filePrefixRegexp = regexp.MustCompile(`\A(after|before|create|dot|empty|encrypted|executable|literal|modify|once|private|remove|run|symlink)_`)
fileSuffixRegexp = regexp.MustCompile(`\.(literal|tmpl)\z`)
)

Expand Down
10 changes: 10 additions & 0 deletions internal/chezmoi/sourcestate.go
Original file line number Diff line number Diff line change
Expand Up @@ -1230,6 +1230,14 @@ func (s *SourceState) newModifyTargetStateEntryFunc(sourceRelPath SourceRelPath,
}
}

// newRemoveTargetStateEntryFunc returns a targetStateEntryFunc that removes a
// target.
func (s *SourceState) newRemoveTargetStateEntryFunc(sourceRelPath SourceRelPath, fileAttr FileAttr) targetStateEntryFunc {
return func(destSystem System, destAbsPath AbsPath) (TargetStateEntry, error) {
return &TargetStateRemove{}, nil
}
}

// newScriptTargetStateEntryFunc returns a targetStateEntryFunc that returns a
// script with sourceLazyContents.
func (s *SourceState) newScriptTargetStateEntryFunc(sourceRelPath SourceRelPath, fileAttr FileAttr, targetRelPath RelPath, sourceLazyContents *lazyContents, interpreter *Interpreter) targetStateEntryFunc {
Expand Down Expand Up @@ -1314,6 +1322,8 @@ func (s *SourceState) newSourceStateFile(sourceRelPath SourceRelPath, fileAttr F
targetRelPath = targetRelPath[:len(targetRelPath)-len(ext)-1]
}
targetStateEntryFunc = s.newModifyTargetStateEntryFunc(sourceRelPath, fileAttr, sourceLazyContents, interpreter)
case SourceFileTypeRemove:
targetStateEntryFunc = s.newRemoveTargetStateEntryFunc(sourceRelPath, fileAttr)
case SourceFileTypeScript:
// If the script has an extension, determine if it indicates an
// interpreter to use.
Expand Down
3 changes: 0 additions & 3 deletions internal/cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ type Config struct {
Mode chezmoi.Mode `mapstructure:"mode"`
Pager string `mapstructure:"pager"`
Safe bool `mapstructure:"safe"`
Remove bool `mapstructure:"remove"`
SourceDirAbsPath chezmoi.AbsPath `mapstructure:"sourceDir"`
Template templateConfig `mapstructure:"template"`
Umask fs.FileMode `mapstructure:"umask"`
Expand Down Expand Up @@ -1033,15 +1032,13 @@ func (c *Config) newRootCmd() (*cobra.Command, error) {
persistentFlags.Var(&c.Color, "color", "Colorize output")
persistentFlags.VarP(&c.DestDirAbsPath, "destination", "D", "Set destination directory")
persistentFlags.BoolVar(&c.Safe, "safe", c.Safe, "Safely replace files and symlinks")
persistentFlags.BoolVar(&c.Remove, "remove", c.Remove, "Remove entries from destination directory")
persistentFlags.VarP(&c.SourceDirAbsPath, "source", "S", "Set source directory")
persistentFlags.Var(&c.Mode, "mode", "Mode")
persistentFlags.Var(&c.UseBuiltinGit, "use-builtin-git", "Use builtin git")
for _, key := range []string{
"color",
"destination",
"mode",
"remove",
"source",
} {
if err := viper.BindPFlag(key, persistentFlags.Lookup(key)); err != nil {
Expand Down
12 changes: 6 additions & 6 deletions internal/cmd/testdata/scripts/applyremove.txt
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
# test that chezmoi apply --dry-run --remove does not remove entries
chezmoi apply --dry-run --force --remove
# test that chezmoi apply --dry-run does not remove entries
chezmoi apply --dry-run --force
exists $HOME/.dir/file
exists $HOME/.file1
exists $HOME/.file2

# test that chezmoi apply --remove file removes only file
chezmoi apply --force --remove $HOME${/}.file1
# test that chezmoi apply file removes only file
chezmoi apply --force $HOME${/}.file1
exists $HOME/.dir/file
! exists $HOME/.file1
exists $HOME/.file2

# test that chezmoi apply --remove removes all entries
chezmoi apply --force --remove
# test that chezmoi apply removes all entries
chezmoi apply --force
! exists $HOME/.dir/file
! exists $HOME/.file1
! exists $HOME/.file2
Expand Down
15 changes: 15 additions & 0 deletions internal/cmd/testdata/scripts/remove.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,18 @@ exists $HOME/.executable
! chezmoi remove --force $HOME${/}.newfile $HOME${/}.executable
stderr 'not in source state'
exists $HOME/.executable

chhome home2/user

# test that chezmoi apply removes a file and a directory
exists $HOME/.file
exists $HOME/.dir
chezmoi apply
! exists $HOME/.file
! exists $HOME/.dir

-- home2/user/.dir/.keep --
-- home2/user/.file --
# contents of .file
-- home2/user/.local/share/chezmoi/remove_dot_file --
-- home2/user/.local/share/chezmoi/remove_dot_dir --

0 comments on commit 076e381

Please sign in to comment.