Files
unkin-agent d78644d173
ci/woodpecker/pr/build Pipeline failed
ci/woodpecker/pr/test Pipeline failed
ci/woodpecker/pr/pre-commit Pipeline failed
Add agentpr and watchpr CLI tools
agentpr manages PRs/comments/whoami as unkin-agent (Vault AppRole -> gitea creds
-> Gitea API), fixing tea's post-as-Ben default. watchpr polls PRs and alerts
only on merge/close, human comment, CI failure, or lost mergeability.

- cobra multi-binary layout mirroring node-lookup (cmd/ + internal/)
- Makefile (build, patch|minor|major, completions, rpm), nfpm RPM with both
  binaries + bash/zsh/fish completions, woodpecker CI publishing to rpm-internal
- unit tests for parsing, meaningful-change detection, and the Vault+Gitea client
2026-08-12 21:37:09 +10:00

60 lines
1.6 KiB
Go

package agent
import (
"fmt"
"strconv"
"strings"
)
// PRRef identifies a single pull request by repository and number.
type PRRef struct {
Owner string
Repo string
Number int
}
// String renders the ref in canonical owner/repo#N form.
func (r PRRef) String() string {
return fmt.Sprintf("%s/%s#%d", r.Owner, r.Repo, r.Number)
}
// RepoPath returns the "owner/repo" portion used in Gitea API URLs.
func (r PRRef) RepoPath() string {
return r.Owner + "/" + r.Repo
}
// ParsePRRef parses "owner/repo#N" or "owner/repo:N" into a PRRef.
func ParsePRRef(s string) (PRRef, error) {
s = strings.TrimSpace(s)
sep := strings.IndexAny(s, "#:")
if sep < 0 {
return PRRef{}, fmt.Errorf("invalid PR reference %q: expected owner/repo#N or owner/repo:N", s)
}
repoPart := s[:sep]
numPart := s[sep+1:]
owner, repo, ok := strings.Cut(repoPart, "/")
if !ok || owner == "" || repo == "" {
return PRRef{}, fmt.Errorf("invalid PR reference %q: repo must be owner/repo", s)
}
if strings.Contains(repo, "/") {
return PRRef{}, fmt.Errorf("invalid PR reference %q: repo must be owner/repo", s)
}
n, err := strconv.Atoi(numPart)
if err != nil || n <= 0 {
return PRRef{}, fmt.Errorf("invalid PR reference %q: PR number must be a positive integer", s)
}
return PRRef{Owner: owner, Repo: repo, Number: n}, nil
}
// ParseRepo validates and splits an "owner/repo" string.
func ParseRepo(s string) (owner, repo string, err error) {
s = strings.TrimSpace(s)
owner, repo, ok := strings.Cut(s, "/")
if !ok || owner == "" || repo == "" || strings.Contains(repo, "/") {
return "", "", fmt.Errorf("invalid repo %q: expected owner/repo", s)
}
return owner, repo, nil
}