diff --git a/internal/cmd/config.go b/internal/cmd/config.go index d61bacec588..5cc7cbb0def 100644 --- a/internal/cmd/config.go +++ b/internal/cmd/config.go @@ -811,8 +811,15 @@ func (c *Config) createAndReloadConfigFile(cmd *cobra.Command) error { // createConfigFile creates a config file using a template and returns its // contents. func (c *Config) createConfigFile(filename chezmoi.RelPath, data []byte, cmd *cobra.Command) ([]byte, error) { + // Clone funcMap and restore it after creating the config. + // This ensures that the init template functions + // are removed before "normal" template parsing. funcMap := make(template.FuncMap) chezmoi.RecursiveMerge(funcMap, c.templateFuncs) + defer func() { + c.templateFuncs = funcMap + }() + initTemplateFuncs := map[string]any{ "exit": c.exitInitTemplateFunc, "promptBool": c.promptBoolInteractiveTemplateFunc, @@ -826,9 +833,9 @@ func (c *Config) createConfigFile(filename chezmoi.RelPath, data []byte, cmd *co "stdinIsATTY": c.stdinIsATTYInitTemplateFunc, "writeToStdout": c.writeToStdout, } - chezmoi.RecursiveMerge(funcMap, initTemplateFuncs) + chezmoi.RecursiveMerge(c.templateFuncs, initTemplateFuncs) - tmpl, err := chezmoi.ParseTemplate(filename.String(), data, funcMap, chezmoi.TemplateOptions{ + tmpl, err := chezmoi.ParseTemplate(filename.String(), data, c.templateFuncs, chezmoi.TemplateOptions{ Options: slices.Clone(c.Template.Options), }) if err != nil { diff --git a/internal/cmd/config_test.go b/internal/cmd/config_test.go index e2b7350b0eb..cb1c2e60bad 100644 --- a/internal/cmd/config_test.go +++ b/internal/cmd/config_test.go @@ -266,6 +266,30 @@ func TestParseConfig(t *testing.T) { } } +func TestInitConfigWithIncludedTemplate(t *testing.T) { + mainFilename := ".chezmoi.yaml.tmpl" + secondaryFilename := "personal.config.yaml.tmpl" + mainContents := chezmoitest.JoinLines( + `color: true`, + fmt.Sprintf(`{{ includeTemplate %q . }}`, secondaryFilename), + ) + secondaryContents := chezmoitest.JoinLines( + `verbose: true`, + `safe: {{ stdinIsATTY }}`, + ) + + chezmoitest.WithTestFS(t, map[string]any{ + "/home/user/.local/share/chezmoi/" + mainFilename: mainContents, + "/home/user/.local/share/chezmoi/" + secondaryFilename: secondaryContents, + }, func(fileSystem vfs.FS) { + c := newTestConfig(t, fileSystem) + assert.NoError(t, c.execute([]string{"init"})) + assert.Equal(t, true, c.Color.Value(c.colorAutoFunc)) + assert.Equal(t, true, c.Verbose) + assert.Equal(t, false, c.Safe) + }) +} + func TestUpperSnakeCaseToCamelCase(t *testing.T) { for s, expected := range map[string]string{ "BUG_REPORT_URL": "bugReportURL",