From 8525ec867d5d0ffeaa8f2e8e9e043b50d7ee0c08 Mon Sep 17 00:00:00 2001 From: Andrew A Date: Sun, 17 May 2026 21:21:50 +0200 Subject: [PATCH] fix: skip files with no updates Do not rewrite files that would not be updated (e.g. no vars configured). --- cmd/doctor.go | 12 ++++- cmd/doctor_test.go | 17 ++++++++ cmd/patch.go | 4 ++ cmd/patch_test.go | 106 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 138 insertions(+), 1 deletion(-) diff --git a/cmd/doctor.go b/cmd/doctor.go index ca9c34b..7c86b1e 100644 --- a/cmd/doctor.go +++ b/cmd/doctor.go @@ -70,13 +70,23 @@ func runChecks(cfg *config.Config, cfgErr error, deps checkDeps) []result { return results } - // Check 5: env files exist (warn only) + // Check 5: env files exist (warn only) + inert files with no checkout vars (info) for _, ef := range cfg.EnvFiles { if deps.existingEnvFiles[ef.Path] { results = append(results, ok(fmt.Sprintf("env file: %s", ef.Path))) } else { results = append(results, warn(fmt.Sprintf("env file: %s — not found (will be created on first patch)", ef.Path))) } + + applicable := 0 + for _, v := range ef.Vars { + if v.On == "checkout" { + applicable++ + } + } + if applicable == 0 { + results = append(results, info(fmt.Sprintf("env file: %s — no vars apply on checkout; file will be left untouched", ef.Path))) + } } // Check 6: strategies valid diff --git a/cmd/doctor_test.go b/cmd/doctor_test.go index 530ce18..f61a85e 100644 --- a/cmd/doctor_test.go +++ b/cmd/doctor_test.go @@ -126,6 +126,23 @@ func TestRunChecks_EnvFileMissing(t *testing.T) { } } +func TestRunChecks_EnvFileNoCheckoutVars(t *testing.T) { + cfg := &config.Config{ + Project: "myapp", + EnvFiles: []config.EnvFile{ + {Path: ".env", Vars: nil}, + }, + } + results := runChecks(cfg, nil, happyDeps) + r, found := findByPrefix(results, "env file: .env — no vars apply on checkout") + if !found { + t.Fatalf("expected info line for env file with no checkout vars, got: %v", results) + } + if r.status != "info" { + t.Errorf("expected info status, got %q", r.status) + } +} + func TestRunChecks_UnknownStrategy(t *testing.T) { cfg := &config.Config{ Project: "myapp", diff --git a/cmd/patch.go b/cmd/patch.go index 2b4f2e2..e71d6fc 100644 --- a/cmd/patch.go +++ b/cmd/patch.go @@ -60,6 +60,10 @@ func patchEnvFiles(cfg *config.Config, branch string) error { sensitiveVars[v.Name] = v.Sensitive } + if len(patches) == 0 { + continue + } + if ef.Backup { if err := env.BackupFile(ef.Path); err != nil { return fmt.Errorf("backup %s: %w", ef.Path, err) diff --git a/cmd/patch_test.go b/cmd/patch_test.go index 4afff89..6017921 100644 --- a/cmd/patch_test.go +++ b/cmd/patch_test.go @@ -185,6 +185,112 @@ func TestPatchEnvFiles_BackupCreated(t *testing.T) { } } +func TestPatchEnvFiles_SkipsWhenNoVars(t *testing.T) { + dir := t.TempDir() + envPath := filepath.Join(dir, ".env") + original := []byte("DB_NAME=keep\n# a comment\nOTHER=val\n") + if err := os.WriteFile(envPath, original, 0600); err != nil { + t.Fatal(err) + } + before, err := os.Stat(envPath) + if err != nil { + t.Fatal(err) + } + + cfg := &config.Config{ + Project: "myapp", + EnvFiles: []config.EnvFile{ + {Path: envPath, Vars: nil}, + }, + } + + if err := patchEnvFiles(cfg, "feature-x"); err != nil { + t.Fatalf("patchEnvFiles: %v", err) + } + + got, err := os.ReadFile(envPath) + if err != nil { + t.Fatal(err) + } + if string(got) != string(original) { + t.Errorf("file content changed: got %q, want %q", got, original) + } + after, err := os.Stat(envPath) + if err != nil { + t.Fatal(err) + } + if !before.ModTime().Equal(after.ModTime()) { + t.Errorf("mtime changed: before %v, after %v", before.ModTime(), after.ModTime()) + } +} + +func TestPatchEnvFiles_SkipsWhenAllVarsNonCheckout(t *testing.T) { + dir := t.TempDir() + envPath := filepath.Join(dir, ".env") + original := []byte("DB_NAME=keep\n") + if err := os.WriteFile(envPath, original, 0600); err != nil { + t.Fatal(err) + } + before, err := os.Stat(envPath) + if err != nil { + t.Fatal(err) + } + + cfg := &config.Config{ + Project: "myapp", + EnvFiles: []config.EnvFile{ + { + Path: envPath, + Vars: []config.Var{ + {Name: "DB_NAME", Strategy: "template", On: "db_create"}, + }, + }, + }, + } + + if err := patchEnvFiles(cfg, "feature-x"); err != nil { + t.Fatalf("patchEnvFiles: %v", err) + } + + got, err := os.ReadFile(envPath) + if err != nil { + t.Fatal(err) + } + if string(got) != string(original) { + t.Errorf("file content changed: got %q, want %q", got, original) + } + after, err := os.Stat(envPath) + if err != nil { + t.Fatal(err) + } + if !before.ModTime().Equal(after.ModTime()) { + t.Errorf("mtime changed: before %v, after %v", before.ModTime(), after.ModTime()) + } +} + +func TestPatchEnvFiles_NoBackupWhenSkipped(t *testing.T) { + dir := t.TempDir() + envPath := filepath.Join(dir, ".env") + if err := os.WriteFile(envPath, []byte("DB_NAME=keep\n"), 0600); err != nil { + t.Fatal(err) + } + + cfg := &config.Config{ + Project: "myapp", + EnvFiles: []config.EnvFile{ + {Path: envPath, Backup: true, Vars: nil}, + }, + } + + if err := patchEnvFiles(cfg, "feature-x"); err != nil { + t.Fatalf("patchEnvFiles: %v", err) + } + + if _, err := os.Stat(envPath + ".bak"); !os.IsNotExist(err) { + t.Errorf("expected no backup file, but it exists (or stat err: %v)", err) + } +} + func TestPatchEnvFiles_NoBackupByDefault(t *testing.T) { dir := t.TempDir() envPath := filepath.Join(dir, ".env")