feat(cli): mgit CLI over the library (flags accepted after positionals)
This commit is contained in:
@@ -0,0 +1,501 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.knownelement.com/ukrrs/mopac-gitea-go/gitea"
|
||||
)
|
||||
|
||||
// --- repo -------------------------------------------------------------------
|
||||
|
||||
func cmdRepo(args []string, stdout, stderr io.Writer) int {
|
||||
if len(args) == 0 {
|
||||
return usageErr(stderr, "repo: want create|list|show")
|
||||
}
|
||||
sub, rest := args[0], args[1:]
|
||||
switch sub {
|
||||
case "create":
|
||||
return repoCreate(rest, stdout, stderr)
|
||||
case "list":
|
||||
return repoList(rest, stdout, stderr)
|
||||
case "show":
|
||||
return repoShow(rest, stdout, stderr)
|
||||
default:
|
||||
return usageErr(stderr, "repo: unknown subcommand %q (create|list|show)", sub)
|
||||
}
|
||||
}
|
||||
|
||||
func repoCreate(args []string, stdout, stderr io.Writer) int {
|
||||
fs, cfgPath, out := newFlags("repo create", stderr)
|
||||
owner := fs.String("owner", "", "create under this organization (default: your account)")
|
||||
name := fs.String("n", "", "repository name (required)")
|
||||
desc := fs.String("desc", "", "description")
|
||||
priv := fs.Bool("private", false, "make the repository private")
|
||||
autoInit := fs.Bool("auto-init", false, "initialize with a README commit")
|
||||
defBranch := fs.String("default-branch", "", "default branch (with --auto-init)")
|
||||
pos, err := parseArgs(fs, args)
|
||||
if err != nil {
|
||||
return usageErr(stderr, "repo create: %v", err)
|
||||
}
|
||||
if len(pos) > 0 {
|
||||
return usageErr(stderr, "repo create: unexpected argument %q", pos[0])
|
||||
}
|
||||
if *name == "" {
|
||||
return usageErr(stderr, "repo create: -n NAME is required")
|
||||
}
|
||||
c, err := client(*cfgPath)
|
||||
if err != nil {
|
||||
return usageErr(stderr, "%v", err)
|
||||
}
|
||||
repo, err := c.CreateRepo(context.Background(), gitea.RepoParams{
|
||||
Owner: *owner,
|
||||
Name: *name,
|
||||
Description: *desc,
|
||||
Private: *priv,
|
||||
AutoInit: *autoInit,
|
||||
DefaultBranch: *defBranch,
|
||||
})
|
||||
if err != nil {
|
||||
return apiErr(stderr, err)
|
||||
}
|
||||
if *out == "json" {
|
||||
emitJSON(stdout, repo)
|
||||
return 0
|
||||
}
|
||||
vis := "public"
|
||||
if repo.Private {
|
||||
vis = "private"
|
||||
}
|
||||
fmt.Fprintf(stdout, "created repo %s (%s)\n%s\n", repo.FullName, vis, repo.HTMLURL)
|
||||
return 0
|
||||
}
|
||||
|
||||
func repoList(args []string, stdout, stderr io.Writer) int {
|
||||
fs, cfgPath, out := newFlags("repo list", stderr)
|
||||
owner := fs.String("owner", "", "list this account's repos (default: yours)")
|
||||
pos, err := parseArgs(fs, args)
|
||||
if err != nil {
|
||||
return usageErr(stderr, "repo list: %v", err)
|
||||
}
|
||||
if len(pos) > 0 {
|
||||
return usageErr(stderr, "repo list: unexpected argument %q", pos[0])
|
||||
}
|
||||
c, err := client(*cfgPath)
|
||||
if err != nil {
|
||||
return usageErr(stderr, "%v", err)
|
||||
}
|
||||
repos, err := c.ListRepos(context.Background(), *owner)
|
||||
if err != nil {
|
||||
return apiErr(stderr, err)
|
||||
}
|
||||
if *out == "json" {
|
||||
emitJSON(stdout, repos)
|
||||
return 0
|
||||
}
|
||||
for _, r := range repos {
|
||||
vis := "public"
|
||||
if r.Private {
|
||||
vis = "private"
|
||||
}
|
||||
fmt.Fprintf(stdout, "%-40s %-12s %-7s %s\n", r.FullName, r.DefaultBranch, vis, r.Description)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func repoShow(args []string, stdout, stderr io.Writer) int {
|
||||
fs, cfgPath, out := newFlags("repo show", stderr)
|
||||
pos, err := parseArgs(fs, args)
|
||||
if err != nil {
|
||||
return usageErr(stderr, "repo show: %v", err)
|
||||
}
|
||||
if len(pos) != 1 {
|
||||
return usageErr(stderr, "repo show: want OWNER/REPO")
|
||||
}
|
||||
owner, name, err := repoArg(pos[0])
|
||||
if err != nil {
|
||||
return usageErr(stderr, "repo show: %v", err)
|
||||
}
|
||||
c, err := client(*cfgPath)
|
||||
if err != nil {
|
||||
return usageErr(stderr, "%v", err)
|
||||
}
|
||||
repo, err := c.GetRepo(context.Background(), owner, name)
|
||||
if err != nil {
|
||||
return apiErr(stderr, err)
|
||||
}
|
||||
if *out == "json" {
|
||||
emitJSON(stdout, repo)
|
||||
return 0
|
||||
}
|
||||
vis := "public"
|
||||
if repo.Private {
|
||||
vis = "private"
|
||||
}
|
||||
fmt.Fprintf(stdout, "%s (%s)\n", repo.FullName, vis)
|
||||
if repo.Description != "" {
|
||||
fmt.Fprintf(stdout, "%s\n", repo.Description)
|
||||
}
|
||||
fmt.Fprintf(stdout, "default branch: %s\n", repo.DefaultBranch)
|
||||
fmt.Fprintf(stdout, "%s\n", repo.HTMLURL)
|
||||
return 0
|
||||
}
|
||||
|
||||
// --- branch -------------------------------------------------------------------
|
||||
|
||||
func cmdBranch(args []string, stdout, stderr io.Writer) int {
|
||||
if len(args) == 0 {
|
||||
return usageErr(stderr, "branch: want list")
|
||||
}
|
||||
if args[0] != "list" {
|
||||
return usageErr(stderr, "branch: unknown subcommand %q (list)", args[0])
|
||||
}
|
||||
fs, cfgPath, out := newFlags("branch list", stderr)
|
||||
pos, err := parseArgs(fs, args[1:])
|
||||
if err != nil {
|
||||
return usageErr(stderr, "branch list: %v", err)
|
||||
}
|
||||
if len(pos) != 1 {
|
||||
return usageErr(stderr, "branch list: want OWNER/REPO")
|
||||
}
|
||||
owner, name, err := repoArg(pos[0])
|
||||
if err != nil {
|
||||
return usageErr(stderr, "branch list: %v", err)
|
||||
}
|
||||
c, err := client(*cfgPath)
|
||||
if err != nil {
|
||||
return usageErr(stderr, "%v", err)
|
||||
}
|
||||
branches, err := c.ListBranches(context.Background(), owner, name)
|
||||
if err != nil {
|
||||
return apiErr(stderr, err)
|
||||
}
|
||||
if *out == "json" {
|
||||
emitJSON(stdout, branches)
|
||||
return 0
|
||||
}
|
||||
for _, b := range branches {
|
||||
fmt.Fprintf(stdout, "%-30s %s\n", b.Name, shortSha(b.Commit.ID))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// --- status -------------------------------------------------------------------
|
||||
|
||||
var statusStates = map[string]bool{
|
||||
gitea.StatusPending: true,
|
||||
gitea.StatusSuccess: true,
|
||||
gitea.StatusError: true,
|
||||
gitea.StatusFailure: true,
|
||||
}
|
||||
|
||||
func cmdStatus(args []string, stdout, stderr io.Writer) int {
|
||||
if len(args) == 0 {
|
||||
return usageErr(stderr, "status: want create|list")
|
||||
}
|
||||
sub, rest := args[0], args[1:]
|
||||
switch sub {
|
||||
case "create":
|
||||
return statusCreate(rest, stdout, stderr)
|
||||
case "list":
|
||||
return statusList(rest, stdout, stderr)
|
||||
default:
|
||||
return usageErr(stderr, "status: unknown subcommand %q (create|list)", sub)
|
||||
}
|
||||
}
|
||||
|
||||
func statusCreate(args []string, stdout, stderr io.Writer) int {
|
||||
fs, cfgPath, out := newFlags("status create", stderr)
|
||||
state := fs.String("state", "", "success|pending|error|failure (required)")
|
||||
ctxName := fs.String("context", "", "check name (e.g. ci/lint)")
|
||||
desc := fs.String("description", "", "free text")
|
||||
target := fs.String("target-url", "", "link to the check run")
|
||||
pos, err := parseArgs(fs, args)
|
||||
if err != nil {
|
||||
return usageErr(stderr, "status create: %v", err)
|
||||
}
|
||||
if len(pos) != 2 {
|
||||
return usageErr(stderr, "status create: want OWNER/REPO REF")
|
||||
}
|
||||
owner, name, err := repoArg(pos[0])
|
||||
if err != nil {
|
||||
return usageErr(stderr, "status create: %v", err)
|
||||
}
|
||||
ref := pos[1]
|
||||
if !statusStates[*state] {
|
||||
return usageErr(stderr, "status create: --state must be success|pending|error|failure")
|
||||
}
|
||||
c, err := client(*cfgPath)
|
||||
if err != nil {
|
||||
return usageErr(stderr, "%v", err)
|
||||
}
|
||||
st, err := c.CreateCommitStatus(context.Background(), owner, name, ref, gitea.CommitStatusParams{
|
||||
State: *state,
|
||||
Context: *ctxName,
|
||||
Description: *desc,
|
||||
TargetURL: *target,
|
||||
})
|
||||
if err != nil {
|
||||
return apiErr(stderr, err)
|
||||
}
|
||||
if *out == "json" {
|
||||
emitJSON(stdout, st)
|
||||
return 0
|
||||
}
|
||||
fmt.Fprintf(stdout, "reported %s on %s/%s@%s (context %s)\n",
|
||||
st.State, owner, name, shortSha(ref), st.Context)
|
||||
return 0
|
||||
}
|
||||
|
||||
func statusList(args []string, stdout, stderr io.Writer) int {
|
||||
fs, cfgPath, out := newFlags("status list", stderr)
|
||||
pos, err := parseArgs(fs, args)
|
||||
if err != nil {
|
||||
return usageErr(stderr, "status list: %v", err)
|
||||
}
|
||||
if len(pos) != 2 {
|
||||
return usageErr(stderr, "status list: want OWNER/REPO REF")
|
||||
}
|
||||
owner, name, err := repoArg(pos[0])
|
||||
if err != nil {
|
||||
return usageErr(stderr, "status list: %v", err)
|
||||
}
|
||||
c, err := client(*cfgPath)
|
||||
if err != nil {
|
||||
return usageErr(stderr, "%v", err)
|
||||
}
|
||||
comb, err := c.GetCombinedStatus(context.Background(), owner, name, pos[1])
|
||||
if err != nil {
|
||||
return apiErr(stderr, err)
|
||||
}
|
||||
if *out == "json" {
|
||||
emitJSON(stdout, comb)
|
||||
return 0
|
||||
}
|
||||
fmt.Fprintf(stdout, "combined %s (%d checks) on %s\n", comb.State, comb.TotalCount, shortSha(comb.SHA))
|
||||
for _, s := range comb.Statuses {
|
||||
fmt.Fprintf(stdout, "%-8s %-20s %s\n", s.State, s.Context, s.Description)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// --- pull requests --------------------------------------------------------------
|
||||
|
||||
var mergeStyles = map[string]bool{
|
||||
gitea.MergeMerge: true,
|
||||
gitea.MergeRebase: true,
|
||||
gitea.MergeRebaseMerge: true,
|
||||
gitea.MergeSquash: true,
|
||||
gitea.MergeFastForward: true,
|
||||
}
|
||||
|
||||
func cmdPR(args []string, stdout, stderr io.Writer) int {
|
||||
if len(args) == 0 {
|
||||
return usageErr(stderr, "pr: want create|list|show|merge")
|
||||
}
|
||||
sub, rest := args[0], args[1:]
|
||||
switch sub {
|
||||
case "create":
|
||||
return prCreate(rest, stdout, stderr)
|
||||
case "list":
|
||||
return prList(rest, stdout, stderr)
|
||||
case "show":
|
||||
return prShow(rest, stdout, stderr)
|
||||
case "merge":
|
||||
return prMerge(rest, stdout, stderr)
|
||||
default:
|
||||
return usageErr(stderr, "pr: unknown subcommand %q (create|list|show|merge)", sub)
|
||||
}
|
||||
}
|
||||
|
||||
// readBody loads a PR body from a file, or stdin when path is "-".
|
||||
func readBody(path string) (string, error) {
|
||||
if path == "" {
|
||||
return "", nil
|
||||
}
|
||||
var b []byte
|
||||
var err error
|
||||
if path == "-" {
|
||||
b, err = io.ReadAll(os.Stdin)
|
||||
} else {
|
||||
b, err = os.ReadFile(path)
|
||||
}
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("cannot read body: %w", err)
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
func prCreate(args []string, stdout, stderr io.Writer) int {
|
||||
fs, cfgPath, out := newFlags("pr create", stderr)
|
||||
title := fs.String("title", "", "PR title (required)")
|
||||
body := fs.String("body", "", "PR body from FILE or - for stdin")
|
||||
base := fs.String("base", "", "target branch (required)")
|
||||
head := fs.String("head", "", "source branch, owner:branch for forks (required)")
|
||||
pos, err := parseArgs(fs, args)
|
||||
if err != nil {
|
||||
return usageErr(stderr, "pr create: %v", err)
|
||||
}
|
||||
if len(pos) != 1 {
|
||||
return usageErr(stderr, "pr create: want OWNER/REPO")
|
||||
}
|
||||
owner, name, err := repoArg(pos[0])
|
||||
if err != nil {
|
||||
return usageErr(stderr, "pr create: %v", err)
|
||||
}
|
||||
if *title == "" {
|
||||
return usageErr(stderr, "pr create: --title is required")
|
||||
}
|
||||
if *base == "" {
|
||||
return usageErr(stderr, "pr create: --base is required")
|
||||
}
|
||||
if *head == "" {
|
||||
return usageErr(stderr, "pr create: --head is required")
|
||||
}
|
||||
bodyText, err := readBody(*body)
|
||||
if err != nil {
|
||||
return usageErr(stderr, "pr create: %v", err)
|
||||
}
|
||||
c, err := client(*cfgPath)
|
||||
if err != nil {
|
||||
return usageErr(stderr, "%v", err)
|
||||
}
|
||||
pr, err := c.CreatePullRequest(context.Background(), owner, name, gitea.PRParams{
|
||||
Title: *title, Body: bodyText, Base: *base, Head: *head,
|
||||
})
|
||||
if err != nil {
|
||||
return apiErr(stderr, err)
|
||||
}
|
||||
if *out == "json" {
|
||||
emitJSON(stdout, pr)
|
||||
return 0
|
||||
}
|
||||
fmt.Fprintf(stdout, "created pull request #%d: %s\n%s\n", pr.Number, pr.Title, pr.HTMLURL)
|
||||
return 0
|
||||
}
|
||||
|
||||
func prList(args []string, stdout, stderr io.Writer) int {
|
||||
fs, cfgPath, out := newFlags("pr list", stderr)
|
||||
state := fs.String("state", gitea.PRStateOpen, "open|closed|all")
|
||||
pos, err := parseArgs(fs, args)
|
||||
if err != nil {
|
||||
return usageErr(stderr, "pr list: %v", err)
|
||||
}
|
||||
if len(pos) != 1 {
|
||||
return usageErr(stderr, "pr list: want OWNER/REPO")
|
||||
}
|
||||
owner, name, err := repoArg(pos[0])
|
||||
if err != nil {
|
||||
return usageErr(stderr, "pr list: %v", err)
|
||||
}
|
||||
switch *state {
|
||||
case gitea.PRStateOpen, gitea.PRStateClosed, gitea.PRStateAll:
|
||||
default:
|
||||
return usageErr(stderr, "pr list: --state must be open|closed|all")
|
||||
}
|
||||
c, err := client(*cfgPath)
|
||||
if err != nil {
|
||||
return usageErr(stderr, "%v", err)
|
||||
}
|
||||
prs, err := c.ListPullRequests(context.Background(), owner, name, *state)
|
||||
if err != nil {
|
||||
return apiErr(stderr, err)
|
||||
}
|
||||
if *out == "json" {
|
||||
emitJSON(stdout, prs)
|
||||
return 0
|
||||
}
|
||||
for _, pr := range prs {
|
||||
fmt.Fprintf(stdout, "#%-4d %-7s %s (%s..%s)\n", pr.Number, pr.State, pr.Title,
|
||||
prBranchRef(pr.Base), prBranchRef(pr.Head))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func prBranchRef(b *gitea.PRBranch) string {
|
||||
if b == nil {
|
||||
return "?"
|
||||
}
|
||||
return b.Ref
|
||||
}
|
||||
|
||||
func prShow(args []string, stdout, stderr io.Writer) int {
|
||||
fs, cfgPath, out := newFlags("pr show", stderr)
|
||||
pos, err := parseArgs(fs, args)
|
||||
if err != nil {
|
||||
return usageErr(stderr, "pr show: %v", err)
|
||||
}
|
||||
if len(pos) != 2 {
|
||||
return usageErr(stderr, "pr show: want OWNER/REPO NUMBER")
|
||||
}
|
||||
owner, name, err := repoArg(pos[0])
|
||||
if err != nil {
|
||||
return usageErr(stderr, "pr show: %v", err)
|
||||
}
|
||||
num, err := strconv.ParseInt(pos[1], 10, 64)
|
||||
if err != nil {
|
||||
return usageErr(stderr, "pr show: NUMBER must be numeric")
|
||||
}
|
||||
c, err := client(*cfgPath)
|
||||
if err != nil {
|
||||
return usageErr(stderr, "%v", err)
|
||||
}
|
||||
pr, err := c.GetPullRequest(context.Background(), owner, name, num)
|
||||
if err != nil {
|
||||
return apiErr(stderr, err)
|
||||
}
|
||||
if *out == "json" {
|
||||
emitJSON(stdout, pr)
|
||||
return 0
|
||||
}
|
||||
state := pr.State
|
||||
if pr.Merged {
|
||||
state = "merged"
|
||||
}
|
||||
fmt.Fprintf(stdout, "#%d %s [%s]\n", pr.Number, pr.Title, state)
|
||||
fmt.Fprintf(stdout, "%s..%s\n", prBranchRef(pr.Base), prBranchRef(pr.Head))
|
||||
fmt.Fprintf(stdout, "mergeable: %v\n", pr.Mergeable)
|
||||
if pr.Body != "" {
|
||||
for _, line := range strings.Split(strings.TrimRight(pr.Body, "\n"), "\n") {
|
||||
fmt.Fprintf(stdout, " %s\n", line)
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(stdout, "%s\n", pr.HTMLURL)
|
||||
return 0
|
||||
}
|
||||
|
||||
func prMerge(args []string, stdout, stderr io.Writer) int {
|
||||
fs, cfgPath, _ := newFlags("pr merge", stderr)
|
||||
style := fs.String("do", gitea.MergeMerge, "merge|rebase|rebase-merge|squash|fast-forward")
|
||||
pos, err := parseArgs(fs, args)
|
||||
if err != nil {
|
||||
return usageErr(stderr, "pr merge: %v", err)
|
||||
}
|
||||
if len(pos) != 2 {
|
||||
return usageErr(stderr, "pr merge: want OWNER/REPO NUMBER")
|
||||
}
|
||||
owner, name, err := repoArg(pos[0])
|
||||
if err != nil {
|
||||
return usageErr(stderr, "pr merge: %v", err)
|
||||
}
|
||||
num, err := strconv.ParseInt(pos[1], 10, 64)
|
||||
if err != nil {
|
||||
return usageErr(stderr, "pr merge: NUMBER must be numeric")
|
||||
}
|
||||
if !mergeStyles[*style] {
|
||||
return usageErr(stderr, "pr merge: --do must be merge|rebase|rebase-merge|squash|fast-forward")
|
||||
}
|
||||
c, err := client(*cfgPath)
|
||||
if err != nil {
|
||||
return usageErr(stderr, "%v", err)
|
||||
}
|
||||
if err := c.MergePullRequest(context.Background(), owner, name, num, gitea.MergeParams{Do: *style}); err != nil {
|
||||
return apiErr(stderr, err)
|
||||
}
|
||||
fmt.Fprintf(stdout, "merged pull request #%d (%s)\n", num, *style)
|
||||
return 0
|
||||
}
|
||||
Reference in New Issue
Block a user