package cmd import ( "fmt" "os" "strconv" "strings" "text/tabwriter" "code.gitea.io/sdk/gitea" "github.com/spf13/cobra" "codeberg.org/romaintb/fgj/internal/api" "codeberg.org/romaintb/fgj/internal/config" ) var prCmd = &cobra.Command{ Use: "pr", Aliases: []string{"pull-request"}, Short: "Manage pull requests", Long: "Create, view, list, and manage pull requests.", } var prListCmd = &cobra.Command{ Use: "list [flags]", Short: "List pull requests", Long: "List pull requests in a repository.", RunE: runPRList, } var prViewCmd = &cobra.Command{ Use: "view ", Short: "View a pull request", Long: "Display detailed information about a pull request.", Args: cobra.ExactArgs(1), RunE: runPRView, } var prCreateCmd = &cobra.Command{ Use: "create", Short: "Create a pull request", Long: "Create a new pull request.", RunE: runPRCreate, } var prMergeCmd = &cobra.Command{ Use: "merge ", Short: "Merge a pull request", Long: "Merge a pull request.", Args: cobra.ExactArgs(1), RunE: runPRMerge, } func init() { rootCmd.AddCommand(prCmd) prCmd.AddCommand(prListCmd) prCmd.AddCommand(prViewCmd) prCmd.AddCommand(prCreateCmd) prCmd.AddCommand(prMergeCmd) prListCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format") prListCmd.Flags().StringP("state", "s", "open", "Filter by state: open, closed, all") prViewCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format") prCreateCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format") prCreateCmd.Flags().StringP("title", "t", "", "Title for the pull request") prCreateCmd.Flags().StringP("body", "b", "", "Body for the pull request") prCreateCmd.Flags().StringP("head", "H", "", "Head branch") prCreateCmd.Flags().StringP("base", "B", "", "Base branch (default: main)") prMergeCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format") prMergeCmd.Flags().String("merge-method", "merge", "Merge method: merge, rebase, squash") } func runPRList(cmd *cobra.Command, args []string) error { repo, _ := cmd.Flags().GetString("repo") state, _ := cmd.Flags().GetString("state") owner, name, err := parseRepo(repo) if err != nil { return err } cfg, err := config.Load() if err != nil { return err } client, err := api.NewClientFromConfig(cfg, "") if err != nil { return err } var stateType gitea.StateType switch strings.ToLower(state) { case "open": stateType = gitea.StateOpen case "closed": stateType = gitea.StateClosed case "all": stateType = gitea.StateAll default: return fmt.Errorf("invalid state: %s", state) } prs, _, err := client.ListRepoPullRequests(owner, name, gitea.ListPullRequestsOptions{ State: stateType, }) if err != nil { return fmt.Errorf("failed to list pull requests: %w", err) } if len(prs) == 0 { fmt.Printf("No %s pull requests in %s/%s\n", state, owner, name) return nil } w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) fmt.Fprintf(w, "NUMBER\tTITLE\tBRANCH\tSTATE\n") for _, pr := range prs { fmt.Fprintf(w, "#%d\t%s\t%s\t%s\n", pr.Index, pr.Title, pr.Head.Ref, pr.State) } w.Flush() return nil } func runPRView(cmd *cobra.Command, args []string) error { repo, _ := cmd.Flags().GetString("repo") prNumber, err := strconv.ParseInt(args[0], 10, 64) if err != nil { return fmt.Errorf("invalid pull request 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, "") if err != nil { return err } pr, _, err := client.GetPullRequest(owner, name, prNumber) if err != nil { return fmt.Errorf("failed to get pull request: %w", err) } fmt.Printf("Pull Request #%d\n", pr.Index) fmt.Printf("Title: %s\n", pr.Title) fmt.Printf("State: %s\n", pr.State) fmt.Printf("Author: %s\n", pr.Poster.UserName) fmt.Printf("Branch: %s -> %s\n", pr.Head.Ref, pr.Base.Ref) fmt.Printf("Created: %s\n", pr.Created.Format("2006-01-02 15:04:05")) fmt.Printf("Updated: %s\n", pr.Updated.Format("2006-01-02 15:04:05")) if pr.Body != "" { fmt.Printf("\n%s\n", pr.Body) } return nil } func runPRCreate(cmd *cobra.Command, args []string) error { repo, _ := cmd.Flags().GetString("repo") title, _ := cmd.Flags().GetString("title") body, _ := cmd.Flags().GetString("body") head, _ := cmd.Flags().GetString("head") base, _ := cmd.Flags().GetString("base") if base == "" { base = "main" } owner, name, err := parseRepo(repo) if err != nil { return err } if title == "" { return fmt.Errorf("title is required") } if head == "" { return fmt.Errorf("head branch is required") } cfg, err := config.Load() if err != nil { return err } client, err := api.NewClientFromConfig(cfg, "") if err != nil { return err } pr, _, err := client.CreatePullRequest(owner, name, gitea.CreatePullRequestOption{ Title: title, Body: body, Head: head, Base: base, }) if err != nil { return fmt.Errorf("failed to create pull request: %w", err) } fmt.Printf("Pull request created: #%d\n", pr.Index) fmt.Printf("View at: %s\n", pr.HTMLURL) return nil } func runPRMerge(cmd *cobra.Command, args []string) error { repo, _ := cmd.Flags().GetString("repo") mergeMethod, _ := cmd.Flags().GetString("merge-method") prNumber, err := strconv.ParseInt(args[0], 10, 64) if err != nil { return fmt.Errorf("invalid pull request 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, "") if err != nil { return err } var method gitea.MergeStyle switch strings.ToLower(mergeMethod) { case "merge": method = gitea.MergeStyleMerge case "rebase": method = gitea.MergeStyleRebase case "squash": method = gitea.MergeStyleSquash default: return fmt.Errorf("invalid merge method: %s", mergeMethod) } _, _, err = client.MergePullRequest(owner, name, prNumber, gitea.MergePullRequestOption{ Style: method, }) if err != nil { return fmt.Errorf("failed to merge pull request: %w", err) } fmt.Printf("Pull request #%d merged successfully\n", prNumber) return nil } func parseRepo(repo string) (string, string, error) { if repo == "" { return "", "", fmt.Errorf("repository flag is required (use -R owner/name)") } parts := strings.Split(repo, "/") if len(parts) != 2 { return "", "", fmt.Errorf("invalid repository format: %s (expected: owner/name)", repo) } return parts[0], parts[1], nil }