diff --git a/internal/engine/engine.go b/internal/engine/engine.go index cf2a770..7420c46 100644 --- a/internal/engine/engine.go +++ b/internal/engine/engine.go @@ -223,16 +223,17 @@ func (e *Engine) WriteLeaf(basepath string, tier store.Tier, env string, vals ma // store so it is never lost when the leaf file is absent (e.g. fresh clone). func (e *Engine) writeLeaf(basepath string, tier store.Tier, env string, vals, seedIgnored map[string]string) error { full := filepath.Join(e.Root, leaf.Path(basepath, tier, env)) + ignoredNames := e.Store.IgnoredNames(basepath, env, tier) // Gather ignored variables to preserve: from the on-disk file first, then // falling back to the seed for any ignored name not present on disk. var ignored map[string]string - if names := e.Store.IgnoredNames(basepath, env, tier); len(names) > 0 { + if len(ignoredNames) > 0 { existing, ok, err := e.ReadLeaf(basepath, tier, env) if err != nil { return err } - for _, k := range names { + for _, k := range ignoredNames { var ( v string present bool @@ -252,11 +253,16 @@ func (e *Engine) writeLeaf(basepath string, tier store.Tier, env string, vals, s } } - // Never emit an ignored variable as a managed one. - if len(ignored) > 0 { + // Never emit an ignored variable as a managed one, even when there is no + // preserved ignored value to write. + if len(ignoredNames) > 0 { + ignoredSet := make(map[string]struct{}, len(ignoredNames)) + for _, k := range ignoredNames { + ignoredSet[k] = struct{}{} + } managed := make(map[string]string, len(vals)) for k, v := range vals { - if _, ok := ignored[k]; !ok { + if _, ok := ignoredSet[k]; !ok { managed[k] = v } } diff --git a/internal/engine/ignore_test.go b/internal/engine/ignore_test.go index f583334..c5e4f98 100644 --- a/internal/engine/ignore_test.go +++ b/internal/engine/ignore_test.go @@ -131,6 +131,33 @@ func TestIgnore_PreservesLocalManagedEdits(t *testing.T) { } } +// TestWriteLeaf_DoesNotEmitIgnoredAsManaged verifies that an ignore marker is +// enough to suppress a managed value, even when there is no ignored value to +// preserve in the trailing block. +func TestWriteLeaf_DoesNotEmitIgnoredAsManaged(t *testing.T) { + dir, _ := newRepo(t) + e := openIn(t, dir) + if err := e.Init(nil, pass); err != nil { + t.Fatal(err) + } + cell := Cell{"apps/api", "_", store.Buildtime} + e.setIgnoreMarker(cell, "SECRET") + + if err := e.writeLeaf(cell.Path, cell.Tier, cell.Env, map[string]string{ + "KEEP": "1", + "SECRET": "2", + }, nil); err != nil { + t.Fatal(err) + } + body := readFile(t, filepath.Join(dir, "apps/api/.env")) + if strings.Contains(body, "SECRET=") { + t.Fatalf("ignored key emitted as managed:\n%s", body) + } + if !strings.Contains(body, "KEEP=1") { + t.Fatalf("managed key missing:\n%s", body) + } +} + // TestIgnore_EvictsExisting verifies that ignoring an already-stored variable // drops it from the store and base but keeps it in the leaf's ignored block. func TestIgnore_EvictsExisting(t *testing.T) {