1
0
mirror of https://gitea.com/jolheiser/sip synced 2024-11-22 19:51:58 +01:00

Refactor (#29)

Clean up docs

Signed-off-by: jolheiser <john.olheiser@gmail.com>

Add generated docs

Signed-off-by: jolheiser <john.olheiser@gmail.com>

Update go.sum

Signed-off-by: jolheiser <john.olheiser@gmail.com>

Fix remote parsing

Signed-off-by: jolheiser <john.olheiser@gmail.com>

Refactor and clean up

Signed-off-by: jolheiser <john.olheiser@gmail.com>

Co-authored-by: jolheiser <john.olheiser@gmail.com>
Reviewed-on: https://gitea.com/jolheiser/sip/pulls/29
This commit is contained in:
John Olheiser 2020-09-17 16:25:09 +00:00
parent a51305f816
commit 894a641ef8
36 changed files with 444 additions and 207 deletions

7
LICENSE Normal file

@ -0,0 +1,7 @@
Copyright 2020 John Olheiser
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

@ -32,6 +32,10 @@ fmt:
test: test:
$(GO) test -race ./... $(GO) test -race ./...
.PHONY: docs
docs:
$(GO) run docs.go
.PHONY: release .PHONY: release
release: release-dirs check-xgo release-windows release-linux release-darwin release-copy release-compress release-check release: release-dirs check-xgo release-windows release-linux release-darwin release-copy release-compress release-check

@ -1,12 +1,14 @@
# Sip (alternative) # Sip
CLI for interacting with Gitea CLI for interacting with Gitea
[![Build Status](https://drone.gitea.com/api/badges/jolheiser/sip/status.svg)](https://drone.gitea.com/jolheiser/sip) [![Build Status](https://drone.gitea.com/api/badges/jolheiser/sip/status.svg)](https://drone.gitea.com/jolheiser/sip)
[Docs](docs.md)
### Features ### Features
Understands the concepts of an origin vs remote repository. Understands the concepts of an origin vs remote repository.
By default uses remotes `origin` and `upstream`. By default, uses remotes `origin` and `upstream`.
If no `upstream` repository is found, `upstream` becomes synonymous with `origin` for the sake of defaults. If no `upstream` repository is found, `upstream` becomes synonymous with `origin` for the sake of defaults.
* Configuration `sip config` * Configuration `sip config`
@ -28,6 +30,12 @@ If no `upstream` repository is found, `upstream` becomes synonymous with `origin
* Create a new pull request `sip pulls create` * Create a new pull request `sip pulls create`
* Check pull request status (default based on current branch) `sip pulls status` * Check pull request status (default based on current branch) `sip pulls status`
* Checkout a pull request to test locally `sip pulls checkout` * Checkout a pull request to test locally `sip pulls checkout`
* Release search `sip release`
* Create a new release `sip release create`
* Attach files to an existing release `sip release attach`
* Open item in a web browser
* Repository `sip open` or `sip open owner/repo`
* Issue or PR `sip open 1234` or `sip open owner/repo/1234`
### Search filters ### Search filters
Sip supports certain search filters for issues/PRs. Sip supports certain search filters for issues/PRs.
@ -39,3 +47,7 @@ Anything in the query that doesn't match one of the below filters will be sent a
* Milestone `mileston:v1.0.0` - only the last milestone in the query will be applied * Milestone `mileston:v1.0.0` - only the last milestone in the query will be applied
e.g. `test is:open query author:jolheiser milestone:0.2.0` will search for issues/PRs with keywords `test query` that are `open`, authored by `jolheiser`, and in the `0.2.0` milestone. e.g. `test is:open query author:jolheiser milestone:0.2.0` will search for issues/PRs with keywords `test query` that are `open`, authored by `jolheiser`, and in the `0.2.0` milestone.
## License
[MIT](LICENSE)

@ -3,10 +3,9 @@ package cmd
import ( import (
"errors" "errors"
"fmt" "fmt"
"sync"
"gitea.com/jolheiser/sip/modules/config" "gitea.com/jolheiser/sip/config"
"gitea.com/jolheiser/sip/modules/git" "gitea.com/jolheiser/sip/flag"
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
@ -14,47 +13,25 @@ import (
"go.jolheiser.com/beaver/color" "go.jolheiser.com/beaver/color"
) )
var ( func NewApp(version string) *cli.App {
Flags = []cli.Flag{ app := cli.NewApp()
&cli.StringFlag{ app.Name = "Sip"
Name: "origin", app.Usage = "Command line tool to interact with Gitea"
Usage: "The origin remote", app.Version = version
Value: config.Origin, app.Commands = []*cli.Command{
}, &Config,
&cli.StringFlag{ &Tokens,
Name: "upstream", &Repo,
Usage: "The upstream remote", &Issues,
Value: config.Upstream, &Pulls,
}, &Release,
&cli.StringFlag{ &Open,
Name: "url", }
Aliases: []string{"u"}, app.Before = flag.Before
Usage: "The base URL to the Gitea instance", app.Flags = flag.Flags
Value: getUpstreamRepo()[0], app.EnableBashCompletion = true
}, return app
&cli.StringFlag{
Name: "owner",
Aliases: []string{"o"},
Usage: "The owner to target",
Value: getUpstreamRepo()[1],
},
&cli.StringFlag{
Name: "repo",
Aliases: []string{"r"},
Usage: "The repo to target",
Value: getUpstreamRepo()[2],
},
&cli.StringFlag{
Name: "token",
Aliases: []string{"t"},
Usage: "The access token to use (by name)",
},
} }
originOnce sync.Once
originRepo []string
upstreamOnce sync.Once
upstreamRepo []string
)
func getToken(name string) string { func getToken(name string) string {
for _, token := range config.Tokens { for _, token := range config.Tokens {
@ -65,9 +42,9 @@ func getToken(name string) string {
return "" return ""
} }
func getClient(ctx *cli.Context, requireToken bool) (*gitea.Client, error) { func getClient(requireToken bool) (*gitea.Client, error) {
if ctx.IsSet("token") { if flag.Token != "" {
return gitea.NewClient(ctx.String("url"), gitea.SetToken(getToken(ctx.String("token")))) return gitea.NewClient(flag.URL, gitea.SetToken(getToken(flag.Token)))
} }
var token string var token string
@ -93,42 +70,5 @@ func getClient(ctx *cli.Context, requireToken bool) (*gitea.Client, error) {
token = tokenMap[answer].Token token = tokenMap[answer].Token
} }
return gitea.NewClient(ctx.String("url"), gitea.SetToken(token)) return gitea.NewClient(flag.URL, gitea.SetToken(token))
}
func defRemote(remote, def string) string {
if remote == "" {
return def
}
return remote
}
func getUpstreamRepo() []string {
upstreamOnce.Do(func() {
var err error
upstreamRepo, err = git.GetRepo(defRemote(config.Upstream, "upstream"))
if err != nil {
upstreamRepo = getOriginRepo()
}
})
return upstreamRepo
}
func getOriginRepo() []string {
originOnce.Do(func() {
var err error
originRepo, err = git.GetRepo(defRemote(config.Origin, "origin"))
if err != nil {
originRepo = []string{"https://gitea.com", "jolheiser", "sip"}
}
})
return originRepo
}
func fullRepoURL(ctx *cli.Context) string {
return ctx.String("url") + "/" + fullName(ctx)
}
func fullName(ctx *cli.Context) string {
return ctx.String("owner") + "/" + ctx.String("repo")
} }

@ -1,7 +1,7 @@
package cmd package cmd
import ( import (
"gitea.com/jolheiser/sip/modules/config" "gitea.com/jolheiser/sip/config"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
@ -34,7 +34,7 @@ func doConfig(ctx *cli.Context) error {
return doConfigUpstream(ctx) return doConfigUpstream(ctx)
} }
func doConfigOrigin(ctx *cli.Context) error { func doConfigOrigin(_ *cli.Context) error {
question := &survey.Input{ question := &survey.Input{
Message: "Default origin name", Message: "Default origin name",
Default: "origin", Default: "origin",
@ -53,7 +53,7 @@ func doConfigOrigin(ctx *cli.Context) error {
return nil return nil
} }
func doConfigUpstream(ctx *cli.Context) error { func doConfigUpstream(_ *cli.Context) error {
question := &survey.Input{ question := &survey.Input{
Message: "Default upstream name", Message: "Default upstream name",
Default: "upstream", Default: "upstream",

@ -7,9 +7,10 @@ import (
"strconv" "strconv"
"strings" "strings"
"gitea.com/jolheiser/sip/modules/csv" "gitea.com/jolheiser/sip/csv"
"gitea.com/jolheiser/sip/modules/markdown" "gitea.com/jolheiser/sip/flag"
"gitea.com/jolheiser/sip/modules/sdk" "gitea.com/jolheiser/sip/markdown"
"gitea.com/jolheiser/sip/sdk"
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
@ -29,7 +30,7 @@ var Issues = cli.Command{
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "csv", Name: "csv",
Usage: "Output results to a CSV file", Usage: "Output results to a CSV file at `PATH`",
}, },
}, },
} }
@ -42,7 +43,7 @@ func doIssuesSearch(ctx *cli.Context) error {
} }
func issuesSearch(ctx *cli.Context, pulls bool) (*gitea.Issue, error) { func issuesSearch(ctx *cli.Context, pulls bool) (*gitea.Issue, error) {
client, err := getClient(ctx, false) client, err := getClient(false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -51,7 +52,7 @@ func issuesSearch(ctx *cli.Context, pulls bool) (*gitea.Issue, error) {
if pulls { if pulls {
typ = "pulls" typ = "pulls"
} }
issues, err := queryIssues(ctx, client, pulls) issues, err := queryIssues(client, pulls)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -113,8 +114,8 @@ func issuesSearch(ctx *cli.Context, pulls bool) (*gitea.Issue, error) {
return issueMap[selection], nil return issueMap[selection], nil
} }
func queryIssues(ctx *cli.Context, client *gitea.Client, pulls bool) ([]*gitea.Issue, error) { func queryIssues(client *gitea.Client, pulls bool) ([]*gitea.Issue, error) {
owner, repo, err := askOwnerRepo(ctx) owner, repo, err := askOwnerRepo()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -150,11 +151,15 @@ func queryIssues(ctx *cli.Context, client *gitea.Client, pulls bool) ([]*gitea.I
return filtered, nil return filtered, nil
} }
func askOwnerRepo(ctx *cli.Context) (string, string, error) { func askOwnerRepo() (string, string, error) {
// If --owner or --repo was set, assume the user knows where they are searching
if flag.OwnerRepoCtxSet {
return flag.Owner, flag.Repo, nil
}
question := []*survey.Question{ question := []*survey.Question{
{ {
Name: "repo", Name: "repo",
Prompt: &survey.Input{Message: "Full repository name", Default: fullName(ctx)}, Prompt: &survey.Input{Message: "Full repository name", Default: flag.FullName()},
Validate: validateFullName, Validate: validateFullName,
}, },
} }

@ -3,7 +3,8 @@ package cmd
import ( import (
"fmt" "fmt"
"gitea.com/jolheiser/sip/modules/markdown" "gitea.com/jolheiser/sip/flag"
"gitea.com/jolheiser/sip/markdown"
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
@ -18,12 +19,12 @@ var IssuesCreate = cli.Command{
Action: doIssueCreate, Action: doIssueCreate,
} }
func doIssueCreate(ctx *cli.Context) error { func doIssueCreate(_ *cli.Context) error {
fmt.Println() fmt.Println()
url := color.New(color.FgYellow).Format(fmt.Sprintf("%s/%s/%s", ctx.String("url"), ctx.String("owner"), ctx.String("repo"))) url := color.New(color.FgYellow).Format(flag.FullURL())
fmt.Println(color.New(color.FgCyan).Format("Creating a new issue for"), url) fmt.Println(color.New(color.FgCyan).Format("Creating a new issue for"), url)
client, err := getClient(ctx, true) client, err := getClient(true)
if err != nil { if err != nil {
return err return err
} }
@ -67,7 +68,7 @@ func doIssueCreate(ctx *cli.Context) error {
} }
} }
issue, _, err := client.CreateIssue(ctx.String("owner"), ctx.String("repo"), gitea.CreateIssueOption{Title: title, Body: body}) issue, _, err := client.CreateIssue(flag.Owner, flag.Repo, gitea.CreateIssueOption{Title: title, Body: body})
if err != nil { if err != nil {
return err return err
} }
@ -75,6 +76,6 @@ func doIssueCreate(ctx *cli.Context) error {
info := color.Info info := color.Info
cyan := color.New(color.FgCyan) cyan := color.New(color.FgCyan)
fmt.Println(info.Format("Issue"), cyan.Format(fmt.Sprintf("#%d", issue.Index)), info.Format("created!")) fmt.Println(info.Format("Issue"), cyan.Format(fmt.Sprintf("#%d", issue.Index)), info.Format("created!"))
fmt.Println(cyan.Format(fmt.Sprintf("%s/%s/%s/issues/%d", ctx.String("url"), ctx.String("owner"), ctx.String("repo"), issue.Index))) fmt.Println(cyan.Format(fmt.Sprintf("%s/issues/%d", flag.FullURL(), issue.Index)))
return nil return nil
} }

@ -6,6 +6,8 @@ import (
"strconv" "strconv"
"strings" "strings"
"gitea.com/jolheiser/sip/flag"
"github.com/skratchdot/open-golang/open" "github.com/skratchdot/open-golang/open"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
@ -18,9 +20,8 @@ var Open = cli.Command{
} }
func doOpen(ctx *cli.Context) error { func doOpen(ctx *cli.Context) error {
repo := fullRepoURL(ctx)
if ctx.NArg() == 0 { if ctx.NArg() == 0 {
return open.Run(repo) return open.Run(flag.FullURL())
} }
arg := ctx.Args().First() arg := ctx.Args().First()
@ -28,18 +29,18 @@ func doOpen(ctx *cli.Context) error {
// Check if issue or PR // Check if issue or PR
issue, err := strconv.ParseInt(arg, 10, 64) issue, err := strconv.ParseInt(arg, 10, 64)
if err == nil { if err == nil {
return open.Run(fmt.Sprintf("%s/issues/%d", repo, issue)) return open.Run(fmt.Sprintf("%s/issues/%d", flag.FullURL(), issue))
} }
// Check if overriding repository (jolheiser/sip) // Check if overriding repository (jolheiser/sip)
ownerRepoIssue := strings.Split(arg, "/") ownerRepoIssue := strings.Split(arg, "/")
if len(ownerRepoIssue) == 2 { if len(ownerRepoIssue) == 2 {
return open.Run(fmt.Sprintf("%s/%s", ctx.String("url"), arg)) return open.Run(fmt.Sprintf("%s/%s", flag.FullURL(), arg))
} }
// Check if both? (jolheiser/sip/1234) // Check if both? (jolheiser/sip/1234)
if len(ownerRepoIssue) == 3 { if len(ownerRepoIssue) == 3 {
return open.Run(fmt.Sprintf("%s/%s/%s/issues/%s", ctx.String("url"), ownerRepoIssue[0], ownerRepoIssue[1], ownerRepoIssue[2])) return open.Run(fmt.Sprintf("%s/%s/%s/issues/%s", flag.URL, ownerRepoIssue[0], ownerRepoIssue[1], ownerRepoIssue[2]))
} }
return errors.New("unknown argument: leave blank to open current repo, pass issue/PR as #1234, or override repo as owner/repo") return errors.New("unknown argument: leave blank to open current repo, pass issue/PR as #1234, or override repo as owner/repo")

@ -17,7 +17,7 @@ var Pulls = cli.Command{
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "csv", Name: "csv",
Usage: "Output results to a CSV file", Usage: "Output results to a CSV file at `PATH`",
}, },
}, },
} }

@ -7,7 +7,8 @@ import (
"os/exec" "os/exec"
"strconv" "strconv"
"gitea.com/jolheiser/sip/modules/config" "gitea.com/jolheiser/sip/config"
"gitea.com/jolheiser/sip/flag"
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
@ -23,7 +24,7 @@ var PullsCheckout = cli.Command{
} }
func doPullCheckout(ctx *cli.Context) error { func doPullCheckout(ctx *cli.Context) error {
client, err := getClient(ctx, true) client, err := getClient(true)
if err != nil { if err != nil {
return err return err
} }
@ -57,7 +58,7 @@ func doPullCheckout(ctx *cli.Context) error {
} }
} }
} else { } else {
iss, _, err := client.GetIssue(upstreamRepo[1], upstreamRepo[2], prNum.Index) iss, _, err := client.GetIssue(flag.Upstream.Owner, flag.Upstream.Repo, prNum.Index)
if err != nil { if err != nil {
return err return err
} }

@ -2,9 +2,11 @@ package cmd
import ( import (
"fmt" "fmt"
"strings"
"gitea.com/jolheiser/sip/modules/git" "gitea.com/jolheiser/sip/flag"
"gitea.com/jolheiser/sip/modules/markdown" "gitea.com/jolheiser/sip/git"
"gitea.com/jolheiser/sip/markdown"
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
@ -19,30 +21,32 @@ var PullsCreate = cli.Command{
Action: doPullCreate, Action: doPullCreate,
} }
func doPullCreate(ctx *cli.Context) error { func doPullCreate(_ *cli.Context) error {
fmt.Println() fmt.Println()
url := color.New(color.FgYellow).Format(fmt.Sprintf("%s/%s/%s", ctx.String("url"), ctx.String("owner"), ctx.String("repo"))) url := color.New(color.FgYellow).Format(flag.FullURL())
fmt.Println(color.New(color.FgCyan).Format("Creating a new pull request for"), url) fmt.Println(color.New(color.FgCyan).Format("Creating a new pull request for"), url)
client, err := getClient(ctx, true) client, err := getClient(true)
if err != nil { if err != nil {
return err return err
} }
upstreams, _, err := client.ListRepoBranches(getUpstreamRepo()[1], getUpstreamRepo()[2], gitea.ListRepoBranchesOptions{}) repo, _, err := client.GetRepo(flag.Upstream.Owner, flag.Upstream.Repo)
if err != nil {
return err
}
upstreams, _, err := client.ListRepoBranches(flag.Upstream.Owner, flag.Upstream.Repo, gitea.ListRepoBranchesOptions{})
if err != nil { if err != nil {
return err return err
} }
bases := make([]string, len(upstreams)) bases := make([]string, len(upstreams))
defUpstream := upstreams[0].Name defUpstream := repo.DefaultBranch
for idx, upstream := range upstreams { for idx, upstream := range upstreams {
if upstream.Name == "master" {
defUpstream = upstream.Name
}
bases[idx] = upstream.Name bases[idx] = upstream.Name
} }
origins, _, err := client.ListRepoBranches(getOriginRepo()[1], getOriginRepo()[2], gitea.ListRepoBranchesOptions{}) origins, _, err := client.ListRepoBranches(flag.Origin.Owner, flag.Origin.Repo, gitea.ListRepoBranchesOptions{})
if err != nil { if err != nil {
return err return err
} }
@ -50,10 +54,10 @@ func doPullCreate(ctx *cli.Context) error {
defOrigin := origins[0].Name defOrigin := origins[0].Name
for idx, origin := range origins { for idx, origin := range origins {
originName := origin.Name originName := origin.Name
if ctx.String("owner") != getOriginRepo()[1] { if flag.Owner != flag.Origin.Owner {
originName = getOriginRepo()[1] + ":" + originName originName = flag.Origin.Owner + ":" + originName
} }
if origin.Name == git.Branch() { if strings.EqualFold(origin.Name, git.Branch()) {
defOrigin = originName defOrigin = originName
} }
heads[idx] = originName heads[idx] = originName
@ -114,10 +118,10 @@ func doPullCreate(ctx *cli.Context) error {
} }
} }
pull, _, err := client.CreatePullRequest(ctx.String("owner"), ctx.String("repo"), gitea.CreatePullRequestOption{Title: title, Body: body, Base: base, Head: head}) pull, _, err := client.CreatePullRequest(flag.Owner, flag.Repo, gitea.CreatePullRequestOption{Title: title, Body: body, Base: base, Head: head})
if err != nil { if err != nil {
if fmt.Sprint(err) == "409 Conflict" { // Hard-coded in the SDK if fmt.Sprint(err) == "409 Conflict" { // Hard-coded in the SDK
return existingPR(client, getUpstreamRepo()[1], getUpstreamRepo()[2], head, err) return existingPR(client, flag.Upstream.Owner, flag.Upstream.Repo, head, err)
} }
return err return err
} }
@ -125,7 +129,7 @@ func doPullCreate(ctx *cli.Context) error {
info := color.Info info := color.Info
cyan := color.New(color.FgCyan) cyan := color.New(color.FgCyan)
fmt.Println(info.Format("PR"), cyan.Format(fmt.Sprintf("#%d", pull.Index)), info.Format("created!")) fmt.Println(info.Format("PR"), cyan.Format(fmt.Sprintf("#%d", pull.Index)), info.Format("created!"))
fmt.Println(cyan.Format(fmt.Sprintf("%s/%s/%s/pulls/%d", ctx.String("url"), ctx.String("owner"), ctx.String("repo"), pull.Index))) fmt.Println(cyan.Format(fmt.Sprintf("%s/pulls/%d", flag.FullURL(), pull.Index)))
return nil return nil
} }

@ -4,8 +4,9 @@ import (
"fmt" "fmt"
"strconv" "strconv"
"gitea.com/jolheiser/sip/modules/git" "gitea.com/jolheiser/sip/flag"
"gitea.com/jolheiser/sip/modules/sdk" "gitea.com/jolheiser/sip/git"
"gitea.com/jolheiser/sip/sdk"
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
@ -18,15 +19,15 @@ var PullsStatus = cli.Command{
Action: doPullStatus, Action: doPullStatus,
} }
func doPullStatus(ctx *cli.Context) error { func doPullStatus(_ *cli.Context) error {
client, err := getClient(ctx, false) client, err := getClient(false)
if err != nil { if err != nil {
return err return err
} }
head := fmt.Sprintf("%s:%s", getOriginRepo()[1], git.Branch()) head := fmt.Sprintf("%s:%s", flag.Origin.Owner, git.Branch())
pulls, err := sdk.GetPulls(client, getUpstreamRepo()[1], getUpstreamRepo()[2], gitea.ListPullRequestsOptions{State: "all"}) pulls, err := sdk.GetPulls(client, flag.Upstream.Owner, flag.Upstream.Repo, gitea.ListPullRequestsOptions{State: "all"})
if err != nil { if err != nil {
return err return err
} }

@ -4,9 +4,9 @@ import (
"fmt" "fmt"
"os" "os"
"gitea.com/jolheiser/sip/modules/csv" "gitea.com/jolheiser/sip/csv"
"gitea.com/jolheiser/sip/modules/markdown" "gitea.com/jolheiser/sip/markdown"
"gitea.com/jolheiser/sip/modules/sdk" "gitea.com/jolheiser/sip/sdk"
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
@ -26,18 +26,18 @@ var Release = cli.Command{
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "csv", Name: "csv",
Usage: "Output results to a CSV file", Usage: "Output results to a CSV file at `PATH`",
}, },
}, },
} }
func doRelease(ctx *cli.Context) error { func doRelease(ctx *cli.Context) error {
client, err := getClient(ctx, false) client, err := getClient(false)
if err != nil { if err != nil {
return err return err
} }
owner, repo, err := askOwnerRepo(ctx) owner, repo, err := askOwnerRepo()
if err != nil { if err != nil {
return err return err
} }

@ -6,7 +6,8 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"gitea.com/jolheiser/sip/modules/sdk" "gitea.com/jolheiser/sip/flag"
"gitea.com/jolheiser/sip/sdk"
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
@ -21,13 +22,13 @@ var ReleaseAttach = cli.Command{
Action: doReleaseAttach, Action: doReleaseAttach,
} }
func doReleaseAttach(ctx *cli.Context) error { func doReleaseAttach(_ *cli.Context) error {
client, err := getClient(ctx, true) client, err := getClient(true)
if err != nil { if err != nil {
return err return err
} }
releases, err := sdk.GetReleases(client, ctx.String("owner"), ctx.String("repo"), gitea.ListReleasesOptions{}) releases, err := sdk.GetReleases(client, flag.Owner, flag.Repo, gitea.ListReleasesOptions{})
if err != nil { if err != nil {
return err return err
} }
@ -64,15 +65,14 @@ func doReleaseAttach(ctx *cli.Context) error {
if err != nil { if err != nil {
return err return err
} }
if err := attachFiles(ctx, client, release.ID, files); err != nil { if err := attachFiles(client, release.ID, files); err != nil {
return err return err
} }
info := color.Info info := color.Info
cyan := color.New(color.FgCyan) cyan := color.New(color.FgCyan)
fmt.Println(info.Format("Release"), cyan.Format(release.TagName), info.Format("updated!")) fmt.Println(info.Format("Release"), cyan.Format(release.TagName), info.Format("updated!"))
fmt.Println(cyan.Format(fmt.Sprintf("%s/%s/%s/releases/tag/%s", fmt.Println(cyan.Format(fmt.Sprintf("%s/releases/tag/%s", flag.FullURL(), release.TagName)))
ctx.String("url"), ctx.String("owner"), ctx.String("repo"), release.TagName)))
return nil return nil
} }
@ -89,7 +89,7 @@ func fileGlobs(globList string) ([]string, error) {
return files, nil return files, nil
} }
func attachFiles(ctx *cli.Context, client *gitea.Client, releaseID int64, files []string) error { func attachFiles(client *gitea.Client, releaseID int64, files []string) error {
beaver.Infof("Attachments:\n\t%s", strings.Join(files, "\n\t")) beaver.Infof("Attachments:\n\t%s", strings.Join(files, "\n\t"))
var confirm bool var confirm bool
@ -104,7 +104,7 @@ func attachFiles(ctx *cli.Context, client *gitea.Client, releaseID int64, files
return err return err
} }
if _, _, err := client.CreateReleaseAttachment(ctx.String("owner"), ctx.String("repo"), releaseID, fi, fi.Name()); err != nil { if _, _, err := client.CreateReleaseAttachment(flag.Owner, flag.Repo, releaseID, fi, fi.Name()); err != nil {
return err return err
} }

@ -3,7 +3,8 @@ package cmd
import ( import (
"fmt" "fmt"
"gitea.com/jolheiser/sip/modules/git" "gitea.com/jolheiser/sip/flag"
"gitea.com/jolheiser/sip/git"
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
@ -18,8 +19,8 @@ var ReleaseCreate = cli.Command{
Action: doReleaseCreate, Action: doReleaseCreate,
} }
func doReleaseCreate(ctx *cli.Context) error { func doReleaseCreate(_ *cli.Context) error {
client, err := getClient(ctx, true) client, err := getClient(true)
if err != nil { if err != nil {
return err return err
} }
@ -73,7 +74,7 @@ func doReleaseCreate(ctx *cli.Context) error {
return err return err
} }
release, _, err := client.CreateRelease(ctx.String("owner"), ctx.String("repo"), gitea.CreateReleaseOption{ release, _, err := client.CreateRelease(flag.Owner, flag.Repo, gitea.CreateReleaseOption{
TagName: answers.Tag, TagName: answers.Tag,
Target: answers.Target, Target: answers.Target,
Title: answers.Title, Title: answers.Title,
@ -90,7 +91,7 @@ func doReleaseCreate(ctx *cli.Context) error {
if err != nil { if err != nil {
return err return err
} }
if err := attachFiles(ctx, client, release.ID, files); err != nil { if err := attachFiles(client, release.ID, files); err != nil {
return err return err
} }
} }
@ -98,7 +99,6 @@ func doReleaseCreate(ctx *cli.Context) error {
info := color.Info info := color.Info
cyan := color.New(color.FgCyan) cyan := color.New(color.FgCyan)
fmt.Println(info.Format("Release"), cyan.Format(release.TagName), info.Format("created!")) fmt.Println(info.Format("Release"), cyan.Format(release.TagName), info.Format("created!"))
fmt.Println(cyan.Format(fmt.Sprintf("%s/%s/%s/releases/tag/%s", fmt.Println(cyan.Format(fmt.Sprintf("%s/releases/tag/%s", flag.FullURL(), release.TagName)))
ctx.String("url"), ctx.String("owner"), ctx.String("repo"), release.TagName)))
return nil return nil
} }

@ -3,7 +3,8 @@ package cmd
import ( import (
"strconv" "strconv"
"gitea.com/jolheiser/sip/modules/sdk" "gitea.com/jolheiser/sip/flag"
"gitea.com/jolheiser/sip/sdk"
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
@ -20,18 +21,18 @@ var Repo = cli.Command{
}, },
} }
func doRepo(ctx *cli.Context) error { func doRepo(_ *cli.Context) error {
client, err := getClient(ctx, false) client, err := getClient(false)
if err != nil { if err != nil {
return err return err
} }
repo, _, err := client.GetRepo(ctx.String("owner"), ctx.String("repo")) repo, _, err := client.GetRepo(flag.Owner, flag.Repo)
if err != nil { if err != nil {
return err return err
} }
issues, err := sdk.GetIssues(client, ctx.String("owner"), ctx.String("repo"), gitea.ListIssueOption{State: "open"}) issues, err := sdk.GetIssues(client, flag.Owner, flag.Repo, gitea.ListIssueOption{State: "open"})
if err != nil { if err != nil {
return err return err
} }

@ -16,8 +16,8 @@ var RepoCreate = cli.Command{
Action: doRepoCreate, Action: doRepoCreate,
} }
func doRepoCreate(ctx *cli.Context) error { func doRepoCreate(_ *cli.Context) error {
client, err := getClient(ctx, true) client, err := getClient(true)
if err != nil { if err != nil {
return err return err
} }

@ -1,7 +1,7 @@
package cmd package cmd
import ( import (
"gitea.com/jolheiser/sip/modules/config" "gitea.com/jolheiser/sip/config"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"go.jolheiser.com/beaver" "go.jolheiser.com/beaver"
@ -19,7 +19,7 @@ var Tokens = cli.Command{
}, },
} }
func doTokenList(ctx *cli.Context) error { func doTokenList(_ *cli.Context) error {
if len(config.Tokens) == 0 { if len(config.Tokens) == 0 {
beaver.Errorf("No tokens found! Add one with %s", color.FgMagenta.Format("sip token create")) beaver.Errorf("No tokens found! Add one with %s", color.FgMagenta.Format("sip token create"))
} }

@ -3,7 +3,8 @@ package cmd
import ( import (
"errors" "errors"
"gitea.com/jolheiser/sip/modules/config" "gitea.com/jolheiser/sip/config"
"gitea.com/jolheiser/sip/flag"
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
@ -28,7 +29,7 @@ func doTokenAdd(ctx *cli.Context) error {
}, },
{ {
Name: "url", Name: "url",
Prompt: &survey.Input{Message: "URL for the Gitea instance", Default: ctx.String("url")}, Prompt: &survey.Input{Message: "URL for the Gitea instance", Default: flag.URL},
Validate: survey.Required, Validate: survey.Required,
}, },
} }

@ -3,7 +3,7 @@ package cmd
import ( import (
"fmt" "fmt"
"gitea.com/jolheiser/sip/modules/config" "gitea.com/jolheiser/sip/config"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
@ -17,7 +17,7 @@ var TokensRemove = cli.Command{
Action: doTokenRemove, Action: doTokenRemove,
} }
func doTokenRemove(ctx *cli.Context) error { func doTokenRemove(_ *cli.Context) error {
opts := make([]string, len(config.Tokens)) opts := make([]string, len(config.Tokens))
for idx, token := range config.Tokens { for idx, token := range config.Tokens {
opts[idx] = fmt.Sprintf("%s (%s)", token.Name, token.URL) opts[idx] = fmt.Sprintf("%s (%s)", token.Name, token.URL)

@ -7,15 +7,12 @@ import (
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
"github.com/mitchellh/go-homedir" "github.com/mitchellh/go-homedir"
"go.jolheiser.com/beaver"
) )
var ( var (
configPath string configPath string
cfg *config
// Config items // Config items
Origin string Origin string
Upstream string Upstream string
Tokens []Token Tokens []Token
@ -34,46 +31,49 @@ type Token struct {
} }
// Load on init so that CLI contexts are correctly populated // Load on init so that CLI contexts are correctly populated
func init() { func Init() error {
home, err := homedir.Dir() home, err := homedir.Dir()
if err != nil { if err != nil {
beaver.Fatalf("could not locate home directory: %v", err) return err
} }
configPath = fmt.Sprintf("%s/.sip/config.toml", home) configPath = fmt.Sprintf("%s/.sip/config.toml", home)
if _, err := os.Stat(configPath); os.IsNotExist(err) { if _, err := os.Stat(configPath); os.IsNotExist(err) {
if err := os.MkdirAll(path.Dir(configPath), os.ModePerm); err != nil { if err := os.MkdirAll(path.Dir(configPath), os.ModePerm); err != nil {
beaver.Fatalf("could not create Sip home: %v", err) return err
} }
if _, err := os.Create(configPath); err != nil { if _, err := os.Create(configPath); err != nil {
beaver.Fatalf("could not create Sip config: %v", err) return err
} }
} }
var cfg config
if _, err := toml.DecodeFile(configPath, &cfg); err != nil { if _, err := toml.DecodeFile(configPath, &cfg); err != nil {
beaver.Fatalf("could not decode Sip config: %v", err) return err
} }
Origin = cfg.Origin Origin = cfg.Origin
Upstream = cfg.Upstream Upstream = cfg.Upstream
Tokens = cfg.Tokens Tokens = cfg.Tokens
return nil
} }
func Save() error { func Save() error {
cfg.Origin = Origin cfg := config{
cfg.Upstream = Upstream Origin: Origin,
cfg.Tokens = Tokens Upstream: Upstream,
Tokens: Tokens,
}
fi, err := os.Create(configPath) fi, err := os.Create(configPath)
if err != nil { if err != nil {
return err return err
} }
defer fi.Close()
if err := toml.NewEncoder(fi).Encode(cfg); err != nil { if err := toml.NewEncoder(fi).Encode(cfg); err != nil {
return err return err
} }
return nil return fi.Close()
} }

35
docs.go Normal file

@ -0,0 +1,35 @@
// +build docs
package main
import (
"os"
"strings"
"gitea.com/jolheiser/sip/cmd"
)
func main() {
app := cmd.NewApp("docs")
fi, err := os.Create("docs.md")
if err != nil {
panic(err)
}
md, err := app.ToMarkdown()
if err != nil {
panic(err)
}
// Clean up the header
md = md[strings.Index(md, "#"):]
if _, err := fi.WriteString(md); err != nil {
panic(err)
}
if err := fi.Close(); err != nil {
panic(err)
}
}

117
docs.md Normal file

@ -0,0 +1,117 @@
# NAME
Sip - Command line tool to interact with Gitea
# SYNOPSIS
Sip
```
[--origin]=[value]
[--owner|-o]=[value]
[--repo|-r]=[value]
[--token|-t]=[value]
[--upstream]=[value]
[--url|-u]=[value]
```
**Usage**:
```
Sip [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
```
# GLOBAL OPTIONS
**--origin**="": The origin remote
**--owner, -o**="": The owner to target (default: jolheiser)
**--repo, -r**="": The repo to target (default: sip)
**--token, -t**="": The access token to use (by name)
**--upstream**="": The upstream remote
**--url, -u**="": The base URL to the Gitea instance (default: https://gitea.com)
# COMMANDS
## config, cfg
Modify Sip config
### origin
Specify default origin name
### upstream
Specify default upstream name
## tokens, token
Manage access tokens
### add, create
Add a new access token
### remove, delete
Remove access tokens
## repo
Commands for interacting with a Gitea repository
### create
Create a new repository
## issues, issue
Commands for interacting with issues
**--csv**="": Output results to a CSV file at `PATH`
### create, new
Create a new issue
## pulls, pull, pr
Commands for interacting with pull requests
**--csv**="": Output results to a CSV file at `PATH`
### create, new
Create a new pull request
### status
View the status of a pull request
### checkout
Checkout a pull request for testing
## releases, release
Commands for interacting with releases
**--csv**="": Output results to a CSV file at `PATH`
### create, new
Create a new release
### attach
Attach files to a release
## open, o
Open a repository or issue/pull request

121
flag/flag.go Normal file

@ -0,0 +1,121 @@
package flag
import (
"fmt"
"sync"
"gitea.com/jolheiser/sip/config"
"gitea.com/jolheiser/sip/git"
"github.com/urfave/cli/v2"
)
type Remote struct {
Name string
URL string
Owner string
Repo string
}
var (
// Set via config
Origin Remote
Upstream Remote
// Set via flags
URL string
Owner string
Repo string
Token string
// Set via Before
OwnerRepoCtxSet bool
Flags = []cli.Flag{
&cli.StringFlag{
Name: "origin",
Usage: "The origin remote",
Value: config.Origin,
Destination: &Origin.Name,
},
&cli.StringFlag{
Name: "upstream",
Usage: "The upstream remote",
Value: config.Upstream,
Destination: &Upstream.Name,
},
&cli.StringFlag{
Name: "url",
Aliases: []string{"u"},
Usage: "The base URL to the Gitea instance",
Value: getUpstream().URL,
Destination: &URL,
},
&cli.StringFlag{
Name: "owner",
Aliases: []string{"o"},
Usage: "The owner to target",
Value: getUpstream().Owner,
Destination: &Owner,
},
&cli.StringFlag{
Name: "repo",
Aliases: []string{"r"},
Usage: "The repo to target",
Value: getUpstream().Repo,
Destination: &Repo,
},
&cli.StringFlag{
Name: "token",
Aliases: []string{"t"},
Usage: "The access token to use (by name)",
Destination: &Token,
},
}
originOnce sync.Once
upstreamOnce sync.Once
)
func FullName() string {
return fmt.Sprintf("%s/%s", Owner, Repo)
}
func FullURL() string {
return fmt.Sprintf("%s/%s", URL, FullName())
}
func Before(ctx *cli.Context) error {
OwnerRepoCtxSet = ctx.IsSet("owner") || ctx.IsSet("repo")
return nil
}
func defRemote(remote, def string) string {
if remote == "" {
return def
}
return remote
}
func getUpstream() Remote {
upstreamOnce.Do(func() {
Upstream.URL, Upstream.Owner, Upstream.Repo = getOrigin().URL, getOrigin().Owner, getOrigin().Name
upstream, err := git.GetRepo(defRemote(config.Upstream, "upstream"))
if err == nil {
Upstream.URL, Upstream.Owner, Upstream.Repo = upstream[0], upstream[1], upstream[2]
}
})
return Upstream
}
func getOrigin() Remote {
originOnce.Do(func() {
Origin.URL, Origin.Owner, Origin.Name = "https://gitea.com", "jolheiser", "sip"
origin, err := git.GetRepo(defRemote(config.Origin, "origin"))
if err == nil {
Origin.URL, Origin.Owner, Origin.Repo = origin[0], origin[1], origin[2]
}
})
return Origin
}

2
go.sum

@ -1,5 +1,3 @@
code.gitea.io/sdk/gitea v0.12.2 h1:NQI8b/CT9AEQjsxbVIZ6gsPUXv38moT5y1ocN7n1YcQ=
code.gitea.io/sdk/gitea v0.12.2/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY=
code.gitea.io/sdk/gitea v0.13.0 h1:iHognp8ZMhMFLooUUNZFpm8IHaC9qoHJDvAE5vTm5aw= code.gitea.io/sdk/gitea v0.13.0 h1:iHognp8ZMhMFLooUUNZFpm8IHaC9qoHJDvAE5vTm5aw=
code.gitea.io/sdk/gitea v0.13.0/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY= code.gitea.io/sdk/gitea v0.13.0/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY=
github.com/AlecAivazis/survey/v2 v2.1.1 h1:LEMbHE0pLj75faaVEKClEX1TM4AJmmnOh9eimREzLWI= github.com/AlecAivazis/survey/v2 v2.1.1 h1:LEMbHE0pLj75faaVEKClEX1TM4AJmmnOh9eimREzLWI=

24
main.go

@ -4,34 +4,22 @@ import (
"os" "os"
"gitea.com/jolheiser/sip/cmd" "gitea.com/jolheiser/sip/cmd"
"gitea.com/jolheiser/sip/config"
"github.com/urfave/cli/v2"
"go.jolheiser.com/beaver" "go.jolheiser.com/beaver"
) )
var Version = "develop" var Version = "develop"
func main() { func main() {
app := cmd.NewApp(Version)
// config loads on init if err := config.Init(); err != nil {
beaver.Fatal(err)
app := cli.NewApp()
app.Name = "Sip"
app.Usage = "Command line tool to interact with Gitea"
app.Version = Version
app.Commands = []*cli.Command{
&cmd.Config,
&cmd.Tokens,
&cmd.Repo,
&cmd.Issues,
&cmd.Pulls,
&cmd.Release,
&cmd.Open,
} }
app.Flags = cmd.Flags
app.EnableBashCompletion = true
err := app.Run(os.Args) err := app.Run(os.Args)
if err != nil { if err != nil {
beaver.Error(err) beaver.Fatal(err)
} }
} }

@ -3,7 +3,7 @@ package sdk
import ( import (
"strings" "strings"
"gitea.com/jolheiser/sip/modules/qualify" "gitea.com/jolheiser/sip/qualify"
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
) )