carlos / platform Public

carlos deploy: cover the clean-tree case, name the dirty tree, guard-order comment

Fix round 1 review findings on the dirty-tree preflight:

- Add TestDeployAcceptsADerivedVersionFromACleanTree: the branch whose
  failure mode is refusing every ordinary deploy was untested, and
  invisible everywhere else in the suite (every runDeploy call site passes
  -version explicitly; CI checks out a clean tree). initDirtyGitRepo is
  now initGitRepo(t, dirty bool), with initDirtyGitRepo kept as a thin
  wrapper for readability at existing call sites.
- deployPreflight now names the absolute path of the tree it inspected in
  its refusal, so an operator shipping a prebuilt artifact from an
  unrelated dirty checkout knows which tree tripped it.
- Comment the explicitVersion capture in runDeploy: it MUST precede the
  gitShortHead() default-fill, and moving it below would silently defeat
  the guard (explicitVersion never empty, refusal never fires) with every
  existing test still green, since none of them rely on the derived-version
  path through runDeploy itself.
Paul Campbell pushed by paul@keymail.dev f860c61a6e4efa18ba3ce8b32c149664c006553d parent 2bd906f
2 files changed, +50 −8
  • deploy.go +16 −2
  • deploy_test.go +34 −6
diff --git a/deploy.go b/deploy.go
index a57693017..c8538b9d7 100644
--- a/deploy.go
+++ b/deploy.go
@@ -50,6 +50,13 @@ func runDeploy(args []string) {
"[-account <sqid-or-name>] [-account-id <sqid>] [-console <origin>] [-capabilities <file.json>] " +
"[-channel <ch>] [-host <host>] [-timeout 120s] <files-or-site-dir>\n" +
"or, in a project with saved defaults (or at a terminal, to create them): carlos deploy"
+ // MUST be captured before the gitShortHead() default-fill below: this is
+ // the only signal that distinguishes "operator typed -version" from
+ // "flag defaulted to the derived sha", and deployPreflight uses it to
+ // decide whether a dirty tree is refused or trusted. Move this line below
+ // the default-fill and explicitVersion is never empty, the guard never
+ // fires — and silently: every runDeploy test in this package passes
+ // -version explicitly, so that regression would still show green.
explicitVersion := *ver
if *ver == "" {
*ver = gitShortHead()
@@ -950,6 +957,13 @@ func deployPreflight(dir, explicitVersion string) error {
if explicitVersion != "" {
return nil
}
+ // Named in the refusal below so an operator shipping a prebuilt artifact
+ // from an unrelated dirty checkout knows which tree tripped it, rather
+ // than guessing from a bare "-C .".
+ named := dir
+ if abs, err := filepath.Abs(dir); err == nil {
+ named = abs
+ }
out, err := exec.Command("git", "-C", dir, "status", "--porcelain").Output()
if err != nil {
return nil // not a git checkout, or no git: nothing to be dirty
@@ -957,8 +971,8 @@ func deployPreflight(dir, explicitVersion string) error {
if len(bytes.TrimSpace(out)) == 0 {
return nil
}
- return errors.New("this tree has uncommitted changes and -version defaults to the git short sha, " +
- "which would name a commit this build does not match — commit, or pass -version explicitly")
+ return fmt.Errorf("%s has uncommitted changes and -version defaults to the git short sha, "+
+ "which would name a commit this build does not match — commit, or pass -version explicitly", named)
}
// gitShortHead resolves -version's default: the working directory's short
diff --git a/deploy_test.go b/deploy_test.go
index bfa6da928..53da1b438 100644
--- a/deploy_test.go
+++ b/deploy_test.go
@@ -1700,10 +1700,14 @@ func TestDeployRefusesAMalformedProbeAndFallsBackToTheHeader(t *testing.T) {
}
}
-// initDirtyGitRepo builds a git repo in a fresh temp dir with one committed
-// file, then modifies that file so the working tree is dirty. Skips the test
-// if git is unavailable in this environment.
-func initDirtyGitRepo(t *testing.T) string {
+// initGitRepo builds a git repo in a fresh temp dir with one committed file.
+// With dirty=true, the file is then modified so the working tree is dirty;
+// with dirty=false, the tree is left exactly as committed — the branch whose
+// failure mode is "refuses every ordinary deploy" and which nothing else in
+// this suite exercises, since every runDeploy call site passes -version
+// explicitly and CI itself checks out a clean tree. Skips the test if git is
+// unavailable in this environment.
+func initGitRepo(t *testing.T, dirty bool) string {
t.Helper()
if _, err := exec.LookPath("git"); err != nil {
t.Skip("git not available")
@@ -1729,12 +1733,21 @@ func initDirtyGitRepo(t *testing.T) string {
}
run("add", "file.txt")
run("commit", "-m", "initial")
- if err := os.WriteFile(f, []byte("dirty\n"), 0o644); err != nil {
- t.Fatal(err)
+ if dirty {
+ if err := os.WriteFile(f, []byte("dirty\n"), 0o644); err != nil {
+ t.Fatal(err)
+ }
}
return dir
}
+// initDirtyGitRepo is initGitRepo(t, true) — kept as its own name since it
+// reads better at call sites that only ever want a dirty tree.
+func initDirtyGitRepo(t *testing.T) string {
+ t.Helper()
+ return initGitRepo(t, true)
+}
+
// TestDeployRefusesADerivedVersionFromADirtyTree: the default -version is the
// git short sha, so a dirty tree ships a stamp naming a commit the binary was
// not built from — which is precisely how a false "verified" happens, and why
@@ -1749,7 +1762,22 @@ func TestDeployRefusesADerivedVersionFromADirtyTree(t *testing.T) {
if !strings.Contains(err.Error(), "-version") {
t.Fatalf("refusal %q should name the flag that overrides it", err)
}
+ if !strings.Contains(err.Error(), dir) {
+ t.Fatalf("refusal %q should name the tree it inspected (%s)", err, dir)
+ }
if err := deployPreflight(dir, "abc1234"); err != nil {
t.Fatalf("an explicit -version from a dirty tree = %v, want accepted", err)
}
}
+
+// TestDeployAcceptsADerivedVersionFromACleanTree: the branch every other
+// test in this file skips over. All 13 runDeploy call sites pass -version
+// explicitly, and CI itself checks out a clean tree, so a preflight that
+// wrongly refused every derived-version deploy from a clean checkout would
+// ship green with nothing else in the suite to catch it.
+func TestDeployAcceptsADerivedVersionFromACleanTree(t *testing.T) {
+ dir := initGitRepo(t, false)
+ if err := deployPreflight(dir, "" /* no explicit -version */); err != nil {
+ t.Fatalf("a clean tree with a derived version = %v, want accepted", err)
+ }
+}