From 63aefeb890a485c32f538d2c10aa671a7ae02485 Mon Sep 17 00:00:00 2001 From: Paul Annesley Date: Tue, 7 Apr 2026 16:16:44 +0930 Subject: [PATCH] preflight: if the repo has no remote, skip push & cleanup This is useful for a pipeline that defines e.g. /path/to/codebase/.git as the repository, with a buildkite-agent running on that local machine. Changes to that git repo don't need to be pushed anywhere. Amp-Thread-ID: https://ampcode.com/threads/T-019d66ad-7398-770a-990d-705838d5dba4 Co-authored-by: Amp --- cmd/preflight/preflight.go | 5 ++++- internal/preflight/snapshot.go | 31 ++++++++++++++++++----------- internal/preflight/snapshot_test.go | 27 +++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 13 deletions(-) diff --git a/cmd/preflight/preflight.go b/cmd/preflight/preflight.go index 3d32a53a..9584434b 100644 --- a/cmd/preflight/preflight.go +++ b/cmd/preflight/preflight.go @@ -111,6 +111,9 @@ func (c *PreflightCmd) Run(kongCtx *kong.Context, globals cli.GlobalFlags) error snapshotDetail += fmt.Sprintf("\n %s %s", file.StatusSymbol(), file.Path) } } + if result.PushSkipped { + snapshotDetail += "\nWarning: no remote \"origin\" found; push skipped" + } _ = renderer.Render(Event{Type: EventOperation, Time: time.Now(), PreflightID: preflightID.String(), Title: "Pushed snapshot of working tree...", Detail: snapshotDetail}) _ = renderer.Render(Event{Type: EventOperation, Time: time.Now(), PreflightID: preflightID.String(), Title: fmt.Sprintf("Creating build on %s/%s...", resolvedPipeline.Org, resolvedPipeline.Name)}) @@ -168,7 +171,7 @@ func (c *PreflightCmd) Run(kongCtx *kong.Context, globals cli.GlobalFlags) error buildResult := NewResult(finalBuild) finalErr := buildResult.Error() - if !c.NoCleanup { + if !c.NoCleanup && !result.PushSkipped { _ = renderer.Render(Event{Type: EventOperation, Time: time.Now(), PreflightID: preflightID.String(), Title: fmt.Sprintf("Cleaning up remote branch %s...", result.Branch)}) if cleanupErr := preflight.Cleanup(repoRoot, result.Ref, globals.EnableDebug()); cleanupErr != nil { _ = renderer.Render(Event{Type: EventOperation, Time: time.Now(), PreflightID: preflightID.String(), Title: fmt.Sprintf("Warning: failed to delete remote branch %s: %v", result.Ref, cleanupErr)}) diff --git a/internal/preflight/snapshot.go b/internal/preflight/snapshot.go index 2784d2f6..ef79a2a6 100644 --- a/internal/preflight/snapshot.go +++ b/internal/preflight/snapshot.go @@ -16,10 +16,11 @@ type FileChange struct { // SnapshotResult holds the output of a successful snapshot operation. type SnapshotResult struct { - Commit string - Ref string - Branch string - Files []FileChange + Commit string + Ref string + Branch string + Files []FileChange + PushSkipped bool } func (r SnapshotResult) ShortCommit() string { @@ -110,17 +111,23 @@ func Snapshot(dir string, preflightID uuid.UUID, opts ...SnapshotOption) (*Snaps return nil, err } - // Push the commit to the remote branch. - refspec := fmt.Sprintf("%s:%s", commit, ref) - if err := gitRun(dir, env, cfg.debug, "push", "origin", refspec); err != nil { - return nil, err + var pushSkipped bool + if _, err := gitOutput(dir, env, cfg.debug, "remote", "get-url", "origin"); err != nil { + pushSkipped = true + } else { + // Push the commit to the remote branch. + refspec := fmt.Sprintf("%s:%s", commit, ref) + if err := gitRun(dir, env, cfg.debug, "push", "origin", refspec); err != nil { + return nil, err + } } return &SnapshotResult{ - Commit: commit, - Ref: ref, - Branch: branch, - Files: files, + Commit: commit, + Ref: ref, + Branch: branch, + Files: files, + PushSkipped: pushSkipped, }, nil } diff --git a/internal/preflight/snapshot_test.go b/internal/preflight/snapshot_test.go index 495ba719..6925e403 100644 --- a/internal/preflight/snapshot_test.go +++ b/internal/preflight/snapshot_test.go @@ -364,6 +364,33 @@ func TestDiffFiles(t *testing.T) { } } +func TestSnapshot_NoRemote(t *testing.T) { + worktree := initTestRepo(t) + + // Remove the origin remote. + runGit(t, worktree, "remote", "remove", "origin") + + // Add a change so we exercise the commit path too. + if err := os.WriteFile(filepath.Join(worktree, "README.md"), []byte("# changed\n"), 0o644); err != nil { + t.Fatal(err) + } + + preflightID := uuid.MustParse("00000000-0000-0000-0000-000000000010") + result, err := Snapshot(worktree, preflightID) + if err != nil { + t.Fatalf("Snapshot() error: %v", err) + } + + if !result.PushSkipped { + t.Error("expected PushSkipped to be true when no remote exists") + } + + // The commit should still be valid. + if len(result.Commit) != 40 { + t.Errorf("expected 40-char SHA, got %q", result.Commit) + } +} + func TestSnapshot_CleanWorktree(t *testing.T) { worktree := initTestRepo(t)