agentpr: close and reopen issues
- add issue close and issue reopen subcommands - add GetIssue and SetIssueState to the Gitea client - fail when the issue is already in the requested state - document the new subcommands in README.md and AGENTS.md
This commit is contained in:
+48
-2
@@ -9,6 +9,8 @@
|
||||
// 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 issue close --repo owner/repo --issue 12
|
||||
// agentpr issue reopen --repo owner/repo --issue 12
|
||||
// agentpr whoami
|
||||
package main
|
||||
|
||||
@@ -69,9 +71,15 @@ func newPRCmd() *cobra.Command {
|
||||
func newIssueCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "issue",
|
||||
Short: "File and edit issues, and post issue comments",
|
||||
Short: "File, edit, close and reopen issues, and post issue comments",
|
||||
}
|
||||
cmd.AddCommand(newIssueCreateCmd(), newCommentCmd("issue", "issue", "Post a comment on an issue"), newIssueEditCmd())
|
||||
cmd.AddCommand(
|
||||
newIssueCreateCmd(),
|
||||
newCommentCmd("issue", "issue", "Post a comment on an issue"),
|
||||
newIssueEditCmd(),
|
||||
newIssueStateCmd("close", "Close an issue", agent.IssueStateClosed),
|
||||
newIssueStateCmd("reopen", "Reopen a closed issue", agent.IssueStateOpen),
|
||||
)
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -301,6 +309,44 @@ func newIssueEditCmd() *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
// newIssueStateCmd builds `issue close` and `issue reopen`, which differ only
|
||||
// in the state they ask for. An issue already in that state is an error, not a
|
||||
// silent success: Gitea answers the PATCH with 200 either way.
|
||||
func newIssueStateCmd(use, short, state string) *cobra.Command {
|
||||
var repo string
|
||||
var issue int
|
||||
cmd := &cobra.Command{
|
||||
Use: use,
|
||||
Short: short,
|
||||
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")
|
||||
}
|
||||
c, err := client()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updated, err := c.SetIssueState(owner+"/"+name, issue, state)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("#%d %s %s\n", updated.Number, updated.State, updated.HTMLURL)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
f := cmd.Flags()
|
||||
f.StringVar(&repo, "repo", "", "Repository as owner/repo (required)")
|
||||
f.IntVar(&issue, "issue", 0, "Issue number (required)")
|
||||
_ = cmd.MarkFlagRequired("repo")
|
||||
_ = cmd.MarkFlagRequired("issue")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func newWhoamiCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "whoami",
|
||||
|
||||
@@ -149,3 +149,56 @@ func TestCommentCommandsStayInStep(t *testing.T) {
|
||||
t.Error("issue comment must take --issue and only --issue")
|
||||
}
|
||||
}
|
||||
|
||||
// Both state commands need a repo and an issue number; cobra and the RunE
|
||||
// guard must reject their absence before anything reaches for a token.
|
||||
func TestIssueStateCommandsRequireFlags(t *testing.T) {
|
||||
for _, verb := range []string{"close", "reopen"} {
|
||||
t.Run(verb+" without --issue", func(t *testing.T) {
|
||||
err := execute("issue", verb, "--repo", "unkin/repo")
|
||||
if err == nil {
|
||||
t.Fatal("Execute() = nil, want an error when --issue is missing")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "issue") {
|
||||
t.Errorf("error = %q, want it to name the missing --issue flag", err)
|
||||
}
|
||||
})
|
||||
t.Run(verb+" without --repo", func(t *testing.T) {
|
||||
err := execute("issue", verb, "--issue", "12")
|
||||
if err == nil {
|
||||
t.Fatal("Execute() = nil, want an error when --repo is missing")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "repo") {
|
||||
t.Errorf("error = %q, want it to name the missing --repo flag", err)
|
||||
}
|
||||
})
|
||||
t.Run(verb+" with a zero --issue", func(t *testing.T) {
|
||||
err := execute("issue", verb, "--repo", "unkin/repo", "--issue", "0")
|
||||
if err == nil {
|
||||
t.Fatal("Execute() = nil, want an error for a non-positive issue number")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "--issue must be a positive") {
|
||||
t.Errorf("error = %q, want it to reject the issue number", err)
|
||||
}
|
||||
})
|
||||
t.Run(verb+" with a malformed --repo", func(t *testing.T) {
|
||||
if err := execute("issue", verb, "--repo", "not-a-repo", "--issue", "12"); err == nil {
|
||||
t.Fatal("Execute() = nil, want error for a malformed --repo")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Closing and reopening are issue-only: a PR is closed by a human, so the pr
|
||||
// group must not grow these verbs by accident.
|
||||
func TestPRHasNoStateCommands(t *testing.T) {
|
||||
for _, verb := range []string{"close", "reopen"} {
|
||||
if cmd, _, err := newRootCmd().Find([]string{"pr", verb}); err == nil && cmd.Name() == verb {
|
||||
t.Errorf("pr %s exists; closing a PR is not agentpr's to do", verb)
|
||||
}
|
||||
cmd, _, err := newRootCmd().Find([]string{"issue", verb})
|
||||
if err != nil || cmd.Name() != verb {
|
||||
t.Fatalf("issue %s not found: %v", verb, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user