agent-orchestrator/backend/internal/cli/pr_ref.go

91 lines
3.0 KiB
Go

package cli
import (
"context"
"errors"
"fmt"
"net/url"
"strconv"
"strings"
)
func (c *commandContext) resolvePRRef(ctx context.Context, ref string, project projectDetails) (string, error) {
ref = strings.TrimSpace(ref)
if ref == "" {
return "", usageError{errors.New("PR reference must be a github.com PR URL or a number")}
}
if isNumericPRRef(ref) {
repo := strings.TrimSpace(project.Repo)
if repo == "" {
// The daemon must not shell out to external CLIs from its loopback API;
// when the durable project record lacks repo_origin_url, the thin CLI
// does the one-off gh lookup from the registered project checkout and
// sends the daemon a normalized URL.
out, err := c.deps.CommandOutputInDir(ctx, project.Path, "gh", "repo", "view", "--json", "url", "-q", ".url")
if err != nil || strings.TrimSpace(string(out)) == "" {
return "", usageError{errors.New("gh not available; pass the full PR URL")}
}
repo = strings.TrimSpace(string(out))
}
owner, name, err := cliGitHubRepoFromURL(repo)
if err != nil {
return "", usageError{errors.New("PR reference must be a github.com PR URL or a number")}
}
n, _ := strconv.Atoi(strings.TrimPrefix(ref, "#"))
return fmt.Sprintf("https://github.com/%s/%s/pull/%d", owner, name, n), nil
}
owner, name, n, err := cliParseGitHubPRURL(ref)
if err != nil || owner == "" || name == "" || n <= 0 {
return "", usageError{errors.New("PR reference must be a github.com PR URL or a number")}
}
return fmt.Sprintf("https://github.com/%s/%s/pull/%d", owner, name, n), nil
}
func isNumericPRRef(ref string) bool {
ref = strings.TrimPrefix(strings.TrimSpace(ref), "#")
n, err := strconv.Atoi(ref)
return err == nil && n > 0
}
func cliParseGitHubPRURL(raw string) (string, string, int, error) {
u, err := url.Parse(raw)
if err != nil {
return "", "", 0, err
}
if !strings.EqualFold(u.Scheme, "https") || !strings.EqualFold(u.Hostname(), "github.com") {
return "", "", 0, errors.New("not github")
}
parts := strings.Split(strings.Trim(u.Path, "/"), "/")
if len(parts) != 4 || parts[2] != "pull" {
return "", "", 0, errors.New("not pr")
}
n, err := strconv.Atoi(parts[3])
if err != nil || n <= 0 {
return "", "", 0, errors.New("bad number")
}
return parts[0], strings.TrimSuffix(parts[1], ".git"), n, nil
}
func cliGitHubRepoFromURL(raw string) (string, string, error) {
raw = strings.TrimSpace(raw)
if strings.HasPrefix(raw, "git@github.com:") {
parts := strings.Split(strings.TrimSuffix(strings.TrimPrefix(raw, "git@github.com:"), ".git"), "/")
if len(parts) == 2 && parts[0] != "" && parts[1] != "" {
return parts[0], parts[1], nil
}
return "", "", errors.New("bad repo")
}
u, err := url.Parse(raw)
if err != nil {
return "", "", err
}
if !strings.EqualFold(u.Hostname(), "github.com") {
return "", "", errors.New("not github")
}
parts := strings.Split(strings.Trim(u.Path, "/"), "/")
if len(parts) < 2 || parts[0] == "" || parts[1] == "" {
return "", "", errors.New("bad repo")
}
return parts[0], strings.TrimSuffix(parts[1], ".git"), nil
}