diff --git a/forge/testhelper_test.go b/forge/testhelper_test.go index ee98aaa..825b88b 100644 --- a/forge/testhelper_test.go +++ b/forge/testhelper_test.go @@ -82,7 +82,8 @@ func newForgejoMux() *http.ServeMux { } jsonResponse(w, map[string]any{ "id": 10, "name": "org-repo", "full_name": "test-org/org-repo", - "owner": map[string]any{"login": "test-org"}, + "owner": map[string]any{"login": "test-org"}, + "default_branch": "main", }) }) diff --git a/jobrunner/handlers/dispatch.go b/jobrunner/handlers/dispatch.go index c7b5138..f80be2b 100644 --- a/jobrunner/handlers/dispatch.go +++ b/jobrunner/handlers/dispatch.go @@ -157,7 +157,7 @@ func (h *DispatchHandler) Execute(ctx context.Context, signal *jobrunner.Pipelin } // Build ticket. - targetBranch := "new" // TODO: resolve from epic or repo default + targetBranch := h.resolveTargetBranch(safeOwner, safeRepo) ticketID := fmt.Sprintf("%s-%s-%d-%d", safeOwner, safeRepo, signal.ChildNumber, time.Now().Unix()) ticket := DispatchTicket{ @@ -263,6 +263,21 @@ func (h *DispatchHandler) Execute(ctx context.Context, signal *jobrunner.Pipelin }, nil } +func (h *DispatchHandler) resolveTargetBranch(owner, repo string) string { + const fallbackBranch = "main" + + if h.forge == nil { + return fallbackBranch + } + + repoInfo, err := h.forge.GetRepo(owner, repo) + if err != nil || repoInfo == nil || repoInfo.DefaultBranch == "" { + return fallbackBranch + } + + return repoInfo.DefaultBranch +} + // failDispatch handles cleanup when dispatch fails (adds failed label, removes in-progress). func (h *DispatchHandler) failDispatch(signal *jobrunner.PipelineSignal, reason string) { if failedLabel, err := h.forge.EnsureLabel(signal.RepoOwner, signal.RepoName, LabelAgentFailed, ColorAgentFailed); err == nil { diff --git a/jobrunner/handlers/dispatch_test.go b/jobrunner/handlers/dispatch_test.go index 9c4a9a5..c889c2e 100644 --- a/jobrunner/handlers/dispatch_test.go +++ b/jobrunner/handlers/dispatch_test.go @@ -175,7 +175,7 @@ func TestDispatch_TicketJSON_Good(t *testing.T) { IssueNumber: 5, IssueTitle: "Fix the thing", IssueBody: "Please fix this bug", - TargetBranch: "new", + TargetBranch: "main", EpicNumber: 3, ForgeURL: "https://forge.lthn.ai", ForgeUser: "darbs-claude", @@ -198,7 +198,7 @@ func TestDispatch_TicketJSON_Good(t *testing.T) { assert.Equal(t, float64(5), decoded["issue_number"]) assert.Equal(t, "Fix the thing", decoded["issue_title"]) assert.Equal(t, "Please fix this bug", decoded["issue_body"]) - assert.Equal(t, "new", decoded["target_branch"]) + assert.Equal(t, "main", decoded["target_branch"]) assert.Equal(t, float64(3), decoded["epic_number"]) assert.Equal(t, "https://forge.lthn.ai", decoded["forge_url"]) assert.Equal(t, "darbs-claude", decoded["forgejo_user"]) @@ -254,6 +254,30 @@ func TestDispatch_TicketJSON_Good_OmitsEmptyModelRunner_Good(t *testing.T) { assert.False(t, hasRunner, "runner should be omitted when empty") } +func TestDispatch_Execute_Good_UsesRepoDefaultBranch_Good(t *testing.T) { + srv := httptest.NewServer(withVersion(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }))) + defer srv.Close() + + client := newTestForgeClient(t, srv.URL) + spinner := newTestSpinner(map[string]agentci.AgentConfig{ + "darbs-claude": {Host: "claude@192.168.0.201", QueueDir: "~/ai-work/queue", Active: true}, + }) + h := NewDispatchHandler(client, srv.URL, "test-token", spinner) + + sig := &jobrunner.PipelineSignal{ + NeedsCoding: true, + Assignee: "darbs-claude", + RepoOwner: "test-org", + RepoName: "org-repo", + ChildNumber: 1, + } + + branch := h.resolveTargetBranch(sig.RepoOwner, sig.RepoName) + assert.Equal(t, "main", branch) +} + func TestDispatch_runRemote_Good_EscapesPath_Good(t *testing.T) { outputPath := filepath.Join(t.TempDir(), "ssh-output.txt") toolPath := writeFakeSSHCommand(t, outputPath)