package app import ( "context" "errors" "flag" "fmt" "net/http" "strings" "time" ) func cliPR(args []string) error { if len(args) < 1 { return errors.New("usage: gitocean pr ") } switch args[0] { case "create": return cliPRCreate(args[1:]) case "list": return cliPRList(args[1:]) case "view": return cliPRView(args[1:]) case "diff": return cliPRDiff(args[1:]) case "checkout": return cliPRCheckout(args[1:]) case "comment": return cliPRComment(args[1:]) case "comments": return cliPRComments(args[1:]) case "close": return cliPRAction(args[1:], "close") case "merge": return cliPRAction(args[1:], "merge") default: return fmt.Errorf("unknown pr command %q", args[0]) } } func cliPRCreate(args []string) error { fs := flag.NewFlagSet("pr create", flag.ExitOnError) repoFlag := fs.String("repo", "", "target repo for same-repository PR") from := fs.String("from", "", "source branch or OWNER/REPO:BRANCH") to := fs.String("to", "", "target branch or OWNER/REPO:BRANCH") title := fs.String("title", "", "title") desc := fs.String("description", "", "description") if err := fs.Parse(args); err != nil { return err } if *from == "" || *to == "" || *title == "" { return errors.New("usage: gitocean pr create --from SRC --to DST --title TITLE [--description DESC]") } var targetOwner, targetRepo, targetBranch, sourceOwner, sourceRepo, sourceBranch string if *repoFlag != "" { var err error targetOwner, targetRepo, err = splitOwnerRepo(*repoFlag) if err != nil { return err } sourceOwner, sourceRepo = targetOwner, targetRepo sourceBranch, targetBranch = *from, *to } else { var err error sourceOwner, sourceRepo, sourceBranch, err = splitRepoBranch(*from) if err != nil { return err } targetOwner, targetRepo, targetBranch, err = splitRepoBranch(*to) if err != nil { return err } } cfg, err := loadConfig() if err != nil { return err } body := map[string]string{"source_owner": sourceOwner, "source_repo": sourceRepo, "source_branch": sourceBranch, "target_branch": targetBranch, "title": *title, "description": *desc} var out PullRequest if err := apiRequest(context.Background(), cfg.Server, http.MethodPost, fmt.Sprintf("/api/repos/%s/%s/pulls", targetOwner, targetRepo), cfg.Token, body, &out); err != nil { return err } printPR(out) return nil } func cliPRList(args []string) error { jsonOut := removeBoolFlag(&args, "--json") if len(args) != 1 { return errors.New("usage: gitocean pr list OWNER/REPO") } owner, repo, err := splitOwnerRepo(args[0]) if err != nil { return err } server, token := envServer(), "" if cfg, err := loadConfig(); err == nil { server, token = cfg.Server, cfg.Token } var out []PullRequest if err := apiRequest(context.Background(), server, http.MethodGet, fmt.Sprintf("/api/repos/%s/%s/pulls", owner, repo), token, nil, &out); err != nil { return err } if jsonOut { printJSON(out) return nil } for _, pr := range out { fmt.Printf("#%-4d %-8s %-30s %s/%s:%s -> %s\n", pr.Number, pr.Status, pr.Title, pr.SourceOwner, pr.SourceRepo, pr.SourceBranch, pr.TargetBranch) } return nil } func cliPRView(args []string) error { jsonOut := removeBoolFlag(&args, "--json") if len(args) != 2 { return errors.New("usage: gitocean pr view OWNER/REPO NUMBER") } owner, repo, err := splitOwnerRepo(args[0]) if err != nil { return err } server, token := envServer(), "" if cfg, err := loadConfig(); err == nil { server, token = cfg.Server, cfg.Token } var out PullRequest if err := apiRequest(context.Background(), server, http.MethodGet, fmt.Sprintf("/api/repos/%s/%s/pulls/%s", owner, repo, args[1]), token, nil, &out); err != nil { return err } if jsonOut { printJSON(out) return nil } printPR(out) return nil } func cliPRDiff(args []string) error { if len(args) != 2 { return errors.New("usage: gitocean pr diff OWNER/REPO NUMBER") } owner, repo, err := splitOwnerRepo(args[0]) if err != nil { return err } server, token := envServer(), "" if cfg, err := loadConfig(); err == nil { server, token = cfg.Server, cfg.Token } text, err := apiTextRequest(context.Background(), server, fmt.Sprintf("/api/repos/%s/%s/pulls/%s/diff", owner, repo, args[1]), token) if err != nil { return err } fmt.Print(text) return nil } func cliPRCheckout(args []string) error { if len(args) != 2 { return errors.New("usage: gitocean pr checkout OWNER/REPO NUMBER") } owner, repo, err := splitOwnerRepo(args[0]) if err != nil { return err } server, token := envServer(), "" if cfg, err := loadConfig(); err == nil { server, token = cfg.Server, cfg.Token } var pr PullRequest if err := apiRequest(context.Background(), server, http.MethodGet, fmt.Sprintf("/api/repos/%s/%s/pulls/%s", owner, repo, args[1]), token, nil, &pr); err != nil { return err } branch := "pr-" + args[1] remote := repoCloneURL(server, pr.SourceOwner, pr.SourceRepo) if err := runGit("", "fetch", remote, pr.SourceBranch+":"+branch); err != nil { _ = runGit("", "branch", "-D", branch) if err := runGit("", "fetch", remote, pr.SourceBranch+":"+branch); err != nil { return err } } return runGit("", "checkout", branch) } func cliPRComment(args []string) error { if len(args) < 3 { return errors.New("usage: gitocean pr comment OWNER/REPO NUMBER \"text\"") } owner, repo, err := splitOwnerRepo(args[0]) if err != nil { return err } cfg, err := loadConfig() if err != nil { return err } body := strings.Join(args[2:], " ") var out PRComment if err := apiRequest(context.Background(), cfg.Server, http.MethodPost, fmt.Sprintf("/api/repos/%s/%s/pulls/%s/comments", owner, repo, args[1]), cfg.Token, map[string]string{"body": body}, &out); err != nil { return err } fmt.Printf("Commented as %s\n", out.Author) return nil } func cliPRComments(args []string) error { if len(args) != 2 { return errors.New("usage: gitocean pr comments OWNER/REPO NUMBER") } owner, repo, err := splitOwnerRepo(args[0]) if err != nil { return err } server, token := envServer(), "" if cfg, err := loadConfig(); err == nil { server, token = cfg.Server, cfg.Token } var out []PRComment if err := apiRequest(context.Background(), server, http.MethodGet, fmt.Sprintf("/api/repos/%s/%s/pulls/%s/comments", owner, repo, args[1]), token, nil, &out); err != nil { return err } for _, c := range out { fmt.Printf("%s by %s:\n%s\n\n", c.CreatedAt.Format(time.RFC3339), c.Author, c.Body) } return nil } func cliPRAction(args []string, action string) error { if len(args) != 2 { return fmt.Errorf("usage: gitocean pr %s OWNER/REPO NUMBER", action) } owner, repo, err := splitOwnerRepo(args[0]) if err != nil { return err } cfg, err := loadConfig() if err != nil { return err } var out PullRequest if err := apiRequest(context.Background(), cfg.Server, http.MethodPost, fmt.Sprintf("/api/repos/%s/%s/pulls/%s/%s", owner, repo, args[1], action), cfg.Token, nil, &out); err != nil { return err } printPR(out) return nil }