package cmd import ( "fmt" "strings" "code.gitea.io/sdk/gitea" "forgejo.zerova.net/public/fgj-sid/internal/api" "forgejo.zerova.net/public/fgj-sid/internal/config" "github.com/spf13/cobra" ) var prReviewCommentsCmd = &cobra.Command{ Use: "review-comments ", Aliases: []string{"rc"}, Short: "List review comments on a pull request", Long: "List all review comments (inline code comments) across every review on a PR.", Example: ` # List review comments on PR #42 fgj pr review-comments 42 # As JSON fgj pr review-comments 42 --json`, Args: cobra.ExactArgs(1), RunE: runPRReviewComments, } var prResolveCmd = &cobra.Command{ Use: "resolve ", Short: "Resolve a PR review comment", Long: `Mark a pull request review comment as resolved. Comment IDs are shown in the output of 'fgj pr review-comments'. Requires Forgejo 8.x+ / Gitea 1.22+.`, Args: cobra.ExactArgs(1), RunE: runPRResolveComment(true), } var prUnresolveCmd = &cobra.Command{ Use: "unresolve ", Short: "Unresolve a PR review comment", Long: "Reopen a previously-resolved pull request review comment.", Args: cobra.ExactArgs(1), RunE: runPRResolveComment(false), } func init() { prCmd.AddCommand(prReviewCommentsCmd) prCmd.AddCommand(prResolveCmd) prCmd.AddCommand(prUnresolveCmd) addRepoFlags(prReviewCommentsCmd) addJSONFlags(prReviewCommentsCmd, "Output as JSON") addRepoFlags(prResolveCmd) addRepoFlags(prUnresolveCmd) } func runPRReviewComments(cmd *cobra.Command, args []string) error { prNumber, err := parseIssueArg(args[0]) if err != nil { return fmt.Errorf("invalid pull request number: %w", err) } client, owner, name, err := newPRCommentClient(cmd) if err != nil { return err } reviews, _, err := client.ListPullReviews(owner, name, prNumber, gitea.ListPullReviewsOptions{}) if err != nil { return fmt.Errorf("failed to list reviews: %w", err) } var all []*gitea.PullReviewComment for _, r := range reviews { comments, _, err := client.ListPullReviewComments(owner, name, prNumber, r.ID) if err != nil { return fmt.Errorf("failed to list comments for review %d: %w", r.ID, err) } all = append(all, comments...) } if wantJSON(cmd) { return outputJSON(cmd, all) } if len(all) == 0 { fmt.Fprintln(ios.Out, "No review comments on this PR.") return nil } tp := ios.NewTablePrinter() tp.AddHeader("ID", "REVIEWER", "PATH", "LINE", "RESOLVED", "BODY") for _, c := range all { reviewer := "" if c.Reviewer != nil { reviewer = c.Reviewer.UserName } resolved := "" if c.Resolver != nil { resolved = "yes" } // Collapse multi-line bodies for table view. body := strings.ReplaceAll(c.Body, "\n", " ") if len(body) > 80 { body = body[:77] + "..." } tp.AddRow( fmt.Sprintf("%d", c.ID), reviewer, c.Path, fmt.Sprintf("%d", c.LineNum), resolved, body, ) } return tp.Render() } // runPRResolveComment returns a RunE closure that either resolves or unresolves // a review comment, depending on the `resolve` flag. The underlying SDK // (v0.22.1) doesn't expose these endpoints yet, so we call them raw. func runPRResolveComment(resolve bool) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error { id, err := parseIssueArg(args[0]) if err != nil { return fmt.Errorf("invalid comment id: %w", err) } client, owner, name, err := newPRCommentClient(cmd) if err != nil { return err } path := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/comments/%d", owner, name, id) action := "unresolve" if resolve { action = "resolve" } endpoint := path + "/" + action if err := client.PostJSON(endpoint, map[string]any{}, nil); err != nil { return fmt.Errorf("failed to %s comment %d: %w", action, id, err) } cs := ios.ColorScheme() verb := "Resolved" if !resolve { verb = "Unresolved" } fmt.Fprintf(ios.Out, "%s %s comment %d\n", cs.SuccessIcon(), verb, id) return nil } } func newPRCommentClient(cmd *cobra.Command) (*api.Client, string, string, error) { repoFlag, _ := cmd.Flags().GetString("repo") owner, name, err := parseRepo(repoFlag) if err != nil { return nil, "", "", err } cfg, err := config.Load() if err != nil { return nil, "", "", err } client, err := api.NewClientFromConfig(cfg, "", getDetectedHost(), getCwd()) if err != nil { return nil, "", "", err } return client, owner, name, nil }