From b957cab2e03a11d195c3a96c76d73f24b942f3bc Mon Sep 17 00:00:00 2001 From: TangoEnSkai <21152231+TangoEnSkai@users.noreply.github.com> Date: Sun, 23 Aug 2026 12:40:36 +0900 Subject: [PATCH] libs/template: don't create directories that a later {{skip}} names The walk records each directory it visits and persistToDisk creates them all, so a template ends up with the directory structure even where every file was skipped. That is deliberate, but it tests the skip patterns only at the moment the walk reaches a directory. {{skip}} directives are registered while rendering files, so a file processed after a directory has been visited can name that directory. The walk has already recorded it by then and the directory is created regardless -- empty, because its own files do match the pattern and are dropped. Re-test the accumulated patterns when materializing directories. Directories whose files merely happened to be skipped are unaffected: those patterns match the files, not the directory, which keeps the existing behaviour of preserving a template's structure. --- .../cli/template-skip-dir-not-created.md | 1 + libs/template/renderer.go | 18 ++++++++++++- libs/template/renderer_test.go | 26 +++++++++++++++++++ .../template/dir1/file1.tmpl | 1 + .../template/dir2/file2.tmpl | 3 +++ 5 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 .nextchanges/cli/template-skip-dir-not-created.md create mode 100644 libs/template/testdata/skip-dir-after-visit/template/dir1/file1.tmpl create mode 100644 libs/template/testdata/skip-dir-after-visit/template/dir2/file2.tmpl diff --git a/.nextchanges/cli/template-skip-dir-not-created.md b/.nextchanges/cli/template-skip-dir-not-created.md new file mode 100644 index 00000000000..0f1c6f8f56a --- /dev/null +++ b/.nextchanges/cli/template-skip-dir-not-created.md @@ -0,0 +1 @@ +`bundle init` no longer creates an empty directory when a template's `{{skip}}` directive names a directory that the template walk had already visited. diff --git a/libs/template/renderer.go b/libs/template/renderer.go index cce0861c0d2..53d5bc95125 100644 --- a/libs/template/renderer.go +++ b/libs/template/renderer.go @@ -372,8 +372,24 @@ func (r *renderer) persistToDisk(ctx context.Context, out filer.Filer) error { continue } + // A {{skip}} directive can name a directory the walk has already + // visited, because the directive lives in a file processed later. + // walk() only tests the patterns known when it reaches a directory, so + // re-test the accumulated set here; otherwise the directory is created + // even though it was explicitly skipped. This leaves directories whose + // files merely happened to be skipped untouched, since those patterns + // match the files rather than the directory itself. + match, err := isSkipped(dir, r.skipPatterns) + if err != nil { + return err + } + if match { + log.Infof(r.ctx, "skipping directory: %s", dir) + continue + } + // Check if directory already exists (may have been created during file writes) - _, err := out.Stat(ctx, dir) + _, err = out.Stat(ctx, dir) if err == nil { // Directory already exists, nothing to do continue diff --git a/libs/template/renderer_test.go b/libs/template/renderer_test.go index 837584c452a..48209e921c9 100644 --- a/libs/template/renderer_test.go +++ b/libs/template/renderer_test.go @@ -631,3 +631,29 @@ func TestRendererSubTemplateInPath(t *testing.T) { assert.Equal(t, "my_directory/my_file", f.RelPath()) } } + +// A {{skip}} directive can name a directory that the BFS walk has already +// visited, because the directive lives in a file processed later. The +// directory must still not be created: the walk records visited directories +// before any later file can skip them, so the skip patterns have to be applied +// again when directories are materialized. +func TestRendererSkipDirectoryVisitedBeforeSkipDirective(t *testing.T) { + ctx := t.Context() + ctx = cmdctx.SetWorkspaceClient(ctx, nil) + tmpDir := t.TempDir() + + helpers := loadHelpers(ctx) + r, err := newRenderer(ctx, nil, helpers, os.DirFS("."), "./testdata/skip-dir-after-visit/template", "./testdata/skip-dir-after-visit/library") + require.NoError(t, err) + + err = r.walk() + require.NoError(t, err) + + out, err := filer.NewLocalClient(tmpDir) + require.NoError(t, err) + err = r.persistToDisk(ctx, out) + require.NoError(t, err) + + assert.NoDirExists(t, filepath.Join(tmpDir, "dir1")) + assert.FileExists(t, filepath.Join(tmpDir, "dir2", "file2")) +} diff --git a/libs/template/testdata/skip-dir-after-visit/template/dir1/file1.tmpl b/libs/template/testdata/skip-dir-after-visit/template/dir1/file1.tmpl new file mode 100644 index 00000000000..c4f25cdf545 --- /dev/null +++ b/libs/template/testdata/skip-dir-after-visit/template/dir1/file1.tmpl @@ -0,0 +1 @@ +This file is skipped by dir2, so dir1 must not be created diff --git a/libs/template/testdata/skip-dir-after-visit/template/dir2/file2.tmpl b/libs/template/testdata/skip-dir-after-visit/template/dir2/file2.tmpl new file mode 100644 index 00000000000..0f02601ab35 --- /dev/null +++ b/libs/template/testdata/skip-dir-after-visit/template/dir2/file2.tmpl @@ -0,0 +1,3 @@ +I should be the only file created + +{{skip "../dir1"}}{{skip "../dir1/*"}}