Skip to content

Commit

Permalink
feat: Add common --parent-dirs/-P flag
Browse files Browse the repository at this point in the history
  • Loading branch information
twpayne committed Oct 13, 2024
1 parent 1b789a6 commit 35148c7
Show file tree
Hide file tree
Showing 11 changed files with 146 additions and 39 deletions.
4 changes: 4 additions & 0 deletions assets/chezmoi.io/docs/reference/command-line-flags/common.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ Prompt before applying each target.

Write the output to *filename* instead of stdout.

## `-P`, `--parent-dirs`

Also perform command on all parent directories of *target*.

## `-r`, `--recursive`

Recurse into subdirectories, `true` by default.
Expand Down
9 changes: 6 additions & 3 deletions internal/cmd/applycmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import (
)

type applyCmdConfig struct {
filter *chezmoi.EntryTypeFilter
init bool
recursive bool
filter *chezmoi.EntryTypeFilter
init bool
parentDirs bool
recursive bool
}

func (c *Config) newApplyCmd() *cobra.Command {
Expand All @@ -30,6 +31,7 @@ func (c *Config) newApplyCmd() *cobra.Command {
applyCmd.Flags().VarP(c.apply.filter.Exclude, "exclude", "x", "Exclude entry types")
applyCmd.Flags().VarP(c.apply.filter.Include, "include", "i", "Include entry types")
applyCmd.Flags().BoolVar(&c.apply.init, "init", c.apply.init, "Recreate config file from template")
applyCmd.Flags().BoolVarP(&c.apply.parentDirs, "parent-dirs", "P", c.apply.parentDirs, "Apply all parent directories")
applyCmd.Flags().BoolVarP(&c.apply.recursive, "recursive", "r", c.apply.recursive, "Recurse into subdirectories")

return applyCmd
Expand All @@ -40,6 +42,7 @@ func (c *Config) runApplyCmd(cmd *cobra.Command, args []string) error {
cmd: cmd,
filter: c.apply.filter,
init: c.apply.init,
parentDirs: c.apply.parentDirs,
recursive: c.apply.recursive,
umask: c.Umask,
preApplyFunc: c.defaultPreApplyFunc,
Expand Down
21 changes: 12 additions & 9 deletions internal/cmd/archivecmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ import (
)

type archiveCmdConfig struct {
filter *chezmoi.EntryTypeFilter
format chezmoi.ArchiveFormat
gzip bool
init bool
recursive bool
filter *chezmoi.EntryTypeFilter
format chezmoi.ArchiveFormat
gzip bool
init bool
parentDirs bool
recursive bool
}

func (c *Config) newArchiveCmd() *cobra.Command {
Expand All @@ -40,6 +41,7 @@ func (c *Config) newArchiveCmd() *cobra.Command {
archiveCmd.Flags().BoolVarP(&c.archive.gzip, "gzip", "z", c.archive.gzip, "Compress output with gzip")
archiveCmd.Flags().VarP(c.archive.filter.Exclude, "include", "i", "Include entry types")
archiveCmd.Flags().BoolVar(&c.archive.init, "init", c.archive.init, "Recreate config file from template")
archiveCmd.Flags().BoolVarP(&c.archive.parentDirs, "parent-dirs", "P", c.archive.parentDirs, "Archive parent directories")
archiveCmd.Flags().BoolVarP(&c.archive.recursive, "recursive", "r", c.archive.recursive, "Recurse into subdirectories")

return archiveCmd
Expand Down Expand Up @@ -73,10 +75,11 @@ func (c *Config) runArchiveCmd(cmd *cobra.Command, args []string) error {
return chezmoi.UnknownArchiveFormatError(format)
}
if err := c.applyArgs(cmd.Context(), archiveSystem, chezmoi.EmptyAbsPath, args, applyArgsOptions{
cmd: cmd,
filter: c.archive.filter,
init: c.archive.init,
recursive: c.archive.recursive,
cmd: cmd,
filter: c.archive.filter,
init: c.archive.init,
parentDirs: c.archive.parentDirs,
recursive: c.archive.recursive,
}); err != nil {
return err
}
Expand Down
27 changes: 27 additions & 0 deletions internal/cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,7 @@ type applyArgsOptions struct {
cmd *cobra.Command
filter *chezmoi.EntryTypeFilter
init bool
parentDirs bool
recursive bool
umask fs.FileMode
preApplyFunc chezmoi.PreApplyFunc
Expand Down Expand Up @@ -652,6 +653,10 @@ func (c *Config) applyArgs(
}
}

if options.parentDirs {
targetRelPaths = prependParentRelPaths(targetRelPaths)
}

applyOptions := chezmoi.ApplyOptions{
Filter: options.filter,
PreApplyFunc: options.preApplyFunc,
Expand Down Expand Up @@ -2941,6 +2946,28 @@ func parseCommand(command string, args []string) (string, []string, error) {
return command, args, nil
}

// prependParentRelPaths returns a new slice of RelPaths where the parents of
// each RelPath appear before each RelPath.
func prependParentRelPaths(relPaths []chezmoi.RelPath) []chezmoi.RelPath {
// For each target relative path, enumerate its parents from the root down
// and insert any parents which have not yet been seen.
result := make([]chezmoi.RelPath, 0, len(relPaths))
seenRelPaths := make(map[chezmoi.RelPath]struct{}, len(relPaths))
for _, relPath := range relPaths {
components := relPath.SplitAll()
for i := 1; i < len(components); i++ {
parentRelPath := chezmoi.EmptyRelPath.Join(components[:i]...)
if _, ok := seenRelPaths[parentRelPath]; !ok {
result = append(result, parentRelPath)
seenRelPaths[parentRelPath] = struct{}{}
}
}
result = append(result, relPath)
seenRelPaths[relPath] = struct{}{}
}
return result
}

// registerCommonFlagCompletionFuncs registers completion functions for cmd's
// common flags, recursively. It panics on any error.
func registerCommonFlagCompletionFuncs(cmd *cobra.Command) {
Expand Down
49 changes: 49 additions & 0 deletions internal/cmd/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,55 @@ func TestParseConfig(t *testing.T) {
}
}

func TestPrependParentRelPaths(t *testing.T) {
for _, tc := range []struct {
name string
relPathStrs []string
expectedRelPathStrs []string
}{
{
name: "empty",
},
{
name: "single",
relPathStrs: []string{"a"},
expectedRelPathStrs: []string{"a"},
},
{
name: "multiple",
relPathStrs: []string{"a", "b", "c"},
expectedRelPathStrs: []string{"a", "b", "c"},
},
{
name: "single_parent",
relPathStrs: []string{"a/b"},
expectedRelPathStrs: []string{"a", "a/b"},
},
{
name: "multiple_parents",
relPathStrs: []string{"a/b/c"},
expectedRelPathStrs: []string{"a", "a/b", "a/b/c"},
},
{
name: "duplicate_parents",
relPathStrs: []string{"a/b/c", "a/b/d"},
expectedRelPathStrs: []string{"a", "a/b", "a/b/c", "a/b/d"},
},
} {
t.Run(tc.name, func(t *testing.T) {
relPaths := make([]chezmoi.RelPath, len(tc.relPathStrs))
for i, relPathStr := range tc.relPathStrs {
relPaths[i] = chezmoi.NewRelPath(relPathStr)
}
expected := make([]chezmoi.RelPath, len(tc.expectedRelPathStrs))
for i, relPathStr := range tc.expectedRelPathStrs {
expected[i] = chezmoi.NewRelPath(relPathStr)
}
assert.Equal(t, expected, prependParentRelPaths(relPaths))
})
}
}

func TestInitConfigWithIncludedTemplate(t *testing.T) {
mainFilename := ".chezmoi.yaml.tmpl"
secondaryFilename := "personal.config.yaml.tmpl"
Expand Down
13 changes: 8 additions & 5 deletions internal/cmd/diffcmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type diffCmdConfig struct {
ScriptContents bool `json:"scriptContents" mapstructure:"scriptContents" yaml:"scriptContents"`
include *chezmoi.EntryTypeSet
init bool
parentDirs bool
recursive bool
}

Expand All @@ -38,6 +39,7 @@ func (c *Config) newDiffCmd() *cobra.Command {
diffCmd.Flags().VarP(c.Diff.include, "include", "i", "Include entry types")
diffCmd.Flags().BoolVar(&c.Diff.init, "init", c.Diff.init, "Recreate config file from template")
diffCmd.Flags().StringVar(&c.Diff.Pager, "pager", c.Diff.Pager, "Set pager")
diffCmd.Flags().BoolVarP(&c.Diff.parentDirs, "parent-dirs", "P", c.apply.parentDirs, "Print the diff of all parent directories")
diffCmd.Flags().BoolVarP(&c.Diff.recursive, "recursive", "r", c.Diff.recursive, "Recurse into subdirectories")
diffCmd.Flags().BoolVar(&c.Diff.Reverse, "reverse", c.Diff.Reverse, "Reverse the direction of the diff")
diffCmd.Flags().BoolVar(&c.Diff.ScriptContents, "script-contents", c.Diff.ScriptContents, "Show script contents")
Expand All @@ -47,10 +49,11 @@ func (c *Config) newDiffCmd() *cobra.Command {

func (c *Config) runDiffCmd(cmd *cobra.Command, args []string) (err error) {
return c.applyArgs(cmd.Context(), c.destSystem, c.DestDirAbsPath, args, applyArgsOptions{
cmd: cmd,
filter: chezmoi.NewEntryTypeFilter(c.Diff.include.Bits(), c.Diff.Exclude.Bits()),
init: c.Diff.init,
recursive: c.Diff.recursive,
umask: c.Umask,
cmd: cmd,
filter: chezmoi.NewEntryTypeFilter(c.Diff.include.Bits(), c.Diff.Exclude.Bits()),
init: c.Diff.init,
parentDirs: c.Diff.parentDirs,
recursive: c.Diff.recursive,
umask: c.Umask,
})
}
19 changes: 11 additions & 8 deletions internal/cmd/dumpcmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import (
)

type dumpCmdConfig struct {
filter *chezmoi.EntryTypeFilter
init bool
recursive bool
filter *chezmoi.EntryTypeFilter
init bool
parentDirs bool
recursive bool
}

func (c *Config) newDumpCmd() *cobra.Command {
Expand All @@ -30,6 +31,7 @@ func (c *Config) newDumpCmd() *cobra.Command {
dumpCmd.Flags().VarP(&c.Format, "format", "f", "Output format")
dumpCmd.Flags().VarP(c.dump.filter.Include, "include", "i", "Include entry types")
dumpCmd.Flags().BoolVar(&c.dump.init, "init", c.dump.init, "Recreate config file from template")
dumpCmd.Flags().BoolVarP(&c.dump.parentDirs, "parent-dirs", "P", c.dump.parentDirs, "Dump all parent directories")
dumpCmd.Flags().BoolVarP(&c.dump.recursive, "recursive", "r", c.dump.recursive, "Recurse into subdirectories")

return dumpCmd
Expand All @@ -38,11 +40,12 @@ func (c *Config) newDumpCmd() *cobra.Command {
func (c *Config) runDumpCmd(cmd *cobra.Command, args []string) error {
dumpSystem := chezmoi.NewDumpSystem()
if err := c.applyArgs(cmd.Context(), dumpSystem, chezmoi.EmptyAbsPath, args, applyArgsOptions{
cmd: cmd,
filter: c.dump.filter,
init: c.dump.init,
recursive: c.dump.recursive,
umask: c.Umask,
cmd: cmd,
filter: c.dump.filter,
init: c.dump.init,
parentDirs: c.dump.parentDirs,
recursive: c.dump.recursive,
umask: c.Umask,
}); err != nil {
return err
}
Expand Down
13 changes: 8 additions & 5 deletions internal/cmd/statuscmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ import (
)

type statusCmdConfig struct {
Exclude *chezmoi.EntryTypeSet `json:"exclude" mapstructure:"exclude" yaml:"exclude"`
PathStyle *chezmoi.PathStyle `json:"pathStyle" mapstructure:"pathStyle" yaml:"pathStyle"`
include *chezmoi.EntryTypeSet
init bool
recursive bool
Exclude *chezmoi.EntryTypeSet `json:"exclude" mapstructure:"exclude" yaml:"exclude"`
PathStyle *chezmoi.PathStyle `json:"pathStyle" mapstructure:"pathStyle" yaml:"pathStyle"`
include *chezmoi.EntryTypeSet
init bool
parentDirs bool
recursive bool
}

func (c *Config) newStatusCmd() *cobra.Command {
Expand All @@ -40,6 +41,7 @@ func (c *Config) newStatusCmd() *cobra.Command {
statusCmd.Flags().VarP(c.Status.PathStyle, "path-style", "p", "Path style")
statusCmd.Flags().VarP(c.Status.include, "include", "i", "Include entry types")
statusCmd.Flags().BoolVar(&c.Status.init, "init", c.Status.init, "Recreate config file from template")
statusCmd.Flags().BoolVarP(&c.Status.parentDirs, "parent-dirs", "P", c.Status.parentDirs, "Show status of all parent directories")
statusCmd.Flags().BoolVarP(&c.Status.recursive, "recursive", "r", c.Status.recursive, "Recurse into subdirectories")

return statusCmd
Expand Down Expand Up @@ -88,6 +90,7 @@ func (c *Config) runStatusCmd(cmd *cobra.Command, args []string) error {
cmd: cmd,
filter: chezmoi.NewEntryTypeFilter(c.Status.include.Bits(), c.Status.Exclude.Bits()),
init: c.Status.init,
parentDirs: c.Status.parentDirs,
recursive: c.Status.recursive,
umask: c.Umask,
preApplyFunc: preApplyFunc,
Expand Down
6 changes: 6 additions & 0 deletions internal/cmd/testdata/scripts/issue3987.txtar
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# test that chezmoi apply --parent-dirs creates parent directories
exec chezmoi apply --parent-dirs $HOME/.config/nvim/after/queries/gotmpl/injections.scm
exists $HOME/.config/nvim/after/queries/gotmpl/injections.scm

-- home/user/.local/share/chezmoi/private_dot_config/nvim/after/queries/gotmpl/injections.scm --
# contents of injections.scm
3 changes: 3 additions & 0 deletions internal/cmd/updatecmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type updateCmdConfig struct {
RecurseSubmodules bool `json:"recurseSubmodules" mapstructure:"recurseSubmodules" yaml:"recurseSubmodules"`
filter *chezmoi.EntryTypeFilter
init bool
parentDirs bool
recursive bool
}

Expand All @@ -40,6 +41,7 @@ func (c *Config) newUpdateCmd() *cobra.Command {
updateCmd.Flags().VarP(c.Update.filter.Exclude, "exclude", "x", "Exclude entry types")
updateCmd.Flags().VarP(c.Update.filter.Include, "include", "i", "Include entry types")
updateCmd.Flags().BoolVar(&c.Update.init, "init", c.Update.init, "Recreate config file from template")
updateCmd.Flags().BoolVarP(&c.Update.parentDirs, "parent-dirs", "P", c.Update.parentDirs, "Update all parent directories")
updateCmd.Flags().
BoolVar(&c.Update.RecurseSubmodules, "recurse-submodules", c.Update.RecurseSubmodules, "Recursively update submodules")
updateCmd.Flags().BoolVarP(&c.Update.recursive, "recursive", "r", c.Update.recursive, "Recurse into subdirectories")
Expand Down Expand Up @@ -92,6 +94,7 @@ func (c *Config) runUpdateCmd(cmd *cobra.Command, args []string) error {
cmd: cmd,
filter: c.Update.filter,
init: c.Update.init,
parentDirs: c.Update.parentDirs,
recursive: c.Update.recursive,
umask: c.Umask,
preApplyFunc: c.defaultPreApplyFunc,
Expand Down
21 changes: 12 additions & 9 deletions internal/cmd/verifycmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import (
)

type verifyCmdConfig struct {
Exclude *chezmoi.EntryTypeSet `json:"exclude" mapstructure:"exclude" yaml:"exclude"`
include *chezmoi.EntryTypeSet
init bool
recursive bool
Exclude *chezmoi.EntryTypeSet `json:"exclude" mapstructure:"exclude" yaml:"exclude"`
include *chezmoi.EntryTypeSet
init bool
parentDirs bool
recursive bool
}

func (c *Config) newVerifyCmd() *cobra.Command {
Expand All @@ -30,6 +31,7 @@ func (c *Config) newVerifyCmd() *cobra.Command {
verifyCmd.Flags().VarP(c.Verify.Exclude, "exclude", "x", "Exclude entry types")
verifyCmd.Flags().VarP(c.Verify.include, "include", "i", "Include entry types")
verifyCmd.Flags().BoolVar(&c.Verify.init, "init", c.Verify.init, "Recreate config file from template")
verifyCmd.Flags().BoolVarP(&c.Verify.parentDirs, "parent-dirs", "P", c.Verify.parentDirs, "Verify all parent directories")
verifyCmd.Flags().BoolVarP(&c.Verify.recursive, "recursive", "r", c.Verify.recursive, "Recurse into subdirectories")

return verifyCmd
Expand All @@ -38,10 +40,11 @@ func (c *Config) newVerifyCmd() *cobra.Command {
func (c *Config) runVerifyCmd(cmd *cobra.Command, args []string) error {
errorOnWriteSystem := chezmoi.NewErrorOnWriteSystem(c.destSystem, chezmoi.ExitCodeError(1))
return c.applyArgs(cmd.Context(), errorOnWriteSystem, c.DestDirAbsPath, args, applyArgsOptions{
cmd: cmd,
filter: chezmoi.NewEntryTypeFilter(c.Verify.include.Bits(), c.Verify.Exclude.Bits()),
init: c.Verify.init,
recursive: c.Verify.recursive,
umask: c.Umask,
cmd: cmd,
filter: chezmoi.NewEntryTypeFilter(c.Verify.include.Bits(), c.Verify.Exclude.Bits()),
init: c.Verify.init,
parentDirs: c.Verify.parentDirs,
recursive: c.Verify.recursive,
umask: c.Umask,
})
}

0 comments on commit 35148c7

Please sign in to comment.