Add agentpr issue create/comment/edit
Filing an issue as unkin-agent means hand-rolling a POST to the issues API with a manually minted token, the exact credential handling agentpr exists to encapsulate. - add `issue create`, `issue comment` and `issue edit`, mirroring the pr command group's flags, output and errors - add CreateIssue/EditIssue to the Gitea client, sharing EditOptions with EditPR - build both comment commands from one constructor so pr and issue comments cannot drift off Gitea's shared endpoint - cover create, edit, a missing repo, an API error and missing flags
This commit is contained in:
+132
-29
@@ -6,6 +6,9 @@
|
||||
// agentpr pr create --repo owner/repo --base main --head feature --title T --body B
|
||||
// agentpr pr comment --repo owner/repo --pr 12 --body "..."
|
||||
// agentpr pr edit --repo owner/repo --pr 12 --title T --body B
|
||||
// agentpr issue create --repo owner/repo --title T --body B
|
||||
// agentpr issue comment --repo owner/repo --issue 12 --body "..."
|
||||
// agentpr issue edit --repo owner/repo --issue 12 --title T --body B
|
||||
// agentpr whoami
|
||||
package main
|
||||
|
||||
@@ -34,14 +37,14 @@ func main() {
|
||||
func newRootCmd() *cobra.Command {
|
||||
root := &cobra.Command{
|
||||
Use: "agentpr",
|
||||
Short: "Manage Gitea PRs and comments as an agent user.",
|
||||
Long: "agentpr manages Gitea pull requests and comments as an agent user, using a\nGitea token minted from Vault (AppRole login + gitea/creds/<AGENT_LOGIN>).\nSet AGENT_LOGIN to act as another agent identity, or GITEA_CREDS_PATH to name\nthe Vault creds path outright.",
|
||||
Short: "Manage Gitea PRs, issues and comments as an agent user.",
|
||||
Long: "agentpr manages Gitea pull requests, issues and comments as an agent user,\nusing a Gitea token minted from Vault (AppRole login + gitea/creds/<AGENT_LOGIN>).\nSet AGENT_LOGIN to act as another agent identity, or GITEA_CREDS_PATH to name\nthe Vault creds path outright.",
|
||||
Version: version,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
root.SetVersionTemplate("{{.Version}}\n")
|
||||
|
||||
root.AddCommand(newPRCmd(), newWhoamiCmd(), newVersionCmd())
|
||||
root.AddCommand(newPRCmd(), newIssueCmd(), newWhoamiCmd(), newVersionCmd())
|
||||
return root
|
||||
}
|
||||
|
||||
@@ -59,7 +62,16 @@ func newPRCmd() *cobra.Command {
|
||||
Use: "pr",
|
||||
Short: "Create and edit PRs, and post PR comments",
|
||||
}
|
||||
cmd.AddCommand(newPRCreateCmd(), newPRCommentCmd(), newPREditCmd())
|
||||
cmd.AddCommand(newPRCreateCmd(), newCommentCmd("pr", "PR", "Post a comment on a pull request"), newPREditCmd())
|
||||
return cmd
|
||||
}
|
||||
|
||||
func newIssueCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "issue",
|
||||
Short: "File and edit issues, and post issue comments",
|
||||
}
|
||||
cmd.AddCommand(newIssueCreateCmd(), newCommentCmd("issue", "issue", "Post a comment on an issue"), newIssueEditCmd())
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -104,20 +116,24 @@ func newPRCreateCmd() *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func newPRCommentCmd() *cobra.Command {
|
||||
// newCommentCmd builds a comment command whose number flag is named numFlag.
|
||||
// Gitea backs every PR with an issue of the same number and serves comments
|
||||
// from the issue endpoint, so `pr comment` and `issue comment` are one command
|
||||
// under two flag names rather than two implementations that could drift.
|
||||
func newCommentCmd(numFlag, noun, short string) *cobra.Command {
|
||||
var repo, body string
|
||||
var pr int
|
||||
var number int
|
||||
cmd := &cobra.Command{
|
||||
Use: "comment",
|
||||
Short: "Post a comment on a pull request",
|
||||
Short: short,
|
||||
SilenceUsage: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
owner, name, err := agent.ParseRepo(repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if pr <= 0 {
|
||||
return fmt.Errorf("--pr must be a positive PR number")
|
||||
if number <= 0 {
|
||||
return fmt.Errorf("--%s must be a positive %s number", numFlag, noun)
|
||||
}
|
||||
if body == "" {
|
||||
return fmt.Errorf("--body is required")
|
||||
@@ -126,20 +142,20 @@ func newPRCommentCmd() *cobra.Command {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cm, err := c.CreateComment(owner+"/"+name, pr, body)
|
||||
cm, err := c.CreateComment(owner+"/"+name, number, body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("comment %d posted on %s/%s#%d\n", cm.ID, owner, name, pr)
|
||||
fmt.Printf("comment %d posted on %s/%s#%d\n", cm.ID, owner, name, number)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
f := cmd.Flags()
|
||||
f.StringVar(&repo, "repo", "", "Repository as owner/repo (required)")
|
||||
f.IntVar(&pr, "pr", 0, "PR number (required)")
|
||||
f.IntVar(&number, numFlag, 0, noun+" number (required)")
|
||||
f.StringVar(&body, "body", "", "Comment body (required)")
|
||||
_ = cmd.MarkFlagRequired("repo")
|
||||
_ = cmd.MarkFlagRequired("pr")
|
||||
_ = cmd.MarkFlagRequired(numFlag)
|
||||
_ = cmd.MarkFlagRequired("body")
|
||||
return cmd
|
||||
}
|
||||
@@ -159,22 +175,9 @@ func newPREditCmd() *cobra.Command {
|
||||
if pr <= 0 {
|
||||
return fmt.Errorf("--pr must be a positive PR number")
|
||||
}
|
||||
// Only the flags actually given are sent: omitting --title must
|
||||
// leave the title as it is, not blank it.
|
||||
var opts agent.EditPROptions
|
||||
if cmd.Flags().Changed("title") {
|
||||
// Gitea ignores an empty title, so sending one would report
|
||||
// success while changing nothing.
|
||||
if title == "" {
|
||||
return fmt.Errorf("--title cannot be empty: a title can be set but not cleared")
|
||||
}
|
||||
opts.Title = &title
|
||||
}
|
||||
if cmd.Flags().Changed("body") {
|
||||
opts.Body = &body
|
||||
}
|
||||
if opts.Title == nil && opts.Body == nil {
|
||||
return fmt.Errorf("at least one of --title or --body is required")
|
||||
opts, err := editOptions(cmd, title, body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c, err := client()
|
||||
if err != nil {
|
||||
@@ -198,6 +201,106 @@ func newPREditCmd() *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
// editOptions turns the --title/--body flags actually given into an edit
|
||||
// payload. Only the flags present are sent: omitting --title must leave the
|
||||
// title as it is, not blank it.
|
||||
func editOptions(cmd *cobra.Command, title, body string) (agent.EditOptions, error) {
|
||||
var opts agent.EditOptions
|
||||
if cmd.Flags().Changed("title") {
|
||||
// Gitea ignores an empty title, so sending one would report success
|
||||
// while changing nothing.
|
||||
if title == "" {
|
||||
return opts, fmt.Errorf("--title cannot be empty: a title can be set but not cleared")
|
||||
}
|
||||
opts.Title = &title
|
||||
}
|
||||
if cmd.Flags().Changed("body") {
|
||||
opts.Body = &body
|
||||
}
|
||||
if opts.Title == nil && opts.Body == nil {
|
||||
return opts, fmt.Errorf("at least one of --title or --body is required")
|
||||
}
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
func newIssueCreateCmd() *cobra.Command {
|
||||
var repo, title, body string
|
||||
cmd := &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "File an issue",
|
||||
SilenceUsage: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
owner, name, err := agent.ParseRepo(repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if title == "" {
|
||||
return fmt.Errorf("--title is required")
|
||||
}
|
||||
c, err := client()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
issue, err := c.CreateIssue(owner+"/"+name, agent.CreateIssueOptions{
|
||||
Title: title,
|
||||
Body: body,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("#%d %s\n", issue.Number, issue.HTMLURL)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
f := cmd.Flags()
|
||||
f.StringVar(&repo, "repo", "", "Repository as owner/repo (required)")
|
||||
f.StringVar(&title, "title", "", "Issue title (required)")
|
||||
f.StringVar(&body, "body", "", "Issue body")
|
||||
_ = cmd.MarkFlagRequired("repo")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func newIssueEditCmd() *cobra.Command {
|
||||
var repo, title, body string
|
||||
var issue int
|
||||
cmd := &cobra.Command{
|
||||
Use: "edit",
|
||||
Short: "Edit an issue's title and/or body",
|
||||
SilenceUsage: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
owner, name, err := agent.ParseRepo(repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if issue <= 0 {
|
||||
return fmt.Errorf("--issue must be a positive issue number")
|
||||
}
|
||||
opts, err := editOptions(cmd, title, body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c, err := client()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updated, err := c.EditIssue(owner+"/"+name, issue, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("#%d %s\n", updated.Number, updated.HTMLURL)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
f := cmd.Flags()
|
||||
f.StringVar(&repo, "repo", "", "Repository as owner/repo (required)")
|
||||
f.IntVar(&issue, "issue", 0, "Issue number (required)")
|
||||
f.StringVar(&title, "title", "", "New issue title (unchanged when omitted)")
|
||||
f.StringVar(&body, "body", "", "New issue body (unchanged when omitted)")
|
||||
_ = cmd.MarkFlagRequired("repo")
|
||||
_ = cmd.MarkFlagRequired("issue")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func newWhoamiCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "whoami",
|
||||
|
||||
Reference in New Issue
Block a user