Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 50 additions & 2 deletions internal/agent/ground/ground.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,32 @@ func Ground(ctx context.Context, cfg *Config, opts Options) (string, error) {
logf(opts.Log, "orun agent serve: grounding — cloning %s (blobless) at %s into %s\n",
redactCredentials(cfg.Remote), refOrDefault(cfg.Ref), dir)
if _, err := runGit(ctx, "", args...); err != nil {
return "", stageErr(StageClone, err)
// A zero-commit repository has no refs, so `--branch <ref>` cannot
// match — and a fresh product repo is the blueprint bootstrap's
// normal starting point ("an empty repo is ideal"). Confirm the
// remote really is refless before rerouting; any other clone
// failure keeps its original error.
if cfg.Ref == "" || !remoteIsEmpty(ctx, cfg.Remote, opts.CredentialHelper) {
return "", stageErr(StageClone, err)
}
logf(opts.Log, "orun agent serve: grounding — %s is an empty repository, cloning refless and starting %s unborn\n",
redactCredentials(cfg.Remote), cfg.Ref)
// git removes the clone target on failure; clear defensively in
// case a partial dir survived, then clone without the ref pin.
_ = os.RemoveAll(dir)
args = []string{"clone", "--filter=blob:none"}
if opts.CredentialHelper != "" {
args = append(args, "--config", "credential.helper="+opts.CredentialHelper)
}
args = append(args, cfg.Remote, dir)
if _, err := runGit(ctx, "", args...); err != nil {
return "", stageErr(StageClone, err)
}
// Aim the unborn HEAD at the bound ref so the very first commit is
// born on the branch the platform expects to see pushed.
if _, err := runGit(ctx, dir, "symbolic-ref", "HEAD", "refs/heads/"+cfg.Ref); err != nil {
return "", stageErr(StageClone, err)
}
}
}

Expand All @@ -163,7 +188,10 @@ func Ground(ctx context.Context, cfg *Config, opts Options) (string, error) {
logf(opts.Log, "orun agent serve: grounding — task branch %s exists, reused\n", opts.Branch)
} else {
args := []string{"checkout", "-b", opts.Branch}
if cfg.Ref != "" {
// The start point must resolve locally; on an empty remote the
// bound ref is an unborn HEAD, and the task branch starts unborn
// from it. (A bad ref on a real repo already failed the clone.)
if cfg.Ref != "" && refResolves(ctx, dir, cfg.Ref) {
args = append(args, cfg.Ref)
}
if _, err := runGit(ctx, dir, args...); err != nil {
Expand Down Expand Up @@ -212,6 +240,26 @@ func branchExists(ctx context.Context, dir, branch string) bool {
return err == nil
}

// refResolves reports whether the ref names a commit in the local clone.
func refResolves(ctx context.Context, dir, ref string) bool {
_, err := runGit(ctx, dir, "rev-parse", "--verify", "--quiet", ref+"^{commit}")
return err == nil
}

// remoteIsEmpty reports whether the remote has no refs at all — a freshly
// created repository. The listing rides the same credential helper as the
// clone; any listing failure reads as NOT empty, so the clone's own error
// stays the one reported.
func remoteIsEmpty(ctx context.Context, remote, credentialHelper string) bool {
var args []string
if credentialHelper != "" {
args = append(args, "-c", "credential.helper="+credentialHelper)
}
args = append(args, "ls-remote", "--heads", "--tags", remote)
out, err := runGit(ctx, "", args...)
return err == nil && strings.TrimSpace(out) == ""
}

// runGit runs `git -C <dir> <args…>` (plain `git <args…>` when dir is empty —
// the clone creates its own directory) and returns trimmed stdout. A failure
// carries git's trimmed stderr, credential-redacted; stage tagging is the
Expand Down
50 changes: 50 additions & 0 deletions internal/agent/ground/ground_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -316,3 +316,53 @@ func assertStage(t *testing.T, err error, stage string) {
t.Errorf("error %q lacks the terminal marker", err.Error())
}
}

// newEmptyRemote builds a bare repo with no commits at all — the shape of a
// product repo the instant GitHub creates it, and the blueprint bootstrap's
// normal starting point ("an empty repo is ideal").
func newEmptyRemote(t *testing.T) string {
t.Helper()
requireGit(t)
bare := filepath.Join(t.TempDir(), "empty.git")
mustGit(t, "", "init", "--quiet", "--bare", "--initial-branch=main", bare)
return bare
}

func TestGroundEmptyRemoteStartsUnbornAtRef(t *testing.T) {
// A zero-commit remote has no refs for `--branch` to match; grounding must
// still come up, with HEAD unborn at the bound ref so the first commit is
// born on the branch the platform expects pushed. (Observed live: the
// blueprint bootstrap on a fresh repo died at clone and the session hung
// at "Agent coming online".)
remote := newEmptyRemote(t)
cfg := &Config{Remote: remote, FullName: "acme/newborn", Ref: "main"}
dir, err := Ground(context.Background(), cfg, Options{WorkdirRoot: t.TempDir()})
if err != nil {
t.Fatalf("Ground on an empty remote: %v", err)
}
if head := mustGit(t, dir, "symbolic-ref", "HEAD"); head != "refs/heads/main" {
t.Errorf("HEAD = %q, want refs/heads/main (unborn)", head)
}
// The workflow's first commit + push must land on main upstream.
mustGit(t, dir, "config", "user.email", "test@example.com")
mustGit(t, dir, "config", "user.name", "Test")
mustGit(t, dir, "commit", "--quiet", "--allow-empty", "-m", "first")
mustGit(t, dir, "push", "--quiet", "origin", "main")
if got := mustGit(t, "", "-C", remote, "rev-parse", "--abbrev-ref", "HEAD"); got != "main" {
t.Errorf("remote HEAD = %q, want main", got)
}
}

func TestGroundEmptyRemoteCreatesUnbornTaskBranch(t *testing.T) {
// Task runs carry a branch; on unborn history it starts unborn too rather
// than failing on a start point that cannot resolve.
remote := newEmptyRemote(t)
cfg := &Config{Remote: remote, FullName: "acme/newborn", Ref: "main"}
dir, err := Ground(context.Background(), cfg, Options{Branch: "agent/ORN-9-implementer", WorkdirRoot: t.TempDir()})
if err != nil {
t.Fatalf("Ground: %v", err)
}
if head := mustGit(t, dir, "symbolic-ref", "HEAD"); head != "refs/heads/agent/ORN-9-implementer" {
t.Errorf("HEAD = %q, want the unborn task branch", head)
}
}
Loading