404 lines
11 KiB
Go
404 lines
11 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
)
|
|
|
|
func cliRepo(args []string) error {
|
|
if len(args) < 1 {
|
|
return errors.New("usage: gitocean repo <create|publish|view|branches|tags|set|archive|unarchive|delete|search|fork|collaborators|collaborator>")
|
|
}
|
|
switch args[0] {
|
|
case "create":
|
|
return cliRepoCreate(args[1:])
|
|
case "publish":
|
|
return cliRepoPublish(args[1:])
|
|
case "view":
|
|
return cliRepoView(args[1:])
|
|
case "branches":
|
|
return cliRepoRefs(args[1:], "branches")
|
|
case "tags":
|
|
return cliRepoRefs(args[1:], "tags")
|
|
case "set":
|
|
return cliRepoSet(args[1:])
|
|
case "archive":
|
|
return cliRepoArchive(args[1:], true)
|
|
case "unarchive":
|
|
return cliRepoArchive(args[1:], false)
|
|
case "delete":
|
|
return cliRepoDelete(args[1:])
|
|
case "search":
|
|
return cliRepoSearch(args[1:])
|
|
case "fork":
|
|
return cliRepoFork(args[1:])
|
|
case "collaborators":
|
|
return cliRepoCollaborators(args[1:])
|
|
case "collaborator":
|
|
return cliRepoCollaborator(args[1:])
|
|
default:
|
|
return fmt.Errorf("unknown repo command %q", args[0])
|
|
}
|
|
}
|
|
|
|
func cliRepoCreate(args []string) error {
|
|
name, visibility, description, jsonOut, err := parseRepoCreateArgs(args)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cfg, err := loadConfig()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var out Repository
|
|
if err := apiRequest(context.Background(), cfg.Server, http.MethodPost, "/api/repos", cfg.Token, map[string]string{"name": name, "visibility": visibility, "description": description}, &out); err != nil {
|
|
return err
|
|
}
|
|
if jsonOut {
|
|
printJSON(out)
|
|
return nil
|
|
}
|
|
printRepoCreated(cfg.Server, out)
|
|
return nil
|
|
}
|
|
|
|
func cliRepoPublish(args []string) error {
|
|
name, visibility, description, remoteName, branch, err := parseRepoPublishArgs(args)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := runGit("", "rev-parse", "--is-inside-work-tree"); err != nil {
|
|
return errors.New("repo publish must be run inside a Git repository")
|
|
}
|
|
cfg, err := loadConfig()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var out Repository
|
|
if err := apiRequest(context.Background(), cfg.Server, http.MethodPost, "/api/repos", cfg.Token, map[string]string{"name": name, "visibility": visibility, "description": description}, &out); err != nil {
|
|
return err
|
|
}
|
|
remote := repoCloneURL(cfg.Server, out.Owner, out.Name)
|
|
if err := runGit("", "remote", "get-url", remoteName); err != nil {
|
|
if err := runGit("", "remote", "add", remoteName, remote); err != nil {
|
|
return err
|
|
}
|
|
} else if err := runGit("", "remote", "set-url", remoteName, remote); err != nil {
|
|
return err
|
|
}
|
|
_ = runGit("", "branch", "-M", branch)
|
|
cmd := exec.Command("git", "push", "-u", remoteName, branch)
|
|
cmd.Stdout, cmd.Stderr, cmd.Stdin = os.Stdout, os.Stderr, os.Stdin
|
|
if err := cmd.Run(); err != nil {
|
|
return err
|
|
}
|
|
if branch != out.DefaultBranch {
|
|
body := map[string]any{"default_branch": branch}
|
|
_ = apiRequest(context.Background(), cfg.Server, http.MethodPatch, fmt.Sprintf("/api/repos/%s/%s", out.Owner, out.Name), cfg.Token, body, &out)
|
|
}
|
|
fmt.Printf("Published %s/%s to %s\n", out.Owner, out.Name, remote)
|
|
return nil
|
|
}
|
|
|
|
func cliRepoView(args []string) error {
|
|
jsonOut := removeBoolFlag(&args, "--json")
|
|
if len(args) != 1 {
|
|
return errors.New("usage: gitocean repo view 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 Repository
|
|
if err := apiRequest(context.Background(), server, http.MethodGet, fmt.Sprintf("/api/repos/%s/%s", owner, repo), token, nil, &out); err != nil {
|
|
return err
|
|
}
|
|
if jsonOut {
|
|
printJSON(out)
|
|
return nil
|
|
}
|
|
printRepoView(server, out)
|
|
return nil
|
|
}
|
|
|
|
func cliRepoRefs(args []string, kind string) error {
|
|
jsonOut := removeBoolFlag(&args, "--json")
|
|
if len(args) != 1 {
|
|
return fmt.Errorf("usage: gitocean repo %s OWNER/REPO", kind)
|
|
}
|
|
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 []RefInfo
|
|
if err := apiRequest(context.Background(), server, http.MethodGet, fmt.Sprintf("/api/repos/%s/%s/%s", owner, repo, kind), token, nil, &out); err != nil {
|
|
return err
|
|
}
|
|
if jsonOut {
|
|
printJSON(out)
|
|
return nil
|
|
}
|
|
printRefs(kind, out)
|
|
return nil
|
|
}
|
|
|
|
func cliRepoSet(args []string) error {
|
|
if len(args) < 1 {
|
|
return errors.New("usage: gitocean repo set OWNER/REPO [--description TEXT] [--visibility public|private] [--default-branch main]")
|
|
}
|
|
repoRef := args[0]
|
|
body := map[string]any{}
|
|
for i := 1; i < len(args); i++ {
|
|
switch args[i] {
|
|
case "--description":
|
|
if i+1 >= len(args) {
|
|
return errors.New("--description requires a value")
|
|
}
|
|
i++
|
|
body["description"] = args[i]
|
|
case "--visibility":
|
|
if i+1 >= len(args) {
|
|
return errors.New("--visibility requires a value")
|
|
}
|
|
i++
|
|
body["visibility"] = args[i]
|
|
case "--default-branch":
|
|
if i+1 >= len(args) {
|
|
return errors.New("--default-branch requires a value")
|
|
}
|
|
i++
|
|
body["default_branch"] = args[i]
|
|
default:
|
|
return fmt.Errorf("unknown flag %s", args[i])
|
|
}
|
|
}
|
|
if len(body) == 0 {
|
|
return errors.New("no settings provided")
|
|
}
|
|
return cliRepoPatch(repoRef, body)
|
|
}
|
|
|
|
func cliRepoArchive(args []string, archived bool) error {
|
|
if len(args) != 1 {
|
|
return errors.New("usage: gitocean repo archive OWNER/REPO")
|
|
}
|
|
return cliRepoPatch(args[0], map[string]any{"archived": archived})
|
|
}
|
|
|
|
func cliRepoPatch(repoRef string, body map[string]any) error {
|
|
owner, repo, err := splitOwnerRepo(repoRef)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cfg, err := loadConfig()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var out Repository
|
|
if err := apiRequest(context.Background(), cfg.Server, http.MethodPatch, fmt.Sprintf("/api/repos/%s/%s", owner, repo), cfg.Token, body, &out); err != nil {
|
|
return err
|
|
}
|
|
printRepoView(cfg.Server, out)
|
|
return nil
|
|
}
|
|
|
|
func cliRepoDelete(args []string) error {
|
|
var repoRef string
|
|
force := false
|
|
for _, arg := range args {
|
|
switch arg {
|
|
case "--force", "-force":
|
|
force = true
|
|
default:
|
|
if strings.HasPrefix(arg, "-") {
|
|
return fmt.Errorf("unknown flag %s", arg)
|
|
}
|
|
if repoRef != "" {
|
|
return errors.New("usage: gitocean repo delete OWNER/REPO [--force]")
|
|
}
|
|
repoRef = arg
|
|
}
|
|
}
|
|
if repoRef == "" {
|
|
return errors.New("usage: gitocean repo delete OWNER/REPO [--force]")
|
|
}
|
|
owner, repo, err := splitOwnerRepo(repoRef)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cfg, err := loadConfig()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
path := fmt.Sprintf("/api/repos/%s/%s?force=%v", owner, repo, force)
|
|
if err := apiRequest(context.Background(), cfg.Server, http.MethodDelete, path, cfg.Token, nil, nil); err != nil {
|
|
return err
|
|
}
|
|
fmt.Printf("Deleted %s/%s\n", owner, repo)
|
|
return nil
|
|
}
|
|
|
|
func cliRepoSearch(args []string) error {
|
|
jsonOut := removeBoolFlag(&args, "--json")
|
|
var query string
|
|
scope := "all"
|
|
for _, arg := range args {
|
|
switch arg {
|
|
case "--mine", "-mine":
|
|
scope = "mine"
|
|
case "--all", "-all":
|
|
scope = "all"
|
|
default:
|
|
if strings.HasPrefix(arg, "-") {
|
|
return fmt.Errorf("unknown flag %s", arg)
|
|
}
|
|
if query != "" {
|
|
return errors.New("usage: gitocean repo search QUERY [--all|--mine]")
|
|
}
|
|
query = arg
|
|
}
|
|
}
|
|
if query == "" {
|
|
return errors.New("usage: gitocean repo search QUERY [--all|--mine]")
|
|
}
|
|
server := envServer()
|
|
token := ""
|
|
if cfg, err := loadConfig(); err == nil {
|
|
server, token = cfg.Server, cfg.Token
|
|
}
|
|
path := "/api/repos/search?q=" + url.QueryEscape(query) + "&scope=" + scope
|
|
var out []Repository
|
|
if err := apiRequest(context.Background(), server, http.MethodGet, path, token, nil, &out); err != nil {
|
|
return err
|
|
}
|
|
if jsonOut {
|
|
printJSON(out)
|
|
return nil
|
|
}
|
|
printRepoTable(out)
|
|
return nil
|
|
}
|
|
|
|
func cliRepoFork(args []string) error {
|
|
var repoRef string
|
|
newName := ""
|
|
for i := 0; i < len(args); i++ {
|
|
arg := args[i]
|
|
switch {
|
|
case arg == "--name" || arg == "-name":
|
|
if i+1 >= len(args) {
|
|
return errors.New("--name requires a value")
|
|
}
|
|
i++
|
|
newName = args[i]
|
|
case strings.HasPrefix(arg, "--name="):
|
|
newName = strings.TrimPrefix(arg, "--name=")
|
|
case strings.HasPrefix(arg, "-name="):
|
|
newName = strings.TrimPrefix(arg, "-name=")
|
|
default:
|
|
if strings.HasPrefix(arg, "-") {
|
|
return fmt.Errorf("unknown flag %s", arg)
|
|
}
|
|
if repoRef != "" {
|
|
return errors.New("usage: gitocean repo fork OWNER/REPO [--name NEW_NAME]")
|
|
}
|
|
repoRef = arg
|
|
}
|
|
}
|
|
if repoRef == "" {
|
|
return errors.New("usage: gitocean repo fork OWNER/REPO [--name NEW_NAME]")
|
|
}
|
|
owner, repo, err := splitOwnerRepo(repoRef)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cfg, err := loadConfig()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var out Repository
|
|
if err := apiRequest(context.Background(), cfg.Server, http.MethodPost, fmt.Sprintf("/api/repos/%s/%s/fork", owner, repo), cfg.Token, map[string]string{"name": newName}, &out); err != nil {
|
|
return err
|
|
}
|
|
fmt.Printf("Forked %s/%s to %s/%s\n", owner, repo, out.Owner, out.Name)
|
|
fmt.Printf("Clone: git clone %s\n", repoCloneURL(cfg.Server, out.Owner, out.Name))
|
|
return nil
|
|
}
|
|
|
|
func cliRepoCollaborators(args []string) error {
|
|
if len(args) != 1 {
|
|
return errors.New("usage: gitocean repo collaborators OWNER/REPO")
|
|
}
|
|
owner, repo, err := splitOwnerRepo(args[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cfg, err := loadConfig()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var out []Collaborator
|
|
if err := apiRequest(context.Background(), cfg.Server, http.MethodGet, fmt.Sprintf("/api/repos/%s/%s/collaborators", owner, repo), cfg.Token, nil, &out); err != nil {
|
|
return err
|
|
}
|
|
for _, c := range out {
|
|
fmt.Printf("%-20s %s\n", c.Username, c.Role)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func cliRepoCollaborator(args []string) error {
|
|
if len(args) < 1 {
|
|
return errors.New("usage: gitocean repo collaborator <add|remove>")
|
|
}
|
|
switch args[0] {
|
|
case "add":
|
|
if len(args) != 5 || args[3] != "--role" {
|
|
return errors.New("usage: gitocean repo collaborator add OWNER/REPO USER --role read|write")
|
|
}
|
|
owner, repo, err := splitOwnerRepo(args[1])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cfg, err := loadConfig()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var out map[string]string
|
|
if err := apiRequest(context.Background(), cfg.Server, http.MethodPost, fmt.Sprintf("/api/repos/%s/%s/collaborators", owner, repo), cfg.Token, map[string]string{"username": args[2], "role": args[4]}, &out); err != nil {
|
|
return err
|
|
}
|
|
fmt.Printf("Added %s as %s\n", out["username"], out["role"])
|
|
return nil
|
|
case "remove":
|
|
if len(args) != 3 {
|
|
return errors.New("usage: gitocean repo collaborator remove OWNER/REPO USER")
|
|
}
|
|
owner, repo, err := splitOwnerRepo(args[1])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cfg, err := loadConfig()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return apiRequest(context.Background(), cfg.Server, http.MethodDelete, fmt.Sprintf("/api/repos/%s/%s/collaborators/%s", owner, repo, strings.ToLower(args[2])), cfg.Token, nil, nil)
|
|
default:
|
|
return fmt.Errorf("unknown collaborator command %q", args[0])
|
|
}
|
|
}
|