feat: v0.3.0d — add PR checks, iostreams, aliases, and broad enhancements
Add PR checks command, iostreams/text packages for colored table output, top-level run/workflow aliases matching gh CLI structure. Enhance actions, issues, PRs, releases, repos, labels, milestones, and wiki commands with improved flags, JSON output, and error handling.
This commit is contained in:
parent
7c0dcc8696
commit
113505de95
29 changed files with 3131 additions and 542 deletions
370
cmd/issue.go
370
cmd/issue.go
|
|
@ -3,14 +3,12 @@ package cmd
|
|||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"forgejo.zerova.net/sid/fgj-sid/internal/api"
|
||||
"forgejo.zerova.net/sid/fgj-sid/internal/config"
|
||||
"forgejo.zerova.net/sid/fgj-sid/internal/text"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
@ -24,46 +22,114 @@ var issueListCmd = &cobra.Command{
|
|||
Use: "list [flags]",
|
||||
Short: "List issues",
|
||||
Long: "List issues in a repository.",
|
||||
RunE: runIssueList,
|
||||
Example: ` # List open issues
|
||||
fgj issue list
|
||||
|
||||
# List closed issues for a specific repo
|
||||
fgj issue list -s closed -R owner/repo
|
||||
|
||||
# Output as JSON
|
||||
fgj issue list --json`,
|
||||
RunE: runIssueList,
|
||||
}
|
||||
|
||||
var issueViewCmd = &cobra.Command{
|
||||
Use: "view <number>",
|
||||
Short: "View an issue",
|
||||
Long: "Display detailed information about an issue.",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runIssueView,
|
||||
Example: ` # View issue #42
|
||||
fgj issue view 42
|
||||
|
||||
# View using URL
|
||||
fgj issue view https://codeberg.org/owner/repo/issues/42
|
||||
|
||||
# Open in browser
|
||||
fgj issue view 42 --web
|
||||
|
||||
# View an issue from a specific repo as JSON
|
||||
fgj issue view 42 -R owner/repo --json`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runIssueView,
|
||||
}
|
||||
|
||||
var issueCreateCmd = &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Create an issue",
|
||||
Long: "Create a new issue.",
|
||||
RunE: runIssueCreate,
|
||||
Example: ` # Create an issue with a title
|
||||
fgj issue create -t "Fix login bug"
|
||||
|
||||
# Create an issue with title, body, and labels
|
||||
fgj issue create -t "Add dark mode" -b "We need a dark theme" -l feature -l ui`,
|
||||
RunE: runIssueCreate,
|
||||
}
|
||||
|
||||
var issueCommentCmd = &cobra.Command{
|
||||
Use: "comment <number>",
|
||||
Short: "Add a comment to an issue",
|
||||
Long: "Add a comment to an existing issue.",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runIssueComment,
|
||||
Example: ` # Add a comment to issue #42
|
||||
fgj issue comment 42 -b "This is fixed in the latest release"
|
||||
|
||||
# Comment on an issue in a specific repo
|
||||
fgj issue comment 10 -b "Confirmed on my end" -R owner/repo`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runIssueComment,
|
||||
}
|
||||
|
||||
var issueCloseCmd = &cobra.Command{
|
||||
Use: "close <number>",
|
||||
Short: "Close an issue",
|
||||
Long: "Close an existing issue.",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runIssueClose,
|
||||
Example: ` # Close issue #42
|
||||
fgj issue close 42
|
||||
|
||||
# Close with a comment
|
||||
fgj issue close 42 -c "Fixed in commit abc1234"`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runIssueClose,
|
||||
}
|
||||
|
||||
var issueReopenCmd = &cobra.Command{
|
||||
Use: "reopen <number>",
|
||||
Short: "Reopen an issue",
|
||||
Long: "Reopen a closed issue.",
|
||||
Example: ` # Reopen issue #42
|
||||
fgj issue reopen 42`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runIssueReopen,
|
||||
}
|
||||
|
||||
var issueDeleteCmd = &cobra.Command{
|
||||
Use: "delete <number>",
|
||||
Short: "Delete an issue",
|
||||
Long: "Delete an issue permanently.",
|
||||
Example: ` # Delete issue #42
|
||||
fgj issue delete 42
|
||||
|
||||
# Delete without confirmation
|
||||
fgj issue delete 42 -y`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runIssueDelete,
|
||||
}
|
||||
|
||||
var issueEditCmd = &cobra.Command{
|
||||
Use: "edit <number>",
|
||||
Short: "Edit an issue",
|
||||
Long: "Edit an existing issue's title, body, or state.",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runIssueEdit,
|
||||
Example: ` # Update the title of issue #42
|
||||
fgj issue edit 42 -t "Updated title"
|
||||
|
||||
# Reopen a closed issue
|
||||
fgj issue edit 42 -s open
|
||||
|
||||
# Add and remove labels
|
||||
fgj issue edit 42 --add-label bug --remove-label wontfix
|
||||
|
||||
# Add a dependency
|
||||
fgj issue edit 42 --add-dependency 10`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runIssueEdit,
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
@ -73,19 +139,31 @@ func init() {
|
|||
issueCmd.AddCommand(issueCreateCmd)
|
||||
issueCmd.AddCommand(issueCommentCmd)
|
||||
issueCmd.AddCommand(issueCloseCmd)
|
||||
issueCmd.AddCommand(issueReopenCmd)
|
||||
issueCmd.AddCommand(issueDeleteCmd)
|
||||
issueCmd.AddCommand(issueEditCmd)
|
||||
|
||||
issueReopenCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
||||
|
||||
issueListCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
||||
issueListCmd.Flags().StringP("state", "s", "open", "Filter by state: open, closed, all")
|
||||
issueListCmd.Flags().Bool("json", false, "Output issues as JSON")
|
||||
issueListCmd.Flags().IntP("limit", "L", 30, "Maximum number of results")
|
||||
issueListCmd.Flags().StringP("assignee", "a", "", "Filter by assignee username")
|
||||
issueListCmd.Flags().String("author", "", "Filter by author username")
|
||||
issueListCmd.Flags().StringSliceP("label", "l", nil, "Filter by label names")
|
||||
issueListCmd.Flags().StringP("search", "S", "", "Search keyword filter")
|
||||
addJSONFlags(issueListCmd, "Output issues as JSON")
|
||||
|
||||
issueViewCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
||||
issueViewCmd.Flags().Bool("json", false, "Output issue as JSON")
|
||||
addJSONFlags(issueViewCmd, "Output issue as JSON")
|
||||
issueViewCmd.Flags().BoolP("web", "w", false, "Open in web browser")
|
||||
|
||||
issueCreateCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
||||
issueCreateCmd.Flags().StringP("title", "t", "", "Title for the issue")
|
||||
issueCreateCmd.Flags().StringP("body", "b", "", "Body for the issue")
|
||||
issueCreateCmd.Flags().StringSliceP("label", "l", nil, "Labels to add (can be specified multiple times)")
|
||||
issueCreateCmd.Flags().StringSliceP("assignee", "a", nil, "Assign people by their login. Use \"@me\" to self-assign.")
|
||||
issueCreateCmd.Flags().StringP("milestone", "m", "", "Milestone name to associate with the issue")
|
||||
|
||||
issueCommentCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
||||
issueCommentCmd.Flags().StringP("body", "b", "", "Comment body")
|
||||
|
|
@ -93,6 +171,9 @@ func init() {
|
|||
issueCloseCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
||||
issueCloseCmd.Flags().StringP("comment", "c", "", "Comment body to add before closing")
|
||||
|
||||
issueDeleteCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
||||
issueDeleteCmd.Flags().BoolP("yes", "y", false, "Skip confirmation prompt")
|
||||
|
||||
issueEditCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
||||
issueEditCmd.Flags().StringP("title", "t", "", "New title for the issue")
|
||||
issueEditCmd.Flags().StringP("body", "b", "", "New body for the issue")
|
||||
|
|
@ -106,6 +187,11 @@ func init() {
|
|||
func runIssueList(cmd *cobra.Command, args []string) error {
|
||||
repo, _ := cmd.Flags().GetString("repo")
|
||||
state, _ := cmd.Flags().GetString("state")
|
||||
limit, _ := cmd.Flags().GetInt("limit")
|
||||
assignee, _ := cmd.Flags().GetString("assignee")
|
||||
author, _ := cmd.Flags().GetString("author")
|
||||
labels, _ := cmd.Flags().GetStringSlice("label")
|
||||
search, _ := cmd.Flags().GetString("search")
|
||||
|
||||
owner, name, err := parseRepo(repo)
|
||||
if err != nil {
|
||||
|
|
@ -134,9 +220,16 @@ func runIssueList(cmd *cobra.Command, args []string) error {
|
|||
return fmt.Errorf("invalid state: %s", state)
|
||||
}
|
||||
|
||||
ios.StartSpinner("Fetching issues...")
|
||||
issues, _, err := client.ListRepoIssues(owner, name, gitea.ListIssueOption{
|
||||
State: stateType,
|
||||
State: stateType,
|
||||
Labels: labels,
|
||||
KeyWord: search,
|
||||
CreatedBy: author,
|
||||
AssignedBy: assignee,
|
||||
ListOptions: gitea.ListOptions{PageSize: limit},
|
||||
})
|
||||
ios.StopSpinner()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list issues: %w", err)
|
||||
}
|
||||
|
|
@ -148,28 +241,26 @@ func runIssueList(cmd *cobra.Command, args []string) error {
|
|||
}
|
||||
}
|
||||
|
||||
if jsonOutput, _ := cmd.Flags().GetBool("json"); jsonOutput {
|
||||
return writeJSON(nonPRIssues)
|
||||
if wantJSON(cmd) {
|
||||
return outputJSON(cmd, nonPRIssues)
|
||||
}
|
||||
|
||||
if len(nonPRIssues) == 0 {
|
||||
fmt.Printf("No %s issues in %s/%s\n", state, owner, name)
|
||||
fmt.Fprintf(ios.Out, "No %s issues in %s/%s\n", state, owner, name)
|
||||
return nil
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||
_, _ = fmt.Fprintf(w, "NUMBER\tTITLE\tSTATE\n")
|
||||
tp := ios.NewTablePrinter()
|
||||
tp.AddHeader("NUMBER", "TITLE", "STATE")
|
||||
for _, issue := range nonPRIssues {
|
||||
_, _ = fmt.Fprintf(w, "#%d\t%s\t%s\n", issue.Index, issue.Title, issue.State)
|
||||
tp.AddRow(fmt.Sprintf("#%d", issue.Index), issue.Title, string(issue.State))
|
||||
}
|
||||
_ = w.Flush()
|
||||
|
||||
return nil
|
||||
return tp.Render()
|
||||
}
|
||||
|
||||
func runIssueView(cmd *cobra.Command, args []string) error {
|
||||
repo, _ := cmd.Flags().GetString("repo")
|
||||
issueNumber, err := strconv.ParseInt(args[0], 10, 64)
|
||||
issueNumber, err := parseIssueArg(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid issue number: %w", err)
|
||||
}
|
||||
|
|
@ -189,8 +280,10 @@ func runIssueView(cmd *cobra.Command, args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
ios.StartSpinner("Fetching issue...")
|
||||
issue, _, err := client.GetIssue(owner, name, issueNumber)
|
||||
if err != nil {
|
||||
ios.StopSpinner()
|
||||
return fmt.Errorf("failed to get issue: %w", err)
|
||||
}
|
||||
|
||||
|
|
@ -199,8 +292,13 @@ func runIssueView(cmd *cobra.Command, args []string) error {
|
|||
if err != nil {
|
||||
comments = nil
|
||||
}
|
||||
ios.StopSpinner()
|
||||
|
||||
if jsonOutput, _ := cmd.Flags().GetBool("json"); jsonOutput {
|
||||
if web, _ := cmd.Flags().GetBool("web"); web {
|
||||
return ios.OpenInBrowser(issue.HTMLURL)
|
||||
}
|
||||
|
||||
if wantJSON(cmd) {
|
||||
payload := struct {
|
||||
Issue *gitea.Issue `json:"issue"`
|
||||
Comments []*gitea.Comment `json:"comments,omitempty"`
|
||||
|
|
@ -208,26 +306,34 @@ func runIssueView(cmd *cobra.Command, args []string) error {
|
|||
Issue: issue,
|
||||
Comments: comments,
|
||||
}
|
||||
return writeJSON(payload)
|
||||
return outputJSON(cmd, payload)
|
||||
}
|
||||
|
||||
fmt.Printf("Issue #%d\n", issue.Index)
|
||||
fmt.Printf("Title: %s\n", issue.Title)
|
||||
fmt.Printf("State: %s\n", issue.State)
|
||||
fmt.Printf("Author: %s\n", issue.Poster.UserName)
|
||||
fmt.Printf("Created: %s\n", issue.Created.Format("2006-01-02 15:04:05"))
|
||||
fmt.Printf("Updated: %s\n", issue.Updated.Format("2006-01-02 15:04:05"))
|
||||
if err := ios.StartPager(); err != nil {
|
||||
fmt.Fprintf(ios.ErrOut, "warning: failed to start pager: %v\n", err)
|
||||
}
|
||||
defer ios.StopPager()
|
||||
|
||||
cs := ios.ColorScheme()
|
||||
isTTY := ios.IsStdoutTTY()
|
||||
|
||||
fmt.Fprintf(ios.Out, "Issue #%d\n", issue.Index)
|
||||
fmt.Fprintf(ios.Out, "Title: %s\n", cs.Bold(issue.Title))
|
||||
fmt.Fprintf(ios.Out, "State: %s\n", issue.State)
|
||||
fmt.Fprintf(ios.Out, "Author: %s\n", issue.Poster.UserName)
|
||||
fmt.Fprintf(ios.Out, "Created: %s\n", text.FormatDate(issue.Created, isTTY))
|
||||
fmt.Fprintf(ios.Out, "Updated: %s\n", text.FormatDate(issue.Updated, isTTY))
|
||||
if issue.Body != "" {
|
||||
fmt.Printf("\n%s\n", issue.Body)
|
||||
fmt.Fprintf(ios.Out, "\n%s\n", issue.Body)
|
||||
}
|
||||
|
||||
if len(comments) > 0 {
|
||||
fmt.Printf("\nComments (%d):\n", len(comments))
|
||||
fmt.Fprintf(ios.Out, "\nComments (%d):\n", len(comments))
|
||||
for _, comment := range comments {
|
||||
fmt.Printf("\n---\n%s (@%s) - %s\n%s\n",
|
||||
fmt.Fprintf(ios.Out, "\n---\n%s (@%s) - %s\n%s\n",
|
||||
comment.Poster.FullName,
|
||||
comment.Poster.UserName,
|
||||
comment.Created.Format("2006-01-02 15:04:05"),
|
||||
text.FormatDate(comment.Created, isTTY),
|
||||
comment.Body)
|
||||
}
|
||||
}
|
||||
|
|
@ -240,14 +346,28 @@ func runIssueCreate(cmd *cobra.Command, args []string) error {
|
|||
title, _ := cmd.Flags().GetString("title")
|
||||
body, _ := cmd.Flags().GetString("body")
|
||||
labelNames, _ := cmd.Flags().GetStringSlice("label")
|
||||
assignees, _ := cmd.Flags().GetStringSlice("assignee")
|
||||
milestoneName, _ := cmd.Flags().GetString("milestone")
|
||||
|
||||
owner, name, err := parseRepo(repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if title == "" {
|
||||
return fmt.Errorf("title is required")
|
||||
// Interactive mode: prompt for missing fields when TTY
|
||||
if title == "" && ios.IsStdinTTY() {
|
||||
title, err = promptLine("Title: ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if title == "" {
|
||||
return fmt.Errorf("title is required")
|
||||
}
|
||||
if body == "" {
|
||||
body, _ = promptLine("Body (optional): ")
|
||||
}
|
||||
} else if title == "" {
|
||||
return fmt.Errorf("title is required (use -t flag)")
|
||||
}
|
||||
|
||||
cfg, err := config.Load()
|
||||
|
|
@ -268,17 +388,56 @@ func runIssueCreate(cmd *cobra.Command, args []string) error {
|
|||
}
|
||||
}
|
||||
|
||||
// Resolve @me in assignees
|
||||
resolvedAssignees := make([]string, 0, len(assignees))
|
||||
for _, assignee := range assignees {
|
||||
if assignee == "@me" {
|
||||
user, _, userErr := client.GetMyUserInfo()
|
||||
if userErr != nil {
|
||||
return fmt.Errorf("failed to get current user info: %w", userErr)
|
||||
}
|
||||
resolvedAssignees = append(resolvedAssignees, user.UserName)
|
||||
} else {
|
||||
resolvedAssignees = append(resolvedAssignees, assignee)
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve milestone name to ID
|
||||
var milestoneID int64
|
||||
if milestoneName != "" {
|
||||
milestones, _, msErr := client.ListRepoMilestones(owner, name, gitea.ListMilestoneOption{})
|
||||
if msErr != nil {
|
||||
return fmt.Errorf("failed to list milestones: %w", msErr)
|
||||
}
|
||||
found := false
|
||||
for _, ms := range milestones {
|
||||
if ms.Title == milestoneName {
|
||||
milestoneID = ms.ID
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("milestone not found: %s", milestoneName)
|
||||
}
|
||||
}
|
||||
|
||||
ios.StartSpinner("Creating issue...")
|
||||
issue, _, err := client.CreateIssue(owner, name, gitea.CreateIssueOption{
|
||||
Title: title,
|
||||
Body: body,
|
||||
Labels: labelIDs,
|
||||
Title: title,
|
||||
Body: body,
|
||||
Labels: labelIDs,
|
||||
Assignees: resolvedAssignees,
|
||||
Milestone: milestoneID,
|
||||
})
|
||||
ios.StopSpinner()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create issue: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Issue created: #%d\n", issue.Index)
|
||||
fmt.Printf("View at: %s\n", issue.HTMLURL)
|
||||
cs := ios.ColorScheme()
|
||||
fmt.Fprintf(ios.Out, "%s Issue created: #%d\n", cs.SuccessIcon(), issue.Index)
|
||||
fmt.Fprintf(ios.Out, "View at: %s\n", issue.HTMLURL)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -286,7 +445,7 @@ func runIssueCreate(cmd *cobra.Command, args []string) error {
|
|||
func runIssueComment(cmd *cobra.Command, args []string) error {
|
||||
repo, _ := cmd.Flags().GetString("repo")
|
||||
body, _ := cmd.Flags().GetString("body")
|
||||
issueNumber, err := strconv.ParseInt(args[0], 10, 64)
|
||||
issueNumber, err := parseIssueArg(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid issue number: %w", err)
|
||||
}
|
||||
|
|
@ -310,15 +469,18 @@ func runIssueComment(cmd *cobra.Command, args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
ios.StartSpinner("Adding comment...")
|
||||
comment, _, err := client.CreateIssueComment(owner, name, issueNumber, gitea.CreateIssueCommentOption{
|
||||
Body: body,
|
||||
})
|
||||
ios.StopSpinner()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create comment: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Comment added to issue #%d\n", issueNumber)
|
||||
fmt.Printf("View at: %s\n", comment.HTMLURL)
|
||||
cs := ios.ColorScheme()
|
||||
fmt.Fprintf(ios.Out, "%s Comment added to issue #%d\n", cs.SuccessIcon(), issueNumber)
|
||||
fmt.Fprintf(ios.Out, "View at: %s\n", comment.HTMLURL)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -326,7 +488,7 @@ func runIssueComment(cmd *cobra.Command, args []string) error {
|
|||
func runIssueClose(cmd *cobra.Command, args []string) error {
|
||||
repo, _ := cmd.Flags().GetString("repo")
|
||||
commentBody, _ := cmd.Flags().GetString("comment")
|
||||
issueNumber, err := strconv.ParseInt(args[0], 10, 64)
|
||||
issueNumber, err := parseIssueArg(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid issue number: %w", err)
|
||||
}
|
||||
|
|
@ -347,23 +509,28 @@ func runIssueClose(cmd *cobra.Command, args []string) error {
|
|||
}
|
||||
|
||||
if commentBody != "" {
|
||||
ios.StartSpinner("Adding comment...")
|
||||
_, _, err = client.CreateIssueComment(owner, name, issueNumber, gitea.CreateIssueCommentOption{
|
||||
Body: commentBody,
|
||||
})
|
||||
ios.StopSpinner()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create comment: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
ios.StartSpinner("Closing issue...")
|
||||
stateClosed := gitea.StateClosed
|
||||
_, _, err = client.EditIssue(owner, name, issueNumber, gitea.EditIssueOption{
|
||||
State: &stateClosed,
|
||||
})
|
||||
ios.StopSpinner()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to close issue: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Issue #%d closed\n", issueNumber)
|
||||
cs := ios.ColorScheme()
|
||||
fmt.Fprintf(ios.Out, "%s Issue #%d closed\n", cs.SuccessIcon(), issueNumber)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -378,7 +545,7 @@ func runIssueEdit(cmd *cobra.Command, args []string) error {
|
|||
addDeps, _ := cmd.Flags().GetInt64Slice("add-dependency")
|
||||
removeDeps, _ := cmd.Flags().GetInt64Slice("remove-dependency")
|
||||
|
||||
issueNumber, err := strconv.ParseInt(args[0], 10, 64)
|
||||
issueNumber, err := parseIssueArg(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid issue number: %w", err)
|
||||
}
|
||||
|
|
@ -425,9 +592,12 @@ func runIssueEdit(cmd *cobra.Command, args []string) error {
|
|||
}
|
||||
}
|
||||
|
||||
ios.StartSpinner("Updating issue...")
|
||||
|
||||
if title != "" || body != "" || stateStr != "" {
|
||||
_, _, err = client.EditIssue(owner, name, issueNumber, editOpt)
|
||||
if err != nil {
|
||||
ios.StopSpinner()
|
||||
return fmt.Errorf("failed to edit issue: %w", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -435,12 +605,14 @@ func runIssueEdit(cmd *cobra.Command, args []string) error {
|
|||
if len(addLabelNames) > 0 {
|
||||
labelIDs, err := resolveLabelIDs(client, owner, name, addLabelNames)
|
||||
if err != nil {
|
||||
ios.StopSpinner()
|
||||
return err
|
||||
}
|
||||
_, _, err = client.AddIssueLabels(owner, name, issueNumber, gitea.IssueLabelsOption{
|
||||
Labels: labelIDs,
|
||||
})
|
||||
if err != nil {
|
||||
ios.StopSpinner()
|
||||
return fmt.Errorf("failed to add labels: %w", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -448,16 +620,20 @@ func runIssueEdit(cmd *cobra.Command, args []string) error {
|
|||
if len(removeLabelNames) > 0 {
|
||||
labelIDs, err := resolveLabelIDs(client, owner, name, removeLabelNames)
|
||||
if err != nil {
|
||||
ios.StopSpinner()
|
||||
return err
|
||||
}
|
||||
for _, labelID := range labelIDs {
|
||||
_, err = client.DeleteIssueLabel(owner, name, issueNumber, labelID)
|
||||
if err != nil {
|
||||
ios.StopSpinner()
|
||||
return fmt.Errorf("failed to remove label %d: %w", labelID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ios.StopSpinner()
|
||||
|
||||
for _, depNumber := range addDeps {
|
||||
depIssue, _, err := client.GetIssue(owner, name, depNumber)
|
||||
if err != nil {
|
||||
|
|
@ -469,7 +645,7 @@ func runIssueEdit(cmd *cobra.Command, args []string) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("failed to add dependency #%d: %w", depNumber, err)
|
||||
}
|
||||
fmt.Printf("Added dependency: #%d depends on #%d\n", issueNumber, depNumber)
|
||||
fmt.Fprintf(ios.Out, "Added dependency: #%d depends on #%d\n", issueNumber, depNumber)
|
||||
}
|
||||
|
||||
for _, depNumber := range removeDeps {
|
||||
|
|
@ -483,10 +659,96 @@ func runIssueEdit(cmd *cobra.Command, args []string) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("failed to remove dependency #%d: %w", depNumber, err)
|
||||
}
|
||||
fmt.Printf("Removed dependency: #%d no longer depends on #%d\n", issueNumber, depNumber)
|
||||
fmt.Fprintf(ios.Out, "Removed dependency: #%d no longer depends on #%d\n", issueNumber, depNumber)
|
||||
}
|
||||
|
||||
fmt.Printf("Issue #%d updated\n", issueNumber)
|
||||
cs := ios.ColorScheme()
|
||||
fmt.Fprintf(ios.Out, "%s Issue #%d updated\n", cs.SuccessIcon(), issueNumber)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func runIssueDelete(cmd *cobra.Command, args []string) error {
|
||||
repo, _ := cmd.Flags().GetString("repo")
|
||||
yes, _ := cmd.Flags().GetBool("yes")
|
||||
|
||||
issueNumber, err := parseIssueArg(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid issue number: %w", err)
|
||||
}
|
||||
|
||||
owner, name, err := parseRepo(repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client, err := api.NewClientFromConfig(cfg, "", getDetectedHost())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !yes {
|
||||
confirmed, confirmErr := ios.ConfirmAction(fmt.Sprintf("Permanently delete issue #%d from %s/%s?", issueNumber, owner, name))
|
||||
if confirmErr != nil {
|
||||
return confirmErr
|
||||
}
|
||||
if !confirmed {
|
||||
fmt.Fprintln(ios.ErrOut, "Aborted")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
ios.StartSpinner("Deleting issue...")
|
||||
_, err = client.DeleteIssue(owner, name, issueNumber)
|
||||
ios.StopSpinner()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete issue: %w", err)
|
||||
}
|
||||
|
||||
cs := ios.ColorScheme()
|
||||
fmt.Fprintf(ios.Out, "%s Issue #%d deleted from %s/%s\n", cs.SuccessIcon(), issueNumber, owner, name)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runIssueReopen(cmd *cobra.Command, args []string) error {
|
||||
repo, _ := cmd.Flags().GetString("repo")
|
||||
issueNumber, err := parseIssueArg(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid issue number: %w", err)
|
||||
}
|
||||
|
||||
owner, name, err := parseRepo(repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client, err := api.NewClientFromConfig(cfg, "", getDetectedHost())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ios.StartSpinner("Reopening issue...")
|
||||
stateOpen := gitea.StateOpen
|
||||
_, _, err = client.EditIssue(owner, name, issueNumber, gitea.EditIssueOption{
|
||||
State: &stateOpen,
|
||||
})
|
||||
ios.StopSpinner()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to reopen issue: %w", err)
|
||||
}
|
||||
|
||||
cs := ios.ColorScheme()
|
||||
fmt.Fprintf(ios.Out, "%s Issue #%d reopened\n", cs.SuccessIcon(), issueNumber)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue