Skip to content

Commit

Permalink
feat: Add .chezmoiexternals directory for multiple externals
Browse files Browse the repository at this point in the history
  • Loading branch information
twpayne committed Jul 19, 2023
1 parent 4481451 commit ed225f0
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# `.chezmoiexternals`

If a directory called `.chezmoiexternals` exists, then all files in this
directory are treated as `.chezmoiexternal.<format>` files.
1 change: 1 addition & 0 deletions assets/chezmoi.io/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ nav:
- .chezmoi.&lt;format&gt;.tmpl: reference/special-files-and-directories/chezmoi-format-tmpl.md
- .chezmoidata.&lt;format&gt;: reference/special-files-and-directories/chezmoidata-format.md
- .chezmoiexternal.&lt;format&gt;: reference/special-files-and-directories/chezmoiexternal-format.md
- .chezmoiexternals: reference/special-files-and-directories/chezmoiexternals.md
- .chezmoiignore: reference/special-files-and-directories/chezmoiignore.md
- .chezmoiremove: reference/special-files-and-directories/chezmoiremove.md
- .chezmoiroot: reference/special-files-and-directories/chezmoiroot.md
Expand Down
2 changes: 2 additions & 0 deletions pkg/chezmoi/chezmoi.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ const (
VersionName = Prefix + "version"
dataName = Prefix + "data"
externalName = Prefix + "external"
externalsDirName = Prefix + "externals"
ignoreName = Prefix + "ignore"
removeName = Prefix + "remove"
scriptsDirName = Prefix + "scripts"
Expand Down Expand Up @@ -115,6 +116,7 @@ var knownPrefixedFiles = newSet(
var knownPrefixedDirs = newSet(
TemplatesDirName,
dataName,
externalsDirName,
scriptsDirName,
)

Expand Down
48 changes: 44 additions & 4 deletions pkg/chezmoi/sourcestate.go
Original file line number Diff line number Diff line change
Expand Up @@ -940,7 +940,13 @@ func (s *SourceState) Read(ctx context.Context, options *ReadOptions) error {
case s.templateDataOnly:
return nil
case isPrefixDotFormat(fileInfo.Name(), externalName) || isPrefixDotFormatDotTmpl(fileInfo.Name(), externalName):
return s.addExternal(sourceAbsPath)
parentAbsPath, _ := sourceAbsPath.Split()
return s.addExternal(sourceAbsPath, parentAbsPath)
case fileInfo.Name() == externalsDirName:
if err := s.addExternalDir(ctx, sourceAbsPath); err != nil {
return err
}
return vfs.SkipDir
case fileInfo.Name() == ignoreName || fileInfo.Name() == ignoreName+TemplateSuffix:
return s.addPatterns(s.ignore, sourceAbsPath, parentSourceRelPath)
case fileInfo.Name() == removeName || fileInfo.Name() == removeName+TemplateSuffix:
Expand Down Expand Up @@ -1287,9 +1293,8 @@ func (s *SourceState) TemplateData() map[string]any {
}

// addExternal adds external source entries to s.
func (s *SourceState) addExternal(sourceAbsPath AbsPath) error {
parentAbsPath, _ := sourceAbsPath.Split()

func (s *SourceState) addExternal(sourceAbsPath, parentAbsPath AbsPath) error {
fmt.Fprintf(os.Stderr, "addExternal sourceAbsPath=%s parentAbsPath=%s\n", sourceAbsPath, parentAbsPath)
parentRelPath, err := parentAbsPath.TrimDirPrefix(s.sourceDirAbsPath)
if err != nil {
return err
Expand Down Expand Up @@ -1325,6 +1330,41 @@ func (s *SourceState) addExternal(sourceAbsPath AbsPath) error {
return nil
}

// addExternalDir adds all externals in externalsDirAbsPath to s.
func (s *SourceState) addExternalDir(ctx context.Context, externalsDirAbsPath AbsPath) error {
walkFunc := func(ctx context.Context, externalAbsPath AbsPath, fileInfo fs.FileInfo, err error) error {
if externalAbsPath == externalsDirAbsPath {
return nil
}
if err == nil && fileInfo.Mode().Type() == fs.ModeSymlink {
fileInfo, err = s.system.Stat(externalAbsPath)
}
switch {
case err != nil:
return err
case strings.HasPrefix(fileInfo.Name(), Prefix):
return fmt.Errorf("%s: not allowed in %s directory", externalAbsPath, externalsDirName)
case strings.HasPrefix(fileInfo.Name(), ignorePrefix):
if fileInfo.IsDir() {
return vfs.SkipDir
}
return nil
case fileInfo.Mode().IsRegular():
parentAbsPath, _ := externalAbsPath.Split()
fmt.Fprintf(os.Stderr, "before addExternal parentAbsPath=%s\n", parentAbsPath)
return s.addExternal(externalAbsPath, parentAbsPath.TrimSuffix("/").Dir())
case fileInfo.IsDir():
return nil
default:
return &unsupportedFileTypeError{
absPath: externalAbsPath,
mode: fileInfo.Mode(),
}
}
}
return concurrentWalkSourceDir(ctx, s.system, externalsDirAbsPath, walkFunc)
}

// addPatterns executes the template at sourceAbsPath, interprets the result as
// a list of patterns, and adds all patterns found to patternSet.
func (s *SourceState) addPatterns(
Expand Down
12 changes: 12 additions & 0 deletions pkg/cmd/testdata/scripts/external.txtar
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,12 @@ exec chezmoi apply
[umask:002] cmpmod 775 $HOME/.file
[umask:022] cmpmod 755 $HOME/.file

chhome home14/user

# test that chezmoi reads external files from the .chezmoiexternal directory
exec chezmoi apply
cmp $HOME/.file golden/dir/file

-- archive/dir/file --
# contents of dir/file
-- golden/.file --
Expand Down Expand Up @@ -156,6 +162,12 @@ exec chezmoi apply
path = "dir/file"
executable = true
stripComponents = 1
-- home14/user/.local/share/chezmoi/.chezmoiexternals/external.toml --
[".file"]
type = "archive-file"
url = "{{ env "HTTPD_URL" }}/archive.tar.gz"
path = "dir/file"
stripComponents = 1
-- home2/user/.local/share/chezmoi/.chezmoiexternal.toml --
[".file"]
type = "file"
Expand Down

0 comments on commit ed225f0

Please sign in to comment.