Refactor: apply new internal structurs (#206)
fix lint fix lint Move print TrackedTimesList to print package Move AbsPathWithExpansion to utils/path.go rename module intern to config Move Subcomands into it's own Packages Split times subcomands into own sourcefiles Split repos subcomands into own sourcefiles Split releases subcomands into own sourcefiles Split pulls subcomands into own sourcefiles Split milestones subcomands into own sourcefiles Split login subcomands into own sourcefiles Split labels subcomands into own sourcefiles split issues subcomands into own sourcefiles mv Move Interactive Login Creation to interact package Move Add Login function to intern/login.go apply from review lint: add description to exported func smal nits Move DetailViews stdout print func to print package Refactor: * Move Config & Login routines into intern package * rename global var in cmd * Move help func to utils Co-authored-by: 6543 <6543@obermui.de> Reviewed-on: https://gitea.com/gitea/tea/pulls/206 Reviewed-by: Norwin <noerw@noreply.gitea.io> Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
This commit is contained in:
parent
9602c149ca
commit
f445ac7521
286
cmd/config.go
286
cmd/config.go
|
@ -1,286 +0,0 @@
|
||||||
// Copyright 2018 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"net/http/cookiejar"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
|
||||||
"code.gitea.io/tea/modules/git"
|
|
||||||
"code.gitea.io/tea/modules/utils"
|
|
||||||
|
|
||||||
"github.com/muesli/termenv"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Login represents a login to a gitea server, you even could add multiple logins for one gitea server
|
|
||||||
type Login struct {
|
|
||||||
Name string `yaml:"name"`
|
|
||||||
URL string `yaml:"url"`
|
|
||||||
Token string `yaml:"token"`
|
|
||||||
Default bool `yaml:"default"`
|
|
||||||
SSHHost string `yaml:"ssh_host"`
|
|
||||||
// optional path to the private key
|
|
||||||
SSHKey string `yaml:"ssh_key"`
|
|
||||||
Insecure bool `yaml:"insecure"`
|
|
||||||
// optional gitea username
|
|
||||||
User string `yaml:"user"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Client returns a client to operate Gitea API
|
|
||||||
func (l *Login) Client() *gitea.Client {
|
|
||||||
httpClient := &http.Client{}
|
|
||||||
if l.Insecure {
|
|
||||||
cookieJar, _ := cookiejar.New(nil)
|
|
||||||
|
|
||||||
httpClient = &http.Client{
|
|
||||||
Jar: cookieJar,
|
|
||||||
Transport: &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := gitea.NewClient(l.URL,
|
|
||||||
gitea.SetToken(l.Token),
|
|
||||||
gitea.SetHTTPClient(httpClient),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
return client
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSSHHost returns SSH host name
|
|
||||||
func (l *Login) GetSSHHost() string {
|
|
||||||
if l.SSHHost != "" {
|
|
||||||
return l.SSHHost
|
|
||||||
}
|
|
||||||
|
|
||||||
u, err := url.Parse(l.URL)
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return u.Hostname()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Config reprensents local configurations
|
|
||||||
type Config struct {
|
|
||||||
Logins []Login `yaml:"logins"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
config Config
|
|
||||||
yamlConfigPath string
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
homeDir, err := utils.Home()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Retrieve home dir failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
dir := filepath.Join(homeDir, ".tea")
|
|
||||||
err = os.MkdirAll(dir, os.ModePerm)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Init tea config dir " + dir + " failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
yamlConfigPath = filepath.Join(dir, "tea.yml")
|
|
||||||
}
|
|
||||||
|
|
||||||
func getGlamourTheme() string {
|
|
||||||
if termenv.HasDarkBackground() {
|
|
||||||
return "dark"
|
|
||||||
}
|
|
||||||
return "light"
|
|
||||||
}
|
|
||||||
|
|
||||||
func getOwnerAndRepo(repoPath, user string) (string, string) {
|
|
||||||
if len(repoPath) == 0 {
|
|
||||||
return "", ""
|
|
||||||
}
|
|
||||||
p := strings.Split(repoPath, "/")
|
|
||||||
if len(p) >= 2 {
|
|
||||||
return p[0], p[1]
|
|
||||||
}
|
|
||||||
return user, repoPath
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDefaultLogin() (*Login, error) {
|
|
||||||
if len(config.Logins) == 0 {
|
|
||||||
return nil, errors.New("No available login")
|
|
||||||
}
|
|
||||||
for _, l := range config.Logins {
|
|
||||||
if l.Default {
|
|
||||||
return &l, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &config.Logins[0], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getLoginByName(name string) *Login {
|
|
||||||
for _, l := range config.Logins {
|
|
||||||
if l.Name == name {
|
|
||||||
return &l
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func addLogin(login Login) error {
|
|
||||||
for _, l := range config.Logins {
|
|
||||||
if l.Name == login.Name {
|
|
||||||
if l.URL == login.URL && l.Token == login.Token {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return errors.New("Login name has already been used")
|
|
||||||
}
|
|
||||||
if l.URL == login.URL && l.Token == login.Token {
|
|
||||||
return errors.New("URL has been added")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
u, err := url.Parse(login.URL)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if login.SSHHost == "" {
|
|
||||||
login.SSHHost = u.Hostname()
|
|
||||||
}
|
|
||||||
config.Logins = append(config.Logins, login)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isFileExist(fileName string) (bool, error) {
|
|
||||||
f, err := os.Stat(fileName)
|
|
||||||
if err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
if f.IsDir() {
|
|
||||||
return false, errors.New("A directory with the same name exists")
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadConfig(ymlPath string) error {
|
|
||||||
exist, _ := isFileExist(ymlPath)
|
|
||||||
if exist {
|
|
||||||
Println("Found config file", ymlPath)
|
|
||||||
bs, err := ioutil.ReadFile(ymlPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = yaml.Unmarshal(bs, &config)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveConfig(ymlPath string) error {
|
|
||||||
bs, err := yaml.Marshal(&config)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return ioutil.WriteFile(ymlPath, bs, 0660)
|
|
||||||
}
|
|
||||||
|
|
||||||
func curGitRepoPath(path string) (*Login, string, error) {
|
|
||||||
var err error
|
|
||||||
var repo *git.TeaRepo
|
|
||||||
if len(path) == 0 {
|
|
||||||
repo, err = git.RepoForWorkdir()
|
|
||||||
} else {
|
|
||||||
repo, err = git.RepoFromPath(path)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", err
|
|
||||||
}
|
|
||||||
gitConfig, err := repo.Config()
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// if no remote
|
|
||||||
if len(gitConfig.Remotes) == 0 {
|
|
||||||
return nil, "", errors.New("No remote(s) found in this Git repository")
|
|
||||||
}
|
|
||||||
|
|
||||||
// if only one remote exists
|
|
||||||
if len(gitConfig.Remotes) >= 1 && len(remoteValue) == 0 {
|
|
||||||
for remote := range gitConfig.Remotes {
|
|
||||||
remoteValue = remote
|
|
||||||
}
|
|
||||||
if len(gitConfig.Remotes) > 1 {
|
|
||||||
// if master branch is present, use it as the default remote
|
|
||||||
masterBranch, ok := gitConfig.Branches["master"]
|
|
||||||
if ok {
|
|
||||||
if len(masterBranch.Remote) > 0 {
|
|
||||||
remoteValue = masterBranch.Remote
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
remoteConfig, ok := gitConfig.Remotes[remoteValue]
|
|
||||||
if !ok || remoteConfig == nil {
|
|
||||||
return nil, "", errors.New("Remote " + remoteValue + " not found in this Git repository")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, l := range config.Logins {
|
|
||||||
for _, u := range remoteConfig.URLs {
|
|
||||||
p, err := git.ParseURL(strings.TrimSpace(u))
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", fmt.Errorf("Git remote URL parse failed: %s", err.Error())
|
|
||||||
}
|
|
||||||
if strings.EqualFold(p.Scheme, "http") || strings.EqualFold(p.Scheme, "https") {
|
|
||||||
if strings.HasPrefix(u, l.URL) {
|
|
||||||
ps := strings.Split(p.Path, "/")
|
|
||||||
path := strings.Join(ps[len(ps)-2:], "/")
|
|
||||||
return &l, strings.TrimSuffix(path, ".git"), nil
|
|
||||||
}
|
|
||||||
} else if strings.EqualFold(p.Scheme, "ssh") {
|
|
||||||
if l.GetSSHHost() == strings.Split(p.Host, ":")[0] {
|
|
||||||
return &l, strings.TrimLeft(strings.TrimSuffix(p.Path, ".git"), "/"), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, "", errors.New("No Gitea login found. You might want to specify --repo (and --login) to work outside of a repository")
|
|
||||||
}
|
|
||||||
|
|
||||||
func getListOptions(ctx *cli.Context) gitea.ListOptions {
|
|
||||||
page := ctx.Int("page")
|
|
||||||
limit := ctx.Int("limit")
|
|
||||||
if limit != 0 && page == 0 {
|
|
||||||
page = 1
|
|
||||||
}
|
|
||||||
return gitea.ListOptions{
|
|
||||||
Page: page,
|
|
||||||
PageSize: limit,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,23 +2,24 @@
|
||||||
// Use of this source code is governed by a MIT-style
|
// Use of this source code is governed by a MIT-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package cmd
|
package flags
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"code.gitea.io/sdk/gitea"
|
||||||
|
|
||||||
"code.gitea.io/tea/modules/utils"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// create global variables for global Flags to simplify
|
// create global variables for global Flags to simplify
|
||||||
// access to the options without requiring cli.Context
|
// access to the options without requiring cli.Context
|
||||||
var (
|
var (
|
||||||
loginValue string
|
// GlobalLoginValue contain value of --login|-l arg
|
||||||
repoValue string
|
GlobalLoginValue string
|
||||||
outputValue string
|
// GlobalRepoValue contain value of --repo|-r arg
|
||||||
remoteValue string
|
GlobalRepoValue string
|
||||||
|
// GlobalOutputValue contain value of --output|-o arg
|
||||||
|
GlobalOutputValue string
|
||||||
|
// GlobalRemoteValue contain value of --remote|-R arg
|
||||||
|
GlobalRemoteValue string
|
||||||
)
|
)
|
||||||
|
|
||||||
// LoginFlag provides flag to specify tea login profile
|
// LoginFlag provides flag to specify tea login profile
|
||||||
|
@ -26,7 +27,7 @@ var LoginFlag = cli.StringFlag{
|
||||||
Name: "login",
|
Name: "login",
|
||||||
Aliases: []string{"l"},
|
Aliases: []string{"l"},
|
||||||
Usage: "Use a different Gitea login. Optional",
|
Usage: "Use a different Gitea login. Optional",
|
||||||
Destination: &loginValue,
|
Destination: &GlobalLoginValue,
|
||||||
}
|
}
|
||||||
|
|
||||||
// RepoFlag provides flag to specify repository
|
// RepoFlag provides flag to specify repository
|
||||||
|
@ -34,7 +35,7 @@ var RepoFlag = cli.StringFlag{
|
||||||
Name: "repo",
|
Name: "repo",
|
||||||
Aliases: []string{"r"},
|
Aliases: []string{"r"},
|
||||||
Usage: "Repository to interact with. Optional",
|
Usage: "Repository to interact with. Optional",
|
||||||
Destination: &repoValue,
|
Destination: &GlobalRepoValue,
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoteFlag provides flag to specify remote repository
|
// RemoteFlag provides flag to specify remote repository
|
||||||
|
@ -42,7 +43,7 @@ var RemoteFlag = cli.StringFlag{
|
||||||
Name: "remote",
|
Name: "remote",
|
||||||
Aliases: []string{"R"},
|
Aliases: []string{"R"},
|
||||||
Usage: "Discover Gitea login from remote. Optional",
|
Usage: "Discover Gitea login from remote. Optional",
|
||||||
Destination: &remoteValue,
|
Destination: &GlobalRemoteValue,
|
||||||
}
|
}
|
||||||
|
|
||||||
// OutputFlag provides flag to specify output type
|
// OutputFlag provides flag to specify output type
|
||||||
|
@ -50,7 +51,7 @@ var OutputFlag = cli.StringFlag{
|
||||||
Name: "output",
|
Name: "output",
|
||||||
Aliases: []string{"o"},
|
Aliases: []string{"o"},
|
||||||
Usage: "Output format. (csv, simple, table, tsv, yaml)",
|
Usage: "Output format. (csv, simple, table, tsv, yaml)",
|
||||||
Destination: &outputValue,
|
Destination: &GlobalOutputValue,
|
||||||
}
|
}
|
||||||
|
|
||||||
// StateFlag provides flag to specify issue/pr state, defaulting to "open"
|
// StateFlag provides flag to specify issue/pr state, defaulting to "open"
|
||||||
|
@ -109,61 +110,15 @@ var IssuePRFlags = append([]cli.Flag{
|
||||||
&PaginationLimitFlag,
|
&PaginationLimitFlag,
|
||||||
}, AllDefaultFlags...)
|
}, AllDefaultFlags...)
|
||||||
|
|
||||||
// initCommand returns repository and *Login based on flags
|
// GetListOptions return ListOptions based on PaginationFlags
|
||||||
func initCommand() (*Login, string, string) {
|
func GetListOptions(ctx *cli.Context) gitea.ListOptions {
|
||||||
var login *Login
|
page := ctx.Int("page")
|
||||||
|
limit := ctx.Int("limit")
|
||||||
err := loadConfig(yamlConfigPath)
|
if limit != 0 && page == 0 {
|
||||||
if err != nil {
|
page = 1
|
||||||
log.Fatal("load config file failed ", yamlConfigPath)
|
|
||||||
}
|
}
|
||||||
|
return gitea.ListOptions{
|
||||||
if login, err = getDefaultLogin(); err != nil {
|
Page: page,
|
||||||
log.Fatal(err.Error())
|
PageSize: limit,
|
||||||
}
|
|
||||||
|
|
||||||
exist, err := utils.PathExists(repoValue)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
if exist || len(repoValue) == 0 {
|
|
||||||
login, repoValue, err = curGitRepoPath(repoValue)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err.Error())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if loginValue != "" {
|
|
||||||
login = getLoginByName(loginValue)
|
|
||||||
if login == nil {
|
|
||||||
log.Fatal("Login name " + loginValue + " does not exist")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
owner, repo := getOwnerAndRepo(repoValue, login.User)
|
|
||||||
return login, owner, repo
|
|
||||||
}
|
|
||||||
|
|
||||||
// initCommandLoginOnly return *Login based on flags
|
|
||||||
func initCommandLoginOnly() *Login {
|
|
||||||
err := loadConfig(yamlConfigPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("load config file failed ", yamlConfigPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
var login *Login
|
|
||||||
if loginValue == "" {
|
|
||||||
login, err = getDefaultLogin()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
login = getLoginByName(loginValue)
|
|
||||||
if login == nil {
|
|
||||||
log.Fatal("Login name " + loginValue + " does not exist")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return login
|
|
||||||
}
|
|
200
cmd/issues.go
200
cmd/issues.go
|
@ -5,12 +5,12 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"code.gitea.io/tea/cmd/flags"
|
||||||
"log"
|
"code.gitea.io/tea/cmd/issues"
|
||||||
"strconv"
|
"code.gitea.io/tea/modules/config"
|
||||||
|
"code.gitea.io/tea/modules/print"
|
||||||
|
"code.gitea.io/tea/modules/utils"
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
|
||||||
"github.com/charmbracelet/glamour"
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -22,34 +22,25 @@ var CmdIssues = cli.Command{
|
||||||
ArgsUsage: "[<issue index>]",
|
ArgsUsage: "[<issue index>]",
|
||||||
Action: runIssues,
|
Action: runIssues,
|
||||||
Subcommands: []*cli.Command{
|
Subcommands: []*cli.Command{
|
||||||
&CmdIssuesList,
|
&issues.CmdIssuesList,
|
||||||
&CmdIssuesCreate,
|
&issues.CmdIssuesCreate,
|
||||||
&CmdIssuesReopen,
|
&issues.CmdIssuesReopen,
|
||||||
&CmdIssuesClose,
|
&issues.CmdIssuesClose,
|
||||||
},
|
},
|
||||||
Flags: IssuePRFlags,
|
Flags: flags.IssuePRFlags,
|
||||||
}
|
|
||||||
|
|
||||||
// CmdIssuesList represents a sub command of issues to list issues
|
|
||||||
var CmdIssuesList = cli.Command{
|
|
||||||
Name: "ls",
|
|
||||||
Usage: "List issues of the repository",
|
|
||||||
Description: `List issues of the repository`,
|
|
||||||
Action: runIssuesList,
|
|
||||||
Flags: IssuePRFlags,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runIssues(ctx *cli.Context) error {
|
func runIssues(ctx *cli.Context) error {
|
||||||
if ctx.Args().Len() == 1 {
|
if ctx.Args().Len() == 1 {
|
||||||
return runIssueDetail(ctx, ctx.Args().First())
|
return runIssueDetail(ctx.Args().First())
|
||||||
}
|
}
|
||||||
return runIssuesList(ctx)
|
return issues.RunIssuesList(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runIssueDetail(ctx *cli.Context, index string) error {
|
func runIssueDetail(index string) error {
|
||||||
login, owner, repo := initCommand()
|
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||||
|
|
||||||
idx, err := argToIndex(index)
|
idx, err := utils.ArgToIndex(index)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -57,165 +48,6 @@ func runIssueDetail(ctx *cli.Context, index string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
print.IssueDetails(issue)
|
||||||
in := fmt.Sprintf("# #%d %s (%s)\n%s created %s\n\n%s\n", issue.Index,
|
|
||||||
issue.Title,
|
|
||||||
issue.State,
|
|
||||||
issue.Poster.UserName,
|
|
||||||
issue.Created.Format("2006-01-02 15:04:05"),
|
|
||||||
issue.Body,
|
|
||||||
)
|
|
||||||
out, err := glamour.Render(in, getGlamourTheme())
|
|
||||||
fmt.Print(out)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runIssuesList(ctx *cli.Context) error {
|
|
||||||
login, owner, repo := initCommand()
|
|
||||||
|
|
||||||
state := gitea.StateOpen
|
|
||||||
switch ctx.String("state") {
|
|
||||||
case "all":
|
|
||||||
state = gitea.StateAll
|
|
||||||
case "open":
|
|
||||||
state = gitea.StateOpen
|
|
||||||
case "closed":
|
|
||||||
state = gitea.StateClosed
|
|
||||||
}
|
|
||||||
|
|
||||||
issues, _, err := login.Client().ListRepoIssues(owner, repo, gitea.ListIssueOption{
|
|
||||||
ListOptions: getListOptions(ctx),
|
|
||||||
State: state,
|
|
||||||
Type: gitea.IssueTypeIssue,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
headers := []string{
|
|
||||||
"Index",
|
|
||||||
"Title",
|
|
||||||
"State",
|
|
||||||
"Author",
|
|
||||||
"Milestone",
|
|
||||||
"Updated",
|
|
||||||
}
|
|
||||||
|
|
||||||
var values [][]string
|
|
||||||
|
|
||||||
if len(issues) == 0 {
|
|
||||||
Output(outputValue, headers, values)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, issue := range issues {
|
|
||||||
author := issue.Poster.FullName
|
|
||||||
if len(author) == 0 {
|
|
||||||
author = issue.Poster.UserName
|
|
||||||
}
|
|
||||||
mile := ""
|
|
||||||
if issue.Milestone != nil {
|
|
||||||
mile = issue.Milestone.Title
|
|
||||||
}
|
|
||||||
values = append(
|
|
||||||
values,
|
|
||||||
[]string{
|
|
||||||
strconv.FormatInt(issue.Index, 10),
|
|
||||||
issue.Title,
|
|
||||||
string(issue.State),
|
|
||||||
author,
|
|
||||||
mile,
|
|
||||||
issue.Updated.Format("2006-01-02 15:04:05"),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Output(outputValue, headers, values)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdIssuesCreate represents a sub command of issues to create issue
|
|
||||||
var CmdIssuesCreate = cli.Command{
|
|
||||||
Name: "create",
|
|
||||||
Usage: "Create an issue on repository",
|
|
||||||
Description: `Create an issue on repository`,
|
|
||||||
Action: runIssuesCreate,
|
|
||||||
Flags: append([]cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "title",
|
|
||||||
Aliases: []string{"t"},
|
|
||||||
Usage: "issue title to create",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "body",
|
|
||||||
Aliases: []string{"b"},
|
|
||||||
Usage: "issue body to create",
|
|
||||||
},
|
|
||||||
}, LoginRepoFlags...),
|
|
||||||
}
|
|
||||||
|
|
||||||
func runIssuesCreate(ctx *cli.Context) error {
|
|
||||||
login, owner, repo := initCommand()
|
|
||||||
|
|
||||||
_, _, err := login.Client().CreateIssue(owner, repo, gitea.CreateIssueOption{
|
|
||||||
Title: ctx.String("title"),
|
|
||||||
Body: ctx.String("body"),
|
|
||||||
// TODO:
|
|
||||||
//Assignee string `json:"assignee"`
|
|
||||||
//Assignees []string `json:"assignees"`
|
|
||||||
//Deadline *time.Time `json:"due_date"`
|
|
||||||
//Milestone int64 `json:"milestone"`
|
|
||||||
//Labels []int64 `json:"labels"`
|
|
||||||
//Closed bool `json:"closed"`
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdIssuesReopen represents a sub command of issues to open an issue
|
|
||||||
var CmdIssuesReopen = cli.Command{
|
|
||||||
Name: "reopen",
|
|
||||||
Aliases: []string{"open"},
|
|
||||||
Usage: "Change state of an issue to 'open'",
|
|
||||||
Description: `Change state of an issue to 'open'`,
|
|
||||||
ArgsUsage: "<issue index>",
|
|
||||||
Action: func(ctx *cli.Context) error {
|
|
||||||
var s = gitea.StateOpen
|
|
||||||
return editIssueState(ctx, gitea.EditIssueOption{State: &s})
|
|
||||||
},
|
|
||||||
Flags: AllDefaultFlags,
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdIssuesClose represents a sub command of issues to close an issue
|
|
||||||
var CmdIssuesClose = cli.Command{
|
|
||||||
Name: "close",
|
|
||||||
Usage: "Change state of an issue to 'closed'",
|
|
||||||
Description: `Change state of an issue to 'closed'`,
|
|
||||||
ArgsUsage: "<issue index>",
|
|
||||||
Action: func(ctx *cli.Context) error {
|
|
||||||
var s = gitea.StateClosed
|
|
||||||
return editIssueState(ctx, gitea.EditIssueOption{State: &s})
|
|
||||||
},
|
|
||||||
Flags: AllDefaultFlags,
|
|
||||||
}
|
|
||||||
|
|
||||||
// editIssueState abstracts the arg parsing to edit the given issue
|
|
||||||
func editIssueState(ctx *cli.Context, opts gitea.EditIssueOption) error {
|
|
||||||
login, owner, repo := initCommand()
|
|
||||||
if ctx.Args().Len() == 0 {
|
|
||||||
log.Fatal(ctx.Command.ArgsUsage)
|
|
||||||
}
|
|
||||||
|
|
||||||
index, err := argToIndex(ctx.Args().First())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, err = login.Client().EditIssue(owner, repo, index, opts)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package issues
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"code.gitea.io/tea/cmd/flags"
|
||||||
|
"code.gitea.io/tea/modules/config"
|
||||||
|
"code.gitea.io/tea/modules/utils"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdIssuesClose represents a sub command of issues to close an issue
|
||||||
|
var CmdIssuesClose = cli.Command{
|
||||||
|
Name: "close",
|
||||||
|
Usage: "Change state of an issue to 'closed'",
|
||||||
|
Description: `Change state of an issue to 'closed'`,
|
||||||
|
ArgsUsage: "<issue index>",
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
var s = gitea.StateClosed
|
||||||
|
return editIssueState(ctx, gitea.EditIssueOption{State: &s})
|
||||||
|
},
|
||||||
|
Flags: flags.AllDefaultFlags,
|
||||||
|
}
|
||||||
|
|
||||||
|
// editIssueState abstracts the arg parsing to edit the given issue
|
||||||
|
func editIssueState(ctx *cli.Context, opts gitea.EditIssueOption) error {
|
||||||
|
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||||
|
if ctx.Args().Len() == 0 {
|
||||||
|
log.Fatal(ctx.Command.ArgsUsage)
|
||||||
|
}
|
||||||
|
|
||||||
|
index, err := utils.ArgToIndex(ctx.Args().First())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = login.Client().EditIssue(owner, repo, index, opts)
|
||||||
|
// TODO: print (short)IssueDetails
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package issues
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"code.gitea.io/tea/cmd/flags"
|
||||||
|
"code.gitea.io/tea/modules/config"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdIssuesCreate represents a sub command of issues to create issue
|
||||||
|
var CmdIssuesCreate = cli.Command{
|
||||||
|
Name: "create",
|
||||||
|
Usage: "Create an issue on repository",
|
||||||
|
Description: `Create an issue on repository`,
|
||||||
|
Action: runIssuesCreate,
|
||||||
|
Flags: append([]cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "title",
|
||||||
|
Aliases: []string{"t"},
|
||||||
|
Usage: "issue title to create",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "body",
|
||||||
|
Aliases: []string{"b"},
|
||||||
|
Usage: "issue body to create",
|
||||||
|
},
|
||||||
|
}, flags.LoginRepoFlags...),
|
||||||
|
}
|
||||||
|
|
||||||
|
func runIssuesCreate(ctx *cli.Context) error {
|
||||||
|
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||||
|
|
||||||
|
_, _, err := login.Client().CreateIssue(owner, repo, gitea.CreateIssueOption{
|
||||||
|
Title: ctx.String("title"),
|
||||||
|
Body: ctx.String("body"),
|
||||||
|
// TODO:
|
||||||
|
//Assignee string `json:"assignee"`
|
||||||
|
//Assignees []string `json:"assignees"`
|
||||||
|
//Deadline *time.Time `json:"due_date"`
|
||||||
|
//Milestone int64 `json:"milestone"`
|
||||||
|
//Labels []int64 `json:"labels"`
|
||||||
|
//Closed bool `json:"closed"`
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Print IssueDetails
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package issues
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"code.gitea.io/tea/cmd/flags"
|
||||||
|
"code.gitea.io/tea/modules/config"
|
||||||
|
"code.gitea.io/tea/modules/print"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdIssuesList represents a sub command of issues to list issues
|
||||||
|
var CmdIssuesList = cli.Command{
|
||||||
|
Name: "ls",
|
||||||
|
Usage: "List issues of the repository",
|
||||||
|
Description: `List issues of the repository`,
|
||||||
|
Action: RunIssuesList,
|
||||||
|
Flags: flags.IssuePRFlags,
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunIssuesList list issues
|
||||||
|
func RunIssuesList(ctx *cli.Context) error {
|
||||||
|
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||||
|
|
||||||
|
state := gitea.StateOpen
|
||||||
|
switch ctx.String("state") {
|
||||||
|
case "all":
|
||||||
|
state = gitea.StateAll
|
||||||
|
case "open":
|
||||||
|
state = gitea.StateOpen
|
||||||
|
case "closed":
|
||||||
|
state = gitea.StateClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
issues, _, err := login.Client().ListRepoIssues(owner, repo, gitea.ListIssueOption{
|
||||||
|
ListOptions: flags.GetListOptions(ctx),
|
||||||
|
State: state,
|
||||||
|
Type: gitea.IssueTypeIssue,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := []string{
|
||||||
|
"Index",
|
||||||
|
"Title",
|
||||||
|
"State",
|
||||||
|
"Author",
|
||||||
|
"Milestone",
|
||||||
|
"Updated",
|
||||||
|
}
|
||||||
|
|
||||||
|
var values [][]string
|
||||||
|
|
||||||
|
if len(issues) == 0 {
|
||||||
|
print.OutputList(flags.GlobalOutputValue, headers, values)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, issue := range issues {
|
||||||
|
author := issue.Poster.FullName
|
||||||
|
if len(author) == 0 {
|
||||||
|
author = issue.Poster.UserName
|
||||||
|
}
|
||||||
|
mile := ""
|
||||||
|
if issue.Milestone != nil {
|
||||||
|
mile = issue.Milestone.Title
|
||||||
|
}
|
||||||
|
values = append(
|
||||||
|
values,
|
||||||
|
[]string{
|
||||||
|
strconv.FormatInt(issue.Index, 10),
|
||||||
|
issue.Title,
|
||||||
|
string(issue.State),
|
||||||
|
author,
|
||||||
|
mile,
|
||||||
|
issue.Updated.Format("2006-01-02 15:04:05"),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
print.OutputList(flags.GlobalOutputValue, headers, values)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package issues
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.gitea.io/tea/cmd/flags"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdIssuesReopen represents a sub command of issues to open an issue
|
||||||
|
var CmdIssuesReopen = cli.Command{
|
||||||
|
Name: "reopen",
|
||||||
|
Aliases: []string{"open"},
|
||||||
|
Usage: "Change state of an issue to 'open'",
|
||||||
|
Description: `Change state of an issue to 'open'`,
|
||||||
|
ArgsUsage: "<issue index>",
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
var s = gitea.StateOpen
|
||||||
|
return editIssueState(ctx, gitea.EditIssueOption{State: &s})
|
||||||
|
},
|
||||||
|
Flags: flags.AllDefaultFlags,
|
||||||
|
}
|
205
cmd/labels.go
205
cmd/labels.go
|
@ -5,15 +5,17 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
|
"code.gitea.io/tea/cmd/flags"
|
||||||
|
"code.gitea.io/tea/cmd/labels"
|
||||||
|
"code.gitea.io/tea/modules/config"
|
||||||
|
"code.gitea.io/tea/modules/print"
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
|
|
||||||
"github.com/muesli/termenv"
|
"github.com/muesli/termenv"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
@ -25,9 +27,9 @@ var CmdLabels = cli.Command{
|
||||||
Description: `Manage issue labels`,
|
Description: `Manage issue labels`,
|
||||||
Action: runLabels,
|
Action: runLabels,
|
||||||
Subcommands: []*cli.Command{
|
Subcommands: []*cli.Command{
|
||||||
&CmdLabelCreate,
|
&labels.CmdLabelCreate,
|
||||||
&CmdLabelUpdate,
|
&labels.CmdLabelUpdate,
|
||||||
&CmdLabelDelete,
|
&labels.CmdLabelDelete,
|
||||||
},
|
},
|
||||||
Flags: append([]cli.Flag{
|
Flags: append([]cli.Flag{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
|
@ -35,13 +37,13 @@ var CmdLabels = cli.Command{
|
||||||
Aliases: []string{"s"},
|
Aliases: []string{"s"},
|
||||||
Usage: "Save all the labels as a file",
|
Usage: "Save all the labels as a file",
|
||||||
},
|
},
|
||||||
&PaginationPageFlag,
|
&flags.PaginationPageFlag,
|
||||||
&PaginationLimitFlag,
|
&flags.PaginationLimitFlag,
|
||||||
}, AllDefaultFlags...),
|
}, flags.AllDefaultFlags...),
|
||||||
}
|
}
|
||||||
|
|
||||||
func runLabels(ctx *cli.Context) error {
|
func runLabels(ctx *cli.Context) error {
|
||||||
login, owner, repo := initCommand()
|
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||||
|
|
||||||
headers := []string{
|
headers := []string{
|
||||||
"Index",
|
"Index",
|
||||||
|
@ -52,13 +54,13 @@ func runLabels(ctx *cli.Context) error {
|
||||||
|
|
||||||
var values [][]string
|
var values [][]string
|
||||||
|
|
||||||
labels, _, err := login.Client().ListRepoLabels(owner, repo, gitea.ListLabelsOptions{ListOptions: getListOptions(ctx)})
|
labels, _, err := login.Client().ListRepoLabels(owner, repo, gitea.ListLabelsOptions{ListOptions: flags.GetListOptions(ctx)})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(labels) == 0 {
|
if len(labels) == 0 {
|
||||||
Output(outputValue, headers, values)
|
print.OutputList(flags.GlobalOutputValue, headers, values)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,184 +91,7 @@ func runLabels(ctx *cli.Context) error {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Output(outputValue, headers, values)
|
print.OutputList(flags.GlobalOutputValue, headers, values)
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdLabelCreate represents a sub command of labels to create label.
|
|
||||||
var CmdLabelCreate = cli.Command{
|
|
||||||
Name: "create",
|
|
||||||
Usage: "Create a label",
|
|
||||||
Description: `Create a label`,
|
|
||||||
Action: runLabelCreate,
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "name",
|
|
||||||
Usage: "label name",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "color",
|
|
||||||
Usage: "label color value",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "description",
|
|
||||||
Usage: "label description",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "file",
|
|
||||||
Usage: "indicate a label file",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func splitLabelLine(line string) (string, string, string) {
|
|
||||||
fields := strings.SplitN(line, ";", 2)
|
|
||||||
var color, name, description string
|
|
||||||
if len(fields) < 1 {
|
|
||||||
return "", "", ""
|
|
||||||
} else if len(fields) >= 2 {
|
|
||||||
description = strings.TrimSpace(fields[1])
|
|
||||||
}
|
|
||||||
fields = strings.Fields(fields[0])
|
|
||||||
if len(fields) <= 0 {
|
|
||||||
return "", "", ""
|
|
||||||
}
|
|
||||||
color = fields[0]
|
|
||||||
if len(fields) == 2 {
|
|
||||||
name = fields[1]
|
|
||||||
} else if len(fields) > 2 {
|
|
||||||
name = strings.Join(fields[1:], " ")
|
|
||||||
}
|
|
||||||
return color, name, description
|
|
||||||
}
|
|
||||||
|
|
||||||
func runLabelCreate(ctx *cli.Context) error {
|
|
||||||
login, owner, repo := initCommand()
|
|
||||||
|
|
||||||
labelFile := ctx.String("file")
|
|
||||||
var err error
|
|
||||||
if len(labelFile) == 0 {
|
|
||||||
_, _, err = login.Client().CreateLabel(owner, repo, gitea.CreateLabelOption{
|
|
||||||
Name: ctx.String("name"),
|
|
||||||
Color: ctx.String("color"),
|
|
||||||
Description: ctx.String("description"),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
f, err := os.Open(labelFile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
scanner := bufio.NewScanner(f)
|
|
||||||
var i = 1
|
|
||||||
// FIXME: if Gitea's API support create multiple labels once, we should move to that API.
|
|
||||||
for scanner.Scan() {
|
|
||||||
line := scanner.Text()
|
|
||||||
color, name, description := splitLabelLine(line)
|
|
||||||
if color == "" || name == "" {
|
|
||||||
log.Printf("Line %d ignored because lack of enough fields: %s\n", i, line)
|
|
||||||
} else {
|
|
||||||
_, _, err = login.Client().CreateLabel(owner, repo, gitea.CreateLabelOption{
|
|
||||||
Name: name,
|
|
||||||
Color: color,
|
|
||||||
Description: description,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdLabelUpdate represents a sub command of labels to update label.
|
|
||||||
var CmdLabelUpdate = cli.Command{
|
|
||||||
Name: "update",
|
|
||||||
Usage: "Update a label",
|
|
||||||
Description: `Update a label`,
|
|
||||||
Action: runLabelUpdate,
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.IntFlag{
|
|
||||||
Name: "id",
|
|
||||||
Usage: "label id",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "name",
|
|
||||||
Usage: "label name",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "color",
|
|
||||||
Usage: "label color value",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "description",
|
|
||||||
Usage: "label description",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func runLabelUpdate(ctx *cli.Context) error {
|
|
||||||
login, owner, repo := initCommand()
|
|
||||||
|
|
||||||
id := ctx.Int64("id")
|
|
||||||
var pName, pColor, pDescription *string
|
|
||||||
name := ctx.String("name")
|
|
||||||
if name != "" {
|
|
||||||
pName = &name
|
|
||||||
}
|
|
||||||
|
|
||||||
color := ctx.String("color")
|
|
||||||
if color != "" {
|
|
||||||
pColor = &color
|
|
||||||
}
|
|
||||||
|
|
||||||
description := ctx.String("description")
|
|
||||||
if description != "" {
|
|
||||||
pDescription = &description
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
_, _, err = login.Client().EditLabel(owner, repo, id, gitea.EditLabelOption{
|
|
||||||
Name: pName,
|
|
||||||
Color: pColor,
|
|
||||||
Description: pDescription,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdLabelDelete represents a sub command of labels to delete label.
|
|
||||||
var CmdLabelDelete = cli.Command{
|
|
||||||
Name: "delete",
|
|
||||||
Usage: "Delete a label",
|
|
||||||
Description: `Delete a label`,
|
|
||||||
Action: runLabelDelete,
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.IntFlag{
|
|
||||||
Name: "id",
|
|
||||||
Usage: "label id",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func runLabelDelete(ctx *cli.Context) error {
|
|
||||||
login, owner, repo := initCommand()
|
|
||||||
|
|
||||||
_, err := login.Client().DeleteLabel(owner, repo, ctx.Int64("id"))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package labels
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/tea/cmd/flags"
|
||||||
|
"code.gitea.io/tea/modules/config"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdLabelCreate represents a sub command of labels to create label.
|
||||||
|
var CmdLabelCreate = cli.Command{
|
||||||
|
Name: "create",
|
||||||
|
Usage: "Create a label",
|
||||||
|
Description: `Create a label`,
|
||||||
|
Action: runLabelCreate,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "name",
|
||||||
|
Usage: "label name",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "color",
|
||||||
|
Usage: "label color value",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "description",
|
||||||
|
Usage: "label description",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "file",
|
||||||
|
Usage: "indicate a label file",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func runLabelCreate(ctx *cli.Context) error {
|
||||||
|
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||||
|
|
||||||
|
labelFile := ctx.String("file")
|
||||||
|
var err error
|
||||||
|
if len(labelFile) == 0 {
|
||||||
|
_, _, err = login.Client().CreateLabel(owner, repo, gitea.CreateLabelOption{
|
||||||
|
Name: ctx.String("name"),
|
||||||
|
Color: ctx.String("color"),
|
||||||
|
Description: ctx.String("description"),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
f, err := os.Open(labelFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
var i = 1
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
color, name, description := splitLabelLine(line)
|
||||||
|
if color == "" || name == "" {
|
||||||
|
log.Printf("Line %d ignored because lack of enough fields: %s\n", i, line)
|
||||||
|
} else {
|
||||||
|
_, _, err = login.Client().CreateLabel(owner, repo, gitea.CreateLabelOption{
|
||||||
|
Name: name,
|
||||||
|
Color: color,
|
||||||
|
Description: description,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitLabelLine(line string) (string, string, string) {
|
||||||
|
fields := strings.SplitN(line, ";", 2)
|
||||||
|
var color, name, description string
|
||||||
|
if len(fields) < 1 {
|
||||||
|
return "", "", ""
|
||||||
|
} else if len(fields) >= 2 {
|
||||||
|
description = strings.TrimSpace(fields[1])
|
||||||
|
}
|
||||||
|
fields = strings.Fields(fields[0])
|
||||||
|
if len(fields) <= 0 {
|
||||||
|
return "", "", ""
|
||||||
|
}
|
||||||
|
color = fields[0]
|
||||||
|
if len(fields) == 2 {
|
||||||
|
name = fields[1]
|
||||||
|
} else if len(fields) > 2 {
|
||||||
|
name = strings.Join(fields[1:], " ")
|
||||||
|
}
|
||||||
|
return color, name, description
|
||||||
|
}
|
|
@ -2,7 +2,7 @@
|
||||||
// Use of this source code is governed by a MIT-style
|
// Use of this source code is governed by a MIT-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package cmd
|
package labels
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
|
@ -0,0 +1,39 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package labels
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"code.gitea.io/tea/cmd/flags"
|
||||||
|
"code.gitea.io/tea/modules/config"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdLabelDelete represents a sub command of labels to delete label.
|
||||||
|
var CmdLabelDelete = cli.Command{
|
||||||
|
Name: "delete",
|
||||||
|
Usage: "Delete a label",
|
||||||
|
Description: `Delete a label`,
|
||||||
|
Action: runLabelDelete,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.IntFlag{
|
||||||
|
Name: "id",
|
||||||
|
Usage: "label id",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func runLabelDelete(ctx *cli.Context) error {
|
||||||
|
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||||
|
|
||||||
|
_, err := login.Client().DeleteLabel(owner, repo, ctx.Int64("id"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package labels
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"code.gitea.io/tea/cmd/flags"
|
||||||
|
"code.gitea.io/tea/modules/config"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdLabelUpdate represents a sub command of labels to update label.
|
||||||
|
var CmdLabelUpdate = cli.Command{
|
||||||
|
Name: "update",
|
||||||
|
Usage: "Update a label",
|
||||||
|
Description: `Update a label`,
|
||||||
|
Action: runLabelUpdate,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.IntFlag{
|
||||||
|
Name: "id",
|
||||||
|
Usage: "label id",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "name",
|
||||||
|
Usage: "label name",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "color",
|
||||||
|
Usage: "label color value",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "description",
|
||||||
|
Usage: "label description",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func runLabelUpdate(ctx *cli.Context) error {
|
||||||
|
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||||
|
|
||||||
|
id := ctx.Int64("id")
|
||||||
|
var pName, pColor, pDescription *string
|
||||||
|
name := ctx.String("name")
|
||||||
|
if name != "" {
|
||||||
|
pName = &name
|
||||||
|
}
|
||||||
|
|
||||||
|
color := ctx.String("color")
|
||||||
|
if color != "" {
|
||||||
|
pColor = &color
|
||||||
|
}
|
||||||
|
|
||||||
|
description := ctx.String("description")
|
||||||
|
if description != "" {
|
||||||
|
pDescription = &description
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
_, _, err = login.Client().EditLabel(owner, repo, id, gitea.EditLabelOption{
|
||||||
|
Name: pName,
|
||||||
|
Color: pColor,
|
||||||
|
Description: pDescription,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
349
cmd/login.go
349
cmd/login.go
|
@ -5,19 +5,8 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"code.gitea.io/tea/cmd/login"
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"net/http/cookiejar"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
|
||||||
|
|
||||||
"github.com/skratchdot/open-golang/open"
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -26,337 +15,11 @@ var CmdLogin = cli.Command{
|
||||||
Name: "login",
|
Name: "login",
|
||||||
Usage: "Log in to a Gitea server",
|
Usage: "Log in to a Gitea server",
|
||||||
Description: `Log in to a Gitea server`,
|
Description: `Log in to a Gitea server`,
|
||||||
Action: runLoginAddInteractive,
|
Action: login.RunLoginAddInteractive, // TODO show list if no arg & detail if login as arg
|
||||||
Subcommands: []*cli.Command{
|
Subcommands: []*cli.Command{
|
||||||
&cmdLoginList,
|
&login.CmdLoginList,
|
||||||
&cmdLoginAdd,
|
&login.CmdLoginAdd,
|
||||||
&cmdLoginEdit,
|
&login.CmdLoginEdit,
|
||||||
&cmdLoginSetDefault,
|
&login.CmdLoginSetDefault,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// cmdLoginEdit represents to login a gitea server.
|
|
||||||
var cmdLoginEdit = cli.Command{
|
|
||||||
Name: "edit",
|
|
||||||
Usage: "Edit Gitea logins",
|
|
||||||
Description: `Edit Gitea logins`,
|
|
||||||
Action: runLoginEdit,
|
|
||||||
Flags: []cli.Flag{&OutputFlag},
|
|
||||||
}
|
|
||||||
|
|
||||||
func runLoginEdit(ctx *cli.Context) error {
|
|
||||||
return open.Start(yamlConfigPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// cmdLoginSetDefault represents to login a gitea server.
|
|
||||||
var cmdLoginSetDefault = cli.Command{
|
|
||||||
Name: "default",
|
|
||||||
Usage: "Get or Set Default Login",
|
|
||||||
Description: `Get or Set Default Login`,
|
|
||||||
ArgsUsage: "<Login>",
|
|
||||||
Action: runLoginSetDefault,
|
|
||||||
Flags: []cli.Flag{&OutputFlag},
|
|
||||||
}
|
|
||||||
|
|
||||||
func runLoginSetDefault(ctx *cli.Context) error {
|
|
||||||
if err := loadConfig(yamlConfigPath); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if ctx.Args().Len() == 0 {
|
|
||||||
l, err := getDefaultLogin()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Printf("Default Login: %s\n", l.Name)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
loginExist := false
|
|
||||||
for i := range config.Logins {
|
|
||||||
config.Logins[i].Default = false
|
|
||||||
if config.Logins[i].Name == ctx.Args().First() {
|
|
||||||
config.Logins[i].Default = true
|
|
||||||
loginExist = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !loginExist {
|
|
||||||
return fmt.Errorf("login '%s' not found", ctx.Args().First())
|
|
||||||
}
|
|
||||||
|
|
||||||
return saveConfig(yamlConfigPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdLogin represents to login a gitea server.
|
|
||||||
var cmdLoginAdd = cli.Command{
|
|
||||||
Name: "add",
|
|
||||||
Usage: "Add a Gitea login",
|
|
||||||
Description: `Add a Gitea login`,
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "name",
|
|
||||||
Aliases: []string{"n"},
|
|
||||||
Usage: "Login name",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "url",
|
|
||||||
Aliases: []string{"u"},
|
|
||||||
Value: "https://try.gitea.io",
|
|
||||||
EnvVars: []string{"GITEA_SERVER_URL"},
|
|
||||||
Usage: "Server URL",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "token",
|
|
||||||
Aliases: []string{"t"},
|
|
||||||
Value: "",
|
|
||||||
EnvVars: []string{"GITEA_SERVER_TOKEN"},
|
|
||||||
Usage: "Access token. Can be obtained from Settings > Applications",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "user",
|
|
||||||
Value: "",
|
|
||||||
EnvVars: []string{"GITEA_SERVER_USER"},
|
|
||||||
Usage: "User for basic auth (will create token)",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "password",
|
|
||||||
Aliases: []string{"pwd"},
|
|
||||||
Value: "",
|
|
||||||
EnvVars: []string{"GITEA_SERVER_PASSWORD"},
|
|
||||||
Usage: "Password for basic auth (will create token)",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "ssh-key",
|
|
||||||
Aliases: []string{"s"},
|
|
||||||
Usage: "Path to a SSH key to use for pull/push operations",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "insecure",
|
|
||||||
Aliases: []string{"i"},
|
|
||||||
Usage: "Disable TLS verification",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Action: runLoginAdd,
|
|
||||||
}
|
|
||||||
|
|
||||||
func runLoginAdd(ctx *cli.Context) error {
|
|
||||||
return runLoginAddMain(
|
|
||||||
ctx.String("name"),
|
|
||||||
ctx.String("token"),
|
|
||||||
ctx.String("user"),
|
|
||||||
ctx.String("password"),
|
|
||||||
ctx.String("ssh-key"),
|
|
||||||
ctx.String("url"),
|
|
||||||
ctx.Bool("insecure"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func runLoginAddInteractive(ctx *cli.Context) error {
|
|
||||||
var stdin, name, token, user, passwd, sshKey, giteaURL string
|
|
||||||
var insecure = false
|
|
||||||
|
|
||||||
fmt.Print("URL of Gitea instance: ")
|
|
||||||
if _, err := fmt.Scanln(&stdin); err != nil {
|
|
||||||
stdin = ""
|
|
||||||
}
|
|
||||||
giteaURL = strings.TrimSpace(stdin)
|
|
||||||
if len(giteaURL) == 0 {
|
|
||||||
fmt.Println("URL is required!")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
parsedURL, err := url.Parse(giteaURL)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
name = strings.ReplaceAll(strings.Title(parsedURL.Host), ".", "")
|
|
||||||
|
|
||||||
fmt.Print("Name of new Login [" + name + "]: ")
|
|
||||||
if _, err := fmt.Scanln(&stdin); err != nil {
|
|
||||||
stdin = ""
|
|
||||||
}
|
|
||||||
if len(strings.TrimSpace(stdin)) != 0 {
|
|
||||||
name = strings.TrimSpace(stdin)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Print("Do you have a token [Yes/no]: ")
|
|
||||||
if _, err := fmt.Scanln(&stdin); err != nil {
|
|
||||||
stdin = ""
|
|
||||||
}
|
|
||||||
if len(stdin) != 0 && strings.ToLower(stdin[:1]) == "n" {
|
|
||||||
fmt.Print("Username: ")
|
|
||||||
if _, err := fmt.Scanln(&stdin); err != nil {
|
|
||||||
stdin = ""
|
|
||||||
}
|
|
||||||
user = strings.TrimSpace(stdin)
|
|
||||||
|
|
||||||
fmt.Print("Password: ")
|
|
||||||
if _, err := fmt.Scanln(&stdin); err != nil {
|
|
||||||
stdin = ""
|
|
||||||
}
|
|
||||||
passwd = strings.TrimSpace(stdin)
|
|
||||||
} else {
|
|
||||||
fmt.Print("Token: ")
|
|
||||||
if _, err := fmt.Scanln(&stdin); err != nil {
|
|
||||||
stdin = ""
|
|
||||||
}
|
|
||||||
token = strings.TrimSpace(stdin)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Print("Set Optional settings [yes/No]: ")
|
|
||||||
if _, err := fmt.Scanln(&stdin); err != nil {
|
|
||||||
stdin = ""
|
|
||||||
}
|
|
||||||
if len(stdin) != 0 && strings.ToLower(stdin[:1]) == "y" {
|
|
||||||
fmt.Print("SSH Key Path: ")
|
|
||||||
if _, err := fmt.Scanln(&stdin); err != nil {
|
|
||||||
stdin = ""
|
|
||||||
}
|
|
||||||
sshKey = strings.TrimSpace(stdin)
|
|
||||||
|
|
||||||
fmt.Print("Allow Insecure connections [yes/No]: ")
|
|
||||||
if _, err := fmt.Scanln(&stdin); err != nil {
|
|
||||||
stdin = ""
|
|
||||||
}
|
|
||||||
insecure = len(stdin) != 0 && strings.ToLower(stdin[:1]) == "y"
|
|
||||||
}
|
|
||||||
|
|
||||||
return runLoginAddMain(name, token, user, passwd, sshKey, giteaURL, insecure)
|
|
||||||
}
|
|
||||||
|
|
||||||
func runLoginAddMain(name, token, user, passwd, sshKey, giteaURL string, insecure bool) error {
|
|
||||||
|
|
||||||
if len(giteaURL) == 0 {
|
|
||||||
log.Fatal("You have to input Gitea server URL")
|
|
||||||
}
|
|
||||||
if len(token) == 0 && (len(user)+len(passwd)) == 0 {
|
|
||||||
log.Fatal("No token set")
|
|
||||||
} else if len(user) != 0 && len(passwd) == 0 {
|
|
||||||
log.Fatal("No password set")
|
|
||||||
} else if len(user) == 0 && len(passwd) != 0 {
|
|
||||||
log.Fatal("No user set")
|
|
||||||
}
|
|
||||||
|
|
||||||
err := loadConfig(yamlConfigPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Unable to load config file " + yamlConfigPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
httpClient := &http.Client{}
|
|
||||||
if insecure {
|
|
||||||
cookieJar, _ := cookiejar.New(nil)
|
|
||||||
httpClient = &http.Client{
|
|
||||||
Jar: cookieJar,
|
|
||||||
Transport: &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
client, err := gitea.NewClient(giteaURL,
|
|
||||||
gitea.SetToken(token),
|
|
||||||
gitea.SetBasicAuth(user, passwd),
|
|
||||||
gitea.SetHTTPClient(httpClient),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
u, _, err := client.GetMyUserInfo()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(token) == 0 {
|
|
||||||
// create token
|
|
||||||
host, _ := os.Hostname()
|
|
||||||
tl, _, err := client.ListAccessTokens(gitea.ListAccessTokensOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
tokenName := host + "-tea"
|
|
||||||
for i := range tl {
|
|
||||||
if tl[i].Name == tokenName {
|
|
||||||
tokenName += time.Now().Format("2006-01-02_15-04-05")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t, _, err := client.CreateAccessToken(gitea.CreateAccessTokenOption{Name: tokenName})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
token = t.Token
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Login successful! Login name " + u.UserName)
|
|
||||||
|
|
||||||
if len(name) == 0 {
|
|
||||||
parsedURL, err := url.Parse(giteaURL)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
name = strings.ReplaceAll(strings.Title(parsedURL.Host), ".", "")
|
|
||||||
for _, l := range config.Logins {
|
|
||||||
if l.Name == name {
|
|
||||||
name += "_" + u.UserName
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = addLogin(Login{
|
|
||||||
Name: name,
|
|
||||||
URL: giteaURL,
|
|
||||||
Token: token,
|
|
||||||
Insecure: insecure,
|
|
||||||
SSHKey: sshKey,
|
|
||||||
User: u.UserName,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = saveConfig(yamlConfigPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdLogin represents to login a gitea server.
|
|
||||||
var cmdLoginList = cli.Command{
|
|
||||||
Name: "ls",
|
|
||||||
Usage: "List Gitea logins",
|
|
||||||
Description: `List Gitea logins`,
|
|
||||||
Action: runLoginList,
|
|
||||||
Flags: []cli.Flag{&OutputFlag},
|
|
||||||
}
|
|
||||||
|
|
||||||
func runLoginList(ctx *cli.Context) error {
|
|
||||||
err := loadConfig(yamlConfigPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Unable to load config file " + yamlConfigPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
headers := []string{
|
|
||||||
"Name",
|
|
||||||
"URL",
|
|
||||||
"SSHHost",
|
|
||||||
"User",
|
|
||||||
"Default",
|
|
||||||
}
|
|
||||||
|
|
||||||
var values [][]string
|
|
||||||
|
|
||||||
for _, l := range config.Logins {
|
|
||||||
values = append(values, []string{
|
|
||||||
l.Name,
|
|
||||||
l.URL,
|
|
||||||
l.GetSSHHost(),
|
|
||||||
l.User,
|
|
||||||
fmt.Sprint(l.Default),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
Output(outputValue, headers, values)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package login
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.gitea.io/tea/modules/config"
|
||||||
|
"code.gitea.io/tea/modules/interact"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdLoginAdd represents to login a gitea server.
|
||||||
|
var CmdLoginAdd = cli.Command{
|
||||||
|
Name: "add",
|
||||||
|
Usage: "Add a Gitea login",
|
||||||
|
Description: `Add a Gitea login`,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "name",
|
||||||
|
Aliases: []string{"n"},
|
||||||
|
Usage: "Login name",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "url",
|
||||||
|
Aliases: []string{"u"},
|
||||||
|
Value: "https://try.gitea.io",
|
||||||
|
EnvVars: []string{"GITEA_SERVER_URL"},
|
||||||
|
Usage: "Server URL",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "token",
|
||||||
|
Aliases: []string{"t"},
|
||||||
|
Value: "",
|
||||||
|
EnvVars: []string{"GITEA_SERVER_TOKEN"},
|
||||||
|
Usage: "Access token. Can be obtained from Settings > Applications",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "user",
|
||||||
|
Value: "",
|
||||||
|
EnvVars: []string{"GITEA_SERVER_USER"},
|
||||||
|
Usage: "User for basic auth (will create token)",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "password",
|
||||||
|
Aliases: []string{"pwd"},
|
||||||
|
Value: "",
|
||||||
|
EnvVars: []string{"GITEA_SERVER_PASSWORD"},
|
||||||
|
Usage: "Password for basic auth (will create token)",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "ssh-key",
|
||||||
|
Aliases: []string{"s"},
|
||||||
|
Usage: "Path to a SSH key to use for pull/push operations",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "insecure",
|
||||||
|
Aliases: []string{"i"},
|
||||||
|
Usage: "Disable TLS verification",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: runLoginAdd,
|
||||||
|
}
|
||||||
|
|
||||||
|
func runLoginAdd(ctx *cli.Context) error {
|
||||||
|
// TODO: if no args -> interactive
|
||||||
|
return config.AddLogin(
|
||||||
|
ctx.String("name"),
|
||||||
|
ctx.String("token"),
|
||||||
|
ctx.String("user"),
|
||||||
|
ctx.String("password"),
|
||||||
|
ctx.String("ssh-key"),
|
||||||
|
ctx.String("url"),
|
||||||
|
ctx.Bool("insecure"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunLoginAddInteractive create login interactive
|
||||||
|
// TODO: should be unexported
|
||||||
|
func RunLoginAddInteractive(_ *cli.Context) error {
|
||||||
|
return interact.CreateLogin()
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package login
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"code.gitea.io/tea/cmd/flags"
|
||||||
|
"code.gitea.io/tea/modules/config"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdLoginSetDefault represents to login a gitea server.
|
||||||
|
var CmdLoginSetDefault = cli.Command{
|
||||||
|
Name: "default",
|
||||||
|
Usage: "Get or Set Default Login",
|
||||||
|
Description: `Get or Set Default Login`,
|
||||||
|
ArgsUsage: "<Login>",
|
||||||
|
Action: runLoginSetDefault,
|
||||||
|
Flags: []cli.Flag{&flags.OutputFlag},
|
||||||
|
}
|
||||||
|
|
||||||
|
func runLoginSetDefault(ctx *cli.Context) error {
|
||||||
|
if err := config.LoadConfig(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if ctx.Args().Len() == 0 {
|
||||||
|
l, err := config.GetDefaultLogin()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Printf("Default Login: %s\n", l.Name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
loginExist := false
|
||||||
|
for i := range config.Config.Logins {
|
||||||
|
config.Config.Logins[i].Default = false
|
||||||
|
if config.Config.Logins[i].Name == ctx.Args().First() {
|
||||||
|
config.Config.Logins[i].Default = true
|
||||||
|
loginExist = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !loginExist {
|
||||||
|
return fmt.Errorf("login '%s' not found", ctx.Args().First())
|
||||||
|
}
|
||||||
|
|
||||||
|
return config.SaveConfig()
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package login
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.gitea.io/tea/cmd/flags"
|
||||||
|
"code.gitea.io/tea/modules/config"
|
||||||
|
|
||||||
|
"github.com/skratchdot/open-golang/open"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdLoginEdit represents to login a gitea server.
|
||||||
|
var CmdLoginEdit = cli.Command{
|
||||||
|
Name: "edit",
|
||||||
|
Usage: "Edit Gitea logins",
|
||||||
|
Description: `Edit Gitea logins`,
|
||||||
|
Action: runLoginEdit,
|
||||||
|
Flags: []cli.Flag{&flags.OutputFlag},
|
||||||
|
}
|
||||||
|
|
||||||
|
func runLoginEdit(ctx *cli.Context) error {
|
||||||
|
return open.Start(config.GetConfigPath())
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package login
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"code.gitea.io/tea/cmd/flags"
|
||||||
|
"code.gitea.io/tea/modules/config"
|
||||||
|
"code.gitea.io/tea/modules/print"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdLoginList represents to login a gitea server.
|
||||||
|
var CmdLoginList = cli.Command{
|
||||||
|
Name: "ls",
|
||||||
|
Usage: "List Gitea logins",
|
||||||
|
Description: `List Gitea logins`,
|
||||||
|
Action: runLoginList,
|
||||||
|
Flags: []cli.Flag{&flags.OutputFlag},
|
||||||
|
}
|
||||||
|
|
||||||
|
func runLoginList(ctx *cli.Context) error {
|
||||||
|
err := config.LoadConfig()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := []string{
|
||||||
|
"Name",
|
||||||
|
"URL",
|
||||||
|
"SSHHost",
|
||||||
|
"User",
|
||||||
|
"Default",
|
||||||
|
}
|
||||||
|
|
||||||
|
var values [][]string
|
||||||
|
|
||||||
|
for _, l := range config.Config.Logins {
|
||||||
|
values = append(values, []string{
|
||||||
|
l.Name,
|
||||||
|
l.URL,
|
||||||
|
l.GetSSHHost(),
|
||||||
|
l.User,
|
||||||
|
fmt.Sprint(l.Default),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
print.OutputList(flags.GlobalOutputValue, headers, values)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -9,6 +9,8 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"code.gitea.io/tea/modules/config"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -37,23 +39,23 @@ func runLogout(ctx *cli.Context) error {
|
||||||
return errors.New("Please specify a login name")
|
return errors.New("Please specify a login name")
|
||||||
}
|
}
|
||||||
|
|
||||||
err := loadConfig(yamlConfigPath)
|
err := config.LoadConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Unable to load config file " + yamlConfigPath)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var idx = -1
|
var idx = -1
|
||||||
for i, l := range config.Logins {
|
for i, l := range config.Config.Logins {
|
||||||
if l.Name == name {
|
if l.Name == name {
|
||||||
idx = i
|
idx = i
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if idx > -1 {
|
if idx > -1 {
|
||||||
config.Logins = append(config.Logins[:idx], config.Logins[idx+1:]...)
|
config.Config.Logins = append(config.Config.Logins[:idx], config.Config.Logins[idx+1:]...)
|
||||||
err = saveConfig(yamlConfigPath)
|
err = config.SaveConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Unable to save config file " + yamlConfigPath)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,10 +5,11 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"code.gitea.io/tea/cmd/flags"
|
||||||
"log"
|
"code.gitea.io/tea/cmd/milestones"
|
||||||
|
"code.gitea.io/tea/modules/config"
|
||||||
|
"code.gitea.io/tea/modules/print"
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -21,42 +22,25 @@ var CmdMilestones = cli.Command{
|
||||||
ArgsUsage: "[<milestone name>]",
|
ArgsUsage: "[<milestone name>]",
|
||||||
Action: runMilestones,
|
Action: runMilestones,
|
||||||
Subcommands: []*cli.Command{
|
Subcommands: []*cli.Command{
|
||||||
&CmdMilestonesList,
|
&milestones.CmdMilestonesList,
|
||||||
&CmdMilestonesCreate,
|
&milestones.CmdMilestonesCreate,
|
||||||
&CmdMilestonesClose,
|
&milestones.CmdMilestonesClose,
|
||||||
&CmdMilestonesDelete,
|
&milestones.CmdMilestonesDelete,
|
||||||
&CmdMilestonesReopen,
|
&milestones.CmdMilestonesReopen,
|
||||||
&CmdMilestonesIssues,
|
&milestones.CmdMilestonesIssues,
|
||||||
},
|
},
|
||||||
Flags: AllDefaultFlags,
|
Flags: flags.AllDefaultFlags,
|
||||||
}
|
|
||||||
|
|
||||||
// CmdMilestonesList represents a sub command of milestones to list milestones
|
|
||||||
var CmdMilestonesList = cli.Command{
|
|
||||||
Name: "ls",
|
|
||||||
Usage: "List milestones of the repository",
|
|
||||||
Description: `List milestones of the repository`,
|
|
||||||
Action: runMilestonesList,
|
|
||||||
Flags: append([]cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "state",
|
|
||||||
Usage: "Filter by milestone state (all|open|closed)",
|
|
||||||
DefaultText: "open",
|
|
||||||
},
|
|
||||||
&PaginationPageFlag,
|
|
||||||
&PaginationLimitFlag,
|
|
||||||
}, AllDefaultFlags...),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runMilestones(ctx *cli.Context) error {
|
func runMilestones(ctx *cli.Context) error {
|
||||||
if ctx.Args().Len() == 1 {
|
if ctx.Args().Len() == 1 {
|
||||||
return runMilestoneDetail(ctx, ctx.Args().First())
|
return runMilestoneDetail(ctx.Args().First())
|
||||||
}
|
}
|
||||||
return runMilestonesList(ctx)
|
return milestones.RunMilestonesList(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runMilestoneDetail(ctx *cli.Context, name string) error {
|
func runMilestoneDetail(name string) error {
|
||||||
login, owner, repo := initCommand()
|
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||||
client := login.Client()
|
client := login.Client()
|
||||||
|
|
||||||
milestone, _, err := client.GetMilestoneByName(owner, repo, name)
|
milestone, _, err := client.GetMilestoneByName(owner, repo, name)
|
||||||
|
@ -64,192 +48,6 @@ func runMilestoneDetail(ctx *cli.Context, name string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s\n",
|
print.MilestoneDetails(milestone)
|
||||||
milestone.Title,
|
|
||||||
)
|
|
||||||
if len(milestone.Description) != 0 {
|
|
||||||
fmt.Printf("\n%s\n", milestone.Description)
|
|
||||||
}
|
|
||||||
if milestone.Deadline != nil && !milestone.Deadline.IsZero() {
|
|
||||||
fmt.Printf("\nDeadline: %s\n", milestone.Deadline.Format("2006-01-02 15:04:05"))
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runMilestonesList(ctx *cli.Context) error {
|
|
||||||
login, owner, repo := initCommand()
|
|
||||||
|
|
||||||
state := gitea.StateOpen
|
|
||||||
switch ctx.String("state") {
|
|
||||||
case "all":
|
|
||||||
state = gitea.StateAll
|
|
||||||
case "closed":
|
|
||||||
state = gitea.StateClosed
|
|
||||||
}
|
|
||||||
|
|
||||||
milestones, _, err := login.Client().ListRepoMilestones(owner, repo, gitea.ListMilestoneOption{
|
|
||||||
ListOptions: getListOptions(ctx),
|
|
||||||
State: state,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
headers := []string{
|
|
||||||
"Title",
|
|
||||||
}
|
|
||||||
if state == gitea.StateAll {
|
|
||||||
headers = append(headers, "State")
|
|
||||||
}
|
|
||||||
headers = append(headers,
|
|
||||||
"Open/Closed Issues",
|
|
||||||
"DueDate",
|
|
||||||
)
|
|
||||||
|
|
||||||
var values [][]string
|
|
||||||
|
|
||||||
for _, m := range milestones {
|
|
||||||
var deadline = ""
|
|
||||||
|
|
||||||
if m.Deadline != nil && !m.Deadline.IsZero() {
|
|
||||||
deadline = m.Deadline.Format("2006-01-02 15:04:05")
|
|
||||||
}
|
|
||||||
|
|
||||||
item := []string{
|
|
||||||
m.Title,
|
|
||||||
}
|
|
||||||
if state == gitea.StateAll {
|
|
||||||
item = append(item, string(m.State))
|
|
||||||
}
|
|
||||||
item = append(item,
|
|
||||||
fmt.Sprintf("%d/%d", m.OpenIssues, m.ClosedIssues),
|
|
||||||
deadline,
|
|
||||||
)
|
|
||||||
|
|
||||||
values = append(values, item)
|
|
||||||
}
|
|
||||||
Output(outputValue, headers, values)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdMilestonesCreate represents a sub command of milestones to create milestone
|
|
||||||
var CmdMilestonesCreate = cli.Command{
|
|
||||||
Name: "create",
|
|
||||||
Usage: "Create an milestone on repository",
|
|
||||||
Description: `Create an milestone on repository`,
|
|
||||||
Action: runMilestonesCreate,
|
|
||||||
Flags: append([]cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "title",
|
|
||||||
Aliases: []string{"t"},
|
|
||||||
Usage: "milestone title to create",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "description",
|
|
||||||
Aliases: []string{"d"},
|
|
||||||
Usage: "milestone description to create",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "state",
|
|
||||||
Usage: "set milestone state (default is open)",
|
|
||||||
DefaultText: "open",
|
|
||||||
},
|
|
||||||
}, AllDefaultFlags...),
|
|
||||||
}
|
|
||||||
|
|
||||||
func runMilestonesCreate(ctx *cli.Context) error {
|
|
||||||
login, owner, repo := initCommand()
|
|
||||||
|
|
||||||
title := ctx.String("title")
|
|
||||||
if len(title) == 0 {
|
|
||||||
fmt.Printf("Title is required\n")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
state := gitea.StateOpen
|
|
||||||
if ctx.String("state") == "closed" {
|
|
||||||
state = gitea.StateClosed
|
|
||||||
}
|
|
||||||
|
|
||||||
mile, _, err := login.Client().CreateMilestone(owner, repo, gitea.CreateMilestoneOption{
|
|
||||||
Title: title,
|
|
||||||
Description: ctx.String("description"),
|
|
||||||
State: state,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return runMilestoneDetail(ctx, mile.Title)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdMilestonesClose represents a sub command of milestones to close an milestone
|
|
||||||
var CmdMilestonesClose = cli.Command{
|
|
||||||
Name: "close",
|
|
||||||
Usage: "Change state of an milestone to 'closed'",
|
|
||||||
Description: `Change state of an milestone to 'closed'`,
|
|
||||||
ArgsUsage: "<milestone name>",
|
|
||||||
Action: func(ctx *cli.Context) error {
|
|
||||||
if ctx.Bool("force") {
|
|
||||||
return deleteMilestone(ctx)
|
|
||||||
}
|
|
||||||
return editMilestoneStatus(ctx, true)
|
|
||||||
},
|
|
||||||
Flags: append([]cli.Flag{
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "force",
|
|
||||||
Aliases: []string{"f"},
|
|
||||||
Usage: "delete milestone",
|
|
||||||
},
|
|
||||||
}, AllDefaultFlags...),
|
|
||||||
}
|
|
||||||
|
|
||||||
func editMilestoneStatus(ctx *cli.Context, close bool) error {
|
|
||||||
login, owner, repo := initCommand()
|
|
||||||
client := login.Client()
|
|
||||||
|
|
||||||
state := gitea.StateOpen
|
|
||||||
if close {
|
|
||||||
state = gitea.StateClosed
|
|
||||||
}
|
|
||||||
_, _, err := client.EditMilestoneByName(owner, repo, ctx.Args().First(), gitea.EditMilestoneOption{
|
|
||||||
State: &state,
|
|
||||||
Title: ctx.Args().First(),
|
|
||||||
})
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdMilestonesDelete represents a sub command of milestones to delete an milestone
|
|
||||||
var CmdMilestonesDelete = cli.Command{
|
|
||||||
Name: "delete",
|
|
||||||
Aliases: []string{"rm"},
|
|
||||||
Usage: "delete a milestone",
|
|
||||||
Description: "delete a milestone",
|
|
||||||
ArgsUsage: "<milestone name>",
|
|
||||||
Action: deleteMilestone,
|
|
||||||
Flags: AllDefaultFlags,
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteMilestone(ctx *cli.Context) error {
|
|
||||||
login, owner, repo := initCommand()
|
|
||||||
client := login.Client()
|
|
||||||
|
|
||||||
_, err := client.DeleteMilestoneByName(owner, repo, ctx.Args().First())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdMilestonesReopen represents a sub command of milestones to open an milestone
|
|
||||||
var CmdMilestonesReopen = cli.Command{
|
|
||||||
Name: "reopen",
|
|
||||||
Aliases: []string{"open"},
|
|
||||||
Usage: "Change state of an milestone to 'open'",
|
|
||||||
Description: `Change state of an milestone to 'open'`,
|
|
||||||
ArgsUsage: "<milestone name>",
|
|
||||||
Action: func(ctx *cli.Context) error {
|
|
||||||
return editMilestoneStatus(ctx, false)
|
|
||||||
},
|
|
||||||
Flags: AllDefaultFlags,
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package milestones
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.gitea.io/tea/cmd/flags"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdMilestonesClose represents a sub command of milestones to close an milestone
|
||||||
|
var CmdMilestonesClose = cli.Command{
|
||||||
|
Name: "close",
|
||||||
|
Usage: "Change state of an milestone to 'closed'",
|
||||||
|
Description: `Change state of an milestone to 'closed'`,
|
||||||
|
ArgsUsage: "<milestone name>",
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
if ctx.Bool("force") {
|
||||||
|
return deleteMilestone(ctx)
|
||||||
|
}
|
||||||
|
return editMilestoneStatus(ctx, true)
|
||||||
|
},
|
||||||
|
Flags: append([]cli.Flag{
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "force",
|
||||||
|
Aliases: []string{"f"},
|
||||||
|
Usage: "delete milestone",
|
||||||
|
},
|
||||||
|
}, flags.AllDefaultFlags...),
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package milestones
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"code.gitea.io/tea/cmd/flags"
|
||||||
|
"code.gitea.io/tea/modules/config"
|
||||||
|
"code.gitea.io/tea/modules/print"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdMilestonesCreate represents a sub command of milestones to create milestone
|
||||||
|
var CmdMilestonesCreate = cli.Command{
|
||||||
|
Name: "create",
|
||||||
|
Usage: "Create an milestone on repository",
|
||||||
|
Description: `Create an milestone on repository`,
|
||||||
|
Action: runMilestonesCreate,
|
||||||
|
Flags: append([]cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "title",
|
||||||
|
Aliases: []string{"t"},
|
||||||
|
Usage: "milestone title to create",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "description",
|
||||||
|
Aliases: []string{"d"},
|
||||||
|
Usage: "milestone description to create",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "state",
|
||||||
|
Usage: "set milestone state (default is open)",
|
||||||
|
DefaultText: "open",
|
||||||
|
},
|
||||||
|
}, flags.AllDefaultFlags...),
|
||||||
|
}
|
||||||
|
|
||||||
|
func runMilestonesCreate(ctx *cli.Context) error {
|
||||||
|
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||||
|
|
||||||
|
title := ctx.String("title")
|
||||||
|
if len(title) == 0 {
|
||||||
|
fmt.Printf("Title is required\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
state := gitea.StateOpen
|
||||||
|
if ctx.String("state") == "closed" {
|
||||||
|
state = gitea.StateClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
mile, _, err := login.Client().CreateMilestone(owner, repo, gitea.CreateMilestoneOption{
|
||||||
|
Title: title,
|
||||||
|
Description: ctx.String("description"),
|
||||||
|
State: state,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
print.MilestoneDetails(mile)
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package milestones
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.gitea.io/tea/cmd/flags"
|
||||||
|
"code.gitea.io/tea/modules/config"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdMilestonesDelete represents a sub command of milestones to delete an milestone
|
||||||
|
var CmdMilestonesDelete = cli.Command{
|
||||||
|
Name: "delete",
|
||||||
|
Aliases: []string{"rm"},
|
||||||
|
Usage: "delete a milestone",
|
||||||
|
Description: "delete a milestone",
|
||||||
|
ArgsUsage: "<milestone name>",
|
||||||
|
Action: deleteMilestone,
|
||||||
|
Flags: flags.AllDefaultFlags,
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteMilestone(ctx *cli.Context) error {
|
||||||
|
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||||
|
client := login.Client()
|
||||||
|
|
||||||
|
_, err := client.DeleteMilestoneByName(owner, repo, ctx.Args().First())
|
||||||
|
return err
|
||||||
|
}
|
|
@ -2,12 +2,17 @@
|
||||||
// Use of this source code is governed by a MIT-style
|
// Use of this source code is governed by a MIT-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package cmd
|
package milestones
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"code.gitea.io/tea/cmd/flags"
|
||||||
|
"code.gitea.io/tea/modules/config"
|
||||||
|
"code.gitea.io/tea/modules/print"
|
||||||
|
"code.gitea.io/tea/modules/utils"
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
@ -34,9 +39,9 @@ var CmdMilestonesIssues = cli.Command{
|
||||||
Name: "kind",
|
Name: "kind",
|
||||||
Usage: "Filter by kind (issue|pull)",
|
Usage: "Filter by kind (issue|pull)",
|
||||||
},
|
},
|
||||||
&PaginationPageFlag,
|
&flags.PaginationPageFlag,
|
||||||
&PaginationLimitFlag,
|
&flags.PaginationLimitFlag,
|
||||||
}, AllDefaultFlags...),
|
}, flags.AllDefaultFlags...),
|
||||||
}
|
}
|
||||||
|
|
||||||
// CmdMilestoneAddIssue represents a sub command of milestone issues to add an issue/pull to an milestone
|
// CmdMilestoneAddIssue represents a sub command of milestone issues to add an issue/pull to an milestone
|
||||||
|
@ -47,7 +52,7 @@ var CmdMilestoneAddIssue = cli.Command{
|
||||||
Description: "Add an issue/pull to an milestone",
|
Description: "Add an issue/pull to an milestone",
|
||||||
ArgsUsage: "<milestone name> <issue/pull index>",
|
ArgsUsage: "<milestone name> <issue/pull index>",
|
||||||
Action: runMilestoneIssueAdd,
|
Action: runMilestoneIssueAdd,
|
||||||
Flags: AllDefaultFlags,
|
Flags: flags.AllDefaultFlags,
|
||||||
}
|
}
|
||||||
|
|
||||||
// CmdMilestoneRemoveIssue represents a sub command of milestones to remove an issue/pull from an milestone
|
// CmdMilestoneRemoveIssue represents a sub command of milestones to remove an issue/pull from an milestone
|
||||||
|
@ -58,11 +63,11 @@ var CmdMilestoneRemoveIssue = cli.Command{
|
||||||
Description: "Remove an issue/pull to an milestone",
|
Description: "Remove an issue/pull to an milestone",
|
||||||
ArgsUsage: "<milestone name> <issue/pull index>",
|
ArgsUsage: "<milestone name> <issue/pull index>",
|
||||||
Action: runMilestoneIssueRemove,
|
Action: runMilestoneIssueRemove,
|
||||||
Flags: AllDefaultFlags,
|
Flags: flags.AllDefaultFlags,
|
||||||
}
|
}
|
||||||
|
|
||||||
func runMilestoneIssueList(ctx *cli.Context) error {
|
func runMilestoneIssueList(ctx *cli.Context) error {
|
||||||
login, owner, repo := initCommand()
|
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||||
client := login.Client()
|
client := login.Client()
|
||||||
|
|
||||||
state := gitea.StateOpen
|
state := gitea.StateOpen
|
||||||
|
@ -91,7 +96,7 @@ func runMilestoneIssueList(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
issues, _, err := client.ListRepoIssues(owner, repo, gitea.ListIssueOption{
|
issues, _, err := client.ListRepoIssues(owner, repo, gitea.ListIssueOption{
|
||||||
ListOptions: getListOptions(ctx),
|
ListOptions: flags.GetListOptions(ctx),
|
||||||
Milestones: []string{milestone},
|
Milestones: []string{milestone},
|
||||||
Type: kind,
|
Type: kind,
|
||||||
State: state,
|
State: state,
|
||||||
|
@ -112,7 +117,7 @@ func runMilestoneIssueList(ctx *cli.Context) error {
|
||||||
var values [][]string
|
var values [][]string
|
||||||
|
|
||||||
if len(issues) == 0 {
|
if len(issues) == 0 {
|
||||||
Output(outputValue, headers, values)
|
print.OutputList(flags.GlobalOutputValue, headers, values)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,12 +142,12 @@ func runMilestoneIssueList(ctx *cli.Context) error {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Output(outputValue, headers, values)
|
print.OutputList(flags.GlobalOutputValue, headers, values)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runMilestoneIssueAdd(ctx *cli.Context) error {
|
func runMilestoneIssueAdd(ctx *cli.Context) error {
|
||||||
login, owner, repo := initCommand()
|
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||||
client := login.Client()
|
client := login.Client()
|
||||||
if ctx.Args().Len() == 0 {
|
if ctx.Args().Len() == 0 {
|
||||||
return fmt.Errorf("need two arguments")
|
return fmt.Errorf("need two arguments")
|
||||||
|
@ -150,7 +155,7 @@ func runMilestoneIssueAdd(ctx *cli.Context) error {
|
||||||
|
|
||||||
mileName := ctx.Args().Get(0)
|
mileName := ctx.Args().Get(0)
|
||||||
issueIndex := ctx.Args().Get(1)
|
issueIndex := ctx.Args().Get(1)
|
||||||
idx, err := argToIndex(issueIndex)
|
idx, err := utils.ArgToIndex(issueIndex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -168,7 +173,7 @@ func runMilestoneIssueAdd(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func runMilestoneIssueRemove(ctx *cli.Context) error {
|
func runMilestoneIssueRemove(ctx *cli.Context) error {
|
||||||
login, owner, repo := initCommand()
|
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||||
client := login.Client()
|
client := login.Client()
|
||||||
if ctx.Args().Len() == 0 {
|
if ctx.Args().Len() == 0 {
|
||||||
return fmt.Errorf("need two arguments")
|
return fmt.Errorf("need two arguments")
|
||||||
|
@ -176,7 +181,7 @@ func runMilestoneIssueRemove(ctx *cli.Context) error {
|
||||||
|
|
||||||
mileName := ctx.Args().Get(0)
|
mileName := ctx.Args().Get(0)
|
||||||
issueIndex := ctx.Args().Get(1)
|
issueIndex := ctx.Args().Get(1)
|
||||||
idx, err := argToIndex(issueIndex)
|
idx, err := utils.ArgToIndex(issueIndex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package milestones
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"code.gitea.io/tea/cmd/flags"
|
||||||
|
"code.gitea.io/tea/modules/config"
|
||||||
|
"code.gitea.io/tea/modules/print"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdMilestonesList represents a sub command of milestones to list milestones
|
||||||
|
var CmdMilestonesList = cli.Command{
|
||||||
|
Name: "ls",
|
||||||
|
Usage: "List milestones of the repository",
|
||||||
|
Description: `List milestones of the repository`,
|
||||||
|
Action: RunMilestonesList,
|
||||||
|
Flags: append([]cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "state",
|
||||||
|
Usage: "Filter by milestone state (all|open|closed)",
|
||||||
|
DefaultText: "open",
|
||||||
|
},
|
||||||
|
&flags.PaginationPageFlag,
|
||||||
|
&flags.PaginationLimitFlag,
|
||||||
|
}, flags.AllDefaultFlags...),
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunMilestonesList list milestones
|
||||||
|
func RunMilestonesList(ctx *cli.Context) error {
|
||||||
|
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||||
|
|
||||||
|
state := gitea.StateOpen
|
||||||
|
switch ctx.String("state") {
|
||||||
|
case "all":
|
||||||
|
state = gitea.StateAll
|
||||||
|
case "closed":
|
||||||
|
state = gitea.StateClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
milestones, _, err := login.Client().ListRepoMilestones(owner, repo, gitea.ListMilestoneOption{
|
||||||
|
ListOptions: flags.GetListOptions(ctx),
|
||||||
|
State: state,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := []string{
|
||||||
|
"Title",
|
||||||
|
}
|
||||||
|
if state == gitea.StateAll {
|
||||||
|
headers = append(headers, "State")
|
||||||
|
}
|
||||||
|
headers = append(headers,
|
||||||
|
"Open/Closed Issues",
|
||||||
|
"DueDate",
|
||||||
|
)
|
||||||
|
|
||||||
|
var values [][]string
|
||||||
|
|
||||||
|
for _, m := range milestones {
|
||||||
|
var deadline = ""
|
||||||
|
|
||||||
|
if m.Deadline != nil && !m.Deadline.IsZero() {
|
||||||
|
deadline = m.Deadline.Format("2006-01-02 15:04:05")
|
||||||
|
}
|
||||||
|
|
||||||
|
item := []string{
|
||||||
|
m.Title,
|
||||||
|
}
|
||||||
|
if state == gitea.StateAll {
|
||||||
|
item = append(item, string(m.State))
|
||||||
|
}
|
||||||
|
item = append(item,
|
||||||
|
fmt.Sprintf("%d/%d", m.OpenIssues, m.ClosedIssues),
|
||||||
|
deadline,
|
||||||
|
)
|
||||||
|
|
||||||
|
values = append(values, item)
|
||||||
|
}
|
||||||
|
print.OutputList(flags.GlobalOutputValue, headers, values)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package milestones
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.gitea.io/tea/cmd/flags"
|
||||||
|
"code.gitea.io/tea/modules/config"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdMilestonesReopen represents a sub command of milestones to open an milestone
|
||||||
|
var CmdMilestonesReopen = cli.Command{
|
||||||
|
Name: "reopen",
|
||||||
|
Aliases: []string{"open"},
|
||||||
|
Usage: "Change state of an milestone to 'open'",
|
||||||
|
Description: `Change state of an milestone to 'open'`,
|
||||||
|
ArgsUsage: "<milestone name>",
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
return editMilestoneStatus(ctx, false)
|
||||||
|
},
|
||||||
|
Flags: flags.AllDefaultFlags,
|
||||||
|
}
|
||||||
|
|
||||||
|
func editMilestoneStatus(ctx *cli.Context, close bool) error {
|
||||||
|
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||||
|
client := login.Client()
|
||||||
|
|
||||||
|
state := gitea.StateOpen
|
||||||
|
if close {
|
||||||
|
state = gitea.StateClosed
|
||||||
|
}
|
||||||
|
_, _, err := client.EditMilestoneByName(owner, repo, ctx.Args().First(), gitea.EditMilestoneOption{
|
||||||
|
State: &state,
|
||||||
|
Title: ctx.Args().First(),
|
||||||
|
})
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
|
@ -8,6 +8,10 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/tea/cmd/flags"
|
||||||
|
"code.gitea.io/tea/modules/config"
|
||||||
|
"code.gitea.io/tea/modules/print"
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
@ -34,16 +38,16 @@ var CmdNotifications = cli.Command{
|
||||||
Aliases: []string{"pd"},
|
Aliases: []string{"pd"},
|
||||||
Usage: "show pinned notifications instead unread",
|
Usage: "show pinned notifications instead unread",
|
||||||
},
|
},
|
||||||
&PaginationPageFlag,
|
&flags.PaginationPageFlag,
|
||||||
&PaginationLimitFlag,
|
&flags.PaginationLimitFlag,
|
||||||
}, AllDefaultFlags...),
|
}, flags.AllDefaultFlags...),
|
||||||
}
|
}
|
||||||
|
|
||||||
func runNotifications(ctx *cli.Context) error {
|
func runNotifications(ctx *cli.Context) error {
|
||||||
var news []*gitea.NotificationThread
|
var news []*gitea.NotificationThread
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
listOpts := getListOptions(ctx)
|
listOpts := flags.GetListOptions(ctx)
|
||||||
if listOpts.Page == 0 {
|
if listOpts.Page == 0 {
|
||||||
listOpts.Page = 1
|
listOpts.Page = 1
|
||||||
}
|
}
|
||||||
|
@ -57,13 +61,13 @@ func runNotifications(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.Bool("all") {
|
if ctx.Bool("all") {
|
||||||
login := initCommandLoginOnly()
|
login := config.InitCommandLoginOnly(flags.GlobalLoginValue)
|
||||||
news, _, err = login.Client().ListNotifications(gitea.ListNotificationOptions{
|
news, _, err = login.Client().ListNotifications(gitea.ListNotificationOptions{
|
||||||
ListOptions: listOpts,
|
ListOptions: listOpts,
|
||||||
Status: status,
|
Status: status,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
login, owner, repo := initCommand()
|
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||||
news, _, err = login.Client().ListRepoNotifications(owner, repo, gitea.ListNotificationOptions{
|
news, _, err = login.Client().ListRepoNotifications(owner, repo, gitea.ListNotificationOptions{
|
||||||
ListOptions: listOpts,
|
ListOptions: listOpts,
|
||||||
Status: status,
|
Status: status,
|
||||||
|
@ -107,7 +111,7 @@ func runNotifications(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(values) != 0 {
|
if len(values) != 0 {
|
||||||
Output(outputValue, headers, values)
|
print.OutputList(flags.GlobalOutputValue, headers, values)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,8 @@ import (
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/tea/cmd/flags"
|
||||||
|
"code.gitea.io/tea/modules/config"
|
||||||
local_git "code.gitea.io/tea/modules/git"
|
local_git "code.gitea.io/tea/modules/git"
|
||||||
|
|
||||||
"github.com/skratchdot/open-golang/open"
|
"github.com/skratchdot/open-golang/open"
|
||||||
|
@ -21,11 +23,11 @@ var CmdOpen = cli.Command{
|
||||||
Usage: "Open something of the repository on web browser",
|
Usage: "Open something of the repository on web browser",
|
||||||
Description: `Open something of the repository on web browser`,
|
Description: `Open something of the repository on web browser`,
|
||||||
Action: runOpen,
|
Action: runOpen,
|
||||||
Flags: append([]cli.Flag{}, LoginRepoFlags...),
|
Flags: append([]cli.Flag{}, flags.LoginRepoFlags...),
|
||||||
}
|
}
|
||||||
|
|
||||||
func runOpen(ctx *cli.Context) error {
|
func runOpen(ctx *cli.Context) error {
|
||||||
login, owner, repo := initCommand()
|
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||||
|
|
||||||
var suffix string
|
var suffix string
|
||||||
number := ctx.Args().Get(0)
|
number := ctx.Args().Get(0)
|
||||||
|
|
413
cmd/pulls.go
413
cmd/pulls.go
|
@ -6,16 +6,12 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
local_git "code.gitea.io/tea/modules/git"
|
"code.gitea.io/tea/cmd/flags"
|
||||||
|
"code.gitea.io/tea/cmd/pulls"
|
||||||
|
"code.gitea.io/tea/modules/config"
|
||||||
|
"code.gitea.io/tea/modules/utils"
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
|
||||||
"github.com/charmbracelet/glamour"
|
|
||||||
"github.com/go-git/go-git/v5"
|
|
||||||
git_config "github.com/go-git/go-git/v5/config"
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -27,35 +23,26 @@ var CmdPulls = cli.Command{
|
||||||
Description: `List, create, checkout and clean pull requests`,
|
Description: `List, create, checkout and clean pull requests`,
|
||||||
ArgsUsage: "[<pull index>]",
|
ArgsUsage: "[<pull index>]",
|
||||||
Action: runPulls,
|
Action: runPulls,
|
||||||
Flags: IssuePRFlags,
|
Flags: flags.IssuePRFlags,
|
||||||
Subcommands: []*cli.Command{
|
Subcommands: []*cli.Command{
|
||||||
&CmdPullsList,
|
&pulls.CmdPullsList,
|
||||||
&CmdPullsCheckout,
|
&pulls.CmdPullsCheckout,
|
||||||
&CmdPullsClean,
|
&pulls.CmdPullsClean,
|
||||||
&CmdPullsCreate,
|
&pulls.CmdPullsCreate,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPulls(ctx *cli.Context) error {
|
func runPulls(ctx *cli.Context) error {
|
||||||
if ctx.Args().Len() == 1 {
|
if ctx.Args().Len() == 1 {
|
||||||
return runPullDetail(ctx, ctx.Args().First())
|
return runPullDetail(ctx.Args().First())
|
||||||
}
|
}
|
||||||
return runPullsList(ctx)
|
return pulls.RunPullsList(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CmdPullsList represents a sub command of issues to list pulls
|
func runPullDetail(index string) error {
|
||||||
var CmdPullsList = cli.Command{
|
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||||
Name: "ls",
|
|
||||||
Usage: "List pull requests of the repository",
|
|
||||||
Description: `List pull requests of the repository`,
|
|
||||||
Action: runPullsList,
|
|
||||||
Flags: IssuePRFlags,
|
|
||||||
}
|
|
||||||
|
|
||||||
func runPullDetail(ctx *cli.Context, index string) error {
|
idx, err := utils.ArgToIndex(index)
|
||||||
login, owner, repo := initCommand()
|
|
||||||
|
|
||||||
idx, err := argToIndex(index)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -73,375 +60,3 @@ func runPullDetail(ctx *cli.Context, index string) error {
|
||||||
)
|
)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPullsList(ctx *cli.Context) error {
|
|
||||||
login, owner, repo := initCommand()
|
|
||||||
|
|
||||||
state := gitea.StateOpen
|
|
||||||
switch ctx.String("state") {
|
|
||||||
case "all":
|
|
||||||
state = gitea.StateAll
|
|
||||||
case "open":
|
|
||||||
state = gitea.StateOpen
|
|
||||||
case "closed":
|
|
||||||
state = gitea.StateClosed
|
|
||||||
}
|
|
||||||
|
|
||||||
prs, _, err := login.Client().ListRepoPullRequests(owner, repo, gitea.ListPullRequestsOptions{
|
|
||||||
State: state,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
headers := []string{
|
|
||||||
"Index",
|
|
||||||
"Title",
|
|
||||||
"State",
|
|
||||||
"Author",
|
|
||||||
"Milestone",
|
|
||||||
"Updated",
|
|
||||||
}
|
|
||||||
|
|
||||||
var values [][]string
|
|
||||||
|
|
||||||
if len(prs) == 0 {
|
|
||||||
Output(outputValue, headers, values)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, pr := range prs {
|
|
||||||
if pr == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
author := pr.Poster.FullName
|
|
||||||
if len(author) == 0 {
|
|
||||||
author = pr.Poster.UserName
|
|
||||||
}
|
|
||||||
mile := ""
|
|
||||||
if pr.Milestone != nil {
|
|
||||||
mile = pr.Milestone.Title
|
|
||||||
}
|
|
||||||
values = append(
|
|
||||||
values,
|
|
||||||
[]string{
|
|
||||||
strconv.FormatInt(pr.Index, 10),
|
|
||||||
pr.Title,
|
|
||||||
string(pr.State),
|
|
||||||
author,
|
|
||||||
mile,
|
|
||||||
pr.Updated.Format("2006-01-02 15:04:05"),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Output(outputValue, headers, values)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdPullsCheckout is a command to locally checkout the given PR
|
|
||||||
var CmdPullsCheckout = cli.Command{
|
|
||||||
Name: "checkout",
|
|
||||||
Usage: "Locally check out the given PR",
|
|
||||||
Description: `Locally check out the given PR`,
|
|
||||||
Action: runPullsCheckout,
|
|
||||||
ArgsUsage: "<pull index>",
|
|
||||||
Flags: AllDefaultFlags,
|
|
||||||
}
|
|
||||||
|
|
||||||
func runPullsCheckout(ctx *cli.Context) error {
|
|
||||||
login, owner, repo := initCommand()
|
|
||||||
if ctx.Args().Len() != 1 {
|
|
||||||
log.Fatal("Must specify a PR index")
|
|
||||||
}
|
|
||||||
|
|
||||||
// fetch PR source-repo & -branch from gitea
|
|
||||||
idx, err := argToIndex(ctx.Args().First())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pr, _, err := login.Client().GetPullRequest(owner, repo, idx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
remoteURL := pr.Head.Repository.CloneURL
|
|
||||||
remoteBranchName := pr.Head.Ref
|
|
||||||
|
|
||||||
// open local git repo
|
|
||||||
localRepo, err := local_git.RepoForWorkdir()
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// verify related remote is in local repo, otherwise add it
|
|
||||||
newRemoteName := fmt.Sprintf("pulls/%v", pr.Head.Repository.Owner.UserName)
|
|
||||||
localRemote, err := localRepo.GetOrCreateRemote(remoteURL, newRemoteName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
localRemoteName := localRemote.Config().Name
|
|
||||||
localBranchName := fmt.Sprintf("pulls/%v-%v", idx, remoteBranchName)
|
|
||||||
|
|
||||||
// fetch remote
|
|
||||||
fmt.Printf("Fetching PR %v (head %s:%s) from remote '%s'\n",
|
|
||||||
idx, remoteURL, remoteBranchName, localRemoteName)
|
|
||||||
|
|
||||||
url, err := local_git.ParseURL(localRemote.Config().URLs[0])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
auth, err := local_git.GetAuthForURL(url, login.User, login.SSHKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = localRemote.Fetch(&git.FetchOptions{Auth: auth})
|
|
||||||
if err == git.NoErrAlreadyUpToDate {
|
|
||||||
fmt.Println(err)
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkout local branch
|
|
||||||
fmt.Printf("Creating branch '%s'\n", localBranchName)
|
|
||||||
err = localRepo.TeaCreateBranch(localBranchName, remoteBranchName, localRemoteName)
|
|
||||||
if err == git.ErrBranchExists {
|
|
||||||
fmt.Println(err)
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Checking out PR %v\n", idx)
|
|
||||||
err = localRepo.TeaCheckout(localBranchName)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdPullsClean removes the remote and local feature branches, if a PR is merged.
|
|
||||||
var CmdPullsClean = cli.Command{
|
|
||||||
Name: "clean",
|
|
||||||
Usage: "Deletes local & remote feature-branches for a closed pull request",
|
|
||||||
Description: `Deletes local & remote feature-branches for a closed pull request`,
|
|
||||||
ArgsUsage: "<pull index>",
|
|
||||||
Action: runPullsClean,
|
|
||||||
Flags: append([]cli.Flag{
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "ignore-sha",
|
|
||||||
Usage: "Find the local branch by name instead of commit hash (less precise)",
|
|
||||||
},
|
|
||||||
}, AllDefaultFlags...),
|
|
||||||
}
|
|
||||||
|
|
||||||
func runPullsClean(ctx *cli.Context) error {
|
|
||||||
login, owner, repo := initCommand()
|
|
||||||
if ctx.Args().Len() != 1 {
|
|
||||||
return fmt.Errorf("Must specify a PR index")
|
|
||||||
}
|
|
||||||
|
|
||||||
// fetch PR source-repo & -branch from gitea
|
|
||||||
idx, err := argToIndex(ctx.Args().First())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pr, _, err := login.Client().GetPullRequest(owner, repo, idx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if pr.State == gitea.StateOpen {
|
|
||||||
return fmt.Errorf("PR is still open, won't delete branches")
|
|
||||||
}
|
|
||||||
|
|
||||||
// IDEA: abort if PR.Head.Repository.CloneURL does not match login.URL?
|
|
||||||
|
|
||||||
r, err := local_git.RepoForWorkdir()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// find a branch with matching sha or name, that has a remote matching the repo url
|
|
||||||
var branch *git_config.Branch
|
|
||||||
if ctx.Bool("ignore-sha") {
|
|
||||||
branch, err = r.TeaFindBranchByName(pr.Head.Ref, pr.Head.Repository.CloneURL)
|
|
||||||
} else {
|
|
||||||
branch, err = r.TeaFindBranchBySha(pr.Head.Sha, pr.Head.Repository.CloneURL)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if branch == nil {
|
|
||||||
if ctx.Bool("ignore-sha") {
|
|
||||||
return fmt.Errorf("Remote branch %s not found in local repo", pr.Head.Ref)
|
|
||||||
}
|
|
||||||
return fmt.Errorf(`Remote branch %s not found in local repo.
|
|
||||||
Either you don't track this PR, or the local branch has diverged from the remote.
|
|
||||||
If you still want to continue & are sure you don't loose any important commits,
|
|
||||||
call me again with the --ignore-sha flag`, pr.Head.Ref)
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepare deletion of local branch:
|
|
||||||
headRef, err := r.Head()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if headRef.Name().Short() == branch.Name {
|
|
||||||
fmt.Printf("Checking out 'master' to delete local branch '%s'\n", branch.Name)
|
|
||||||
err = r.TeaCheckout("master")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove local & remote branch
|
|
||||||
fmt.Printf("Deleting local branch %s and remote branch %s\n", branch.Name, pr.Head.Ref)
|
|
||||||
url, err := r.TeaRemoteURL(branch.Remote)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
auth, err := local_git.GetAuthForURL(url, login.User, login.SSHKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return r.TeaDeleteBranch(branch, pr.Head.Ref, auth)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdPullsCreate creates a pull request
|
|
||||||
var CmdPullsCreate = cli.Command{
|
|
||||||
Name: "create",
|
|
||||||
Usage: "Create a pull-request",
|
|
||||||
Description: "Create a pull-request",
|
|
||||||
Action: runPullsCreate,
|
|
||||||
Flags: append([]cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "head",
|
|
||||||
Usage: "Set head branch (default is current one)",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "base",
|
|
||||||
Aliases: []string{"b"},
|
|
||||||
Usage: "Set base branch (default is default branch)",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "title",
|
|
||||||
Aliases: []string{"t"},
|
|
||||||
Usage: "Set title of pull (default is head branch name)",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "description",
|
|
||||||
Aliases: []string{"d"},
|
|
||||||
Usage: "Set body of new pull",
|
|
||||||
},
|
|
||||||
}, AllDefaultFlags...),
|
|
||||||
}
|
|
||||||
|
|
||||||
func runPullsCreate(ctx *cli.Context) error {
|
|
||||||
login, ownerArg, repoArg := initCommand()
|
|
||||||
client := login.Client()
|
|
||||||
|
|
||||||
repo, _, err := client.GetRepo(ownerArg, repoArg)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("could not fetch repo meta: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// open local git repo
|
|
||||||
localRepo, err := local_git.RepoForWorkdir()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("could not open local repo: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// push if possible
|
|
||||||
log.Println("git push")
|
|
||||||
err = localRepo.Push(&git.PushOptions{})
|
|
||||||
if err != nil && err != git.NoErrAlreadyUpToDate {
|
|
||||||
log.Printf("Error occurred during 'git push':\n%s\n", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
base := ctx.String("base")
|
|
||||||
// default is default branch
|
|
||||||
if len(base) == 0 {
|
|
||||||
base = repo.DefaultBranch
|
|
||||||
}
|
|
||||||
|
|
||||||
head := ctx.String("head")
|
|
||||||
// default is current one
|
|
||||||
if len(head) == 0 {
|
|
||||||
headBranch, err := localRepo.Head()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
sha := headBranch.Hash().String()
|
|
||||||
|
|
||||||
remote, err := localRepo.TeaFindBranchRemote("", sha)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("could not determine remote for current branch: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if remote == nil {
|
|
||||||
// if no remote branch is found for the local hash, we abort:
|
|
||||||
// user has probably not configured a remote for the local branch,
|
|
||||||
// or local branch does not represent remote state.
|
|
||||||
log.Fatal("no matching remote found for this branch. try git push -u <remote> <branch>")
|
|
||||||
}
|
|
||||||
|
|
||||||
branchName, err := localRepo.TeaGetCurrentBranchName()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
url, err := local_git.ParseURL(remote.Config().URLs[0])
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
owner, _ := getOwnerAndRepo(strings.TrimLeft(url.Path, "/"), "")
|
|
||||||
head = fmt.Sprintf("%s:%s", owner, branchName)
|
|
||||||
}
|
|
||||||
|
|
||||||
title := ctx.String("title")
|
|
||||||
// default is head branch name
|
|
||||||
if len(title) == 0 {
|
|
||||||
title = head
|
|
||||||
if strings.Contains(title, ":") {
|
|
||||||
title = strings.SplitN(title, ":", 2)[1]
|
|
||||||
}
|
|
||||||
title = strings.Replace(title, "-", " ", -1)
|
|
||||||
title = strings.Replace(title, "_", " ", -1)
|
|
||||||
title = strings.Title(strings.ToLower(title))
|
|
||||||
}
|
|
||||||
// title is required
|
|
||||||
if len(title) == 0 {
|
|
||||||
fmt.Printf("Title is required")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
pr, _, err := client.CreatePullRequest(ownerArg, repoArg, gitea.CreatePullRequestOption{
|
|
||||||
Head: head,
|
|
||||||
Base: base,
|
|
||||||
Title: title,
|
|
||||||
Body: ctx.String("description"),
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("could not create PR from %s to %s:%s: %s", head, ownerArg, base, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
in := fmt.Sprintf("# #%d %s (%s)\n%s created %s\n\n%s\n", pr.Index,
|
|
||||||
pr.Title,
|
|
||||||
pr.State,
|
|
||||||
pr.Poster.UserName,
|
|
||||||
pr.Created.Format("2006-01-02 15:04:05"),
|
|
||||||
pr.Body,
|
|
||||||
)
|
|
||||||
out, err := glamour.Render(in, getGlamourTheme())
|
|
||||||
fmt.Print(out)
|
|
||||||
|
|
||||||
fmt.Println(pr.HTMLURL)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func argToIndex(arg string) (int64, error) {
|
|
||||||
if strings.HasPrefix(arg, "#") {
|
|
||||||
arg = arg[1:]
|
|
||||||
}
|
|
||||||
return strconv.ParseInt(arg, 10, 64)
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package pulls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"code.gitea.io/tea/cmd/flags"
|
||||||
|
"code.gitea.io/tea/modules/config"
|
||||||
|
local_git "code.gitea.io/tea/modules/git"
|
||||||
|
"code.gitea.io/tea/modules/utils"
|
||||||
|
|
||||||
|
"github.com/go-git/go-git/v5"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdPullsCheckout is a command to locally checkout the given PR
|
||||||
|
var CmdPullsCheckout = cli.Command{
|
||||||
|
Name: "checkout",
|
||||||
|
Usage: "Locally check out the given PR",
|
||||||
|
Description: `Locally check out the given PR`,
|
||||||
|
Action: runPullsCheckout,
|
||||||
|
ArgsUsage: "<pull index>",
|
||||||
|
Flags: flags.AllDefaultFlags,
|
||||||
|
}
|
||||||
|
|
||||||
|
func runPullsCheckout(ctx *cli.Context) error {
|
||||||
|
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||||
|
if ctx.Args().Len() != 1 {
|
||||||
|
log.Fatal("Must specify a PR index")
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch PR source-repo & -branch from gitea
|
||||||
|
idx, err := utils.ArgToIndex(ctx.Args().First())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pr, _, err := login.Client().GetPullRequest(owner, repo, idx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
remoteURL := pr.Head.Repository.CloneURL
|
||||||
|
remoteBranchName := pr.Head.Ref
|
||||||
|
|
||||||
|
// open local git repo
|
||||||
|
localRepo, err := local_git.RepoForWorkdir()
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify related remote is in local repo, otherwise add it
|
||||||
|
newRemoteName := fmt.Sprintf("pulls/%v", pr.Head.Repository.Owner.UserName)
|
||||||
|
localRemote, err := localRepo.GetOrCreateRemote(remoteURL, newRemoteName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
localRemoteName := localRemote.Config().Name
|
||||||
|
localBranchName := fmt.Sprintf("pulls/%v-%v", idx, remoteBranchName)
|
||||||
|
|
||||||
|
// fetch remote
|
||||||
|
fmt.Printf("Fetching PR %v (head %s:%s) from remote '%s'\n",
|
||||||
|
idx, remoteURL, remoteBranchName, localRemoteName)
|
||||||
|
|
||||||
|
url, err := local_git.ParseURL(localRemote.Config().URLs[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
auth, err := local_git.GetAuthForURL(url, login.User, login.SSHKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = localRemote.Fetch(&git.FetchOptions{Auth: auth})
|
||||||
|
if err == git.NoErrAlreadyUpToDate {
|
||||||
|
fmt.Println(err)
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkout local branch
|
||||||
|
fmt.Printf("Creating branch '%s'\n", localBranchName)
|
||||||
|
err = localRepo.TeaCreateBranch(localBranchName, remoteBranchName, localRemoteName)
|
||||||
|
if err == git.ErrBranchExists {
|
||||||
|
fmt.Println(err)
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Checking out PR %v\n", idx)
|
||||||
|
err = localRepo.TeaCheckout(localBranchName)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package pulls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"code.gitea.io/tea/cmd/flags"
|
||||||
|
"code.gitea.io/tea/modules/config"
|
||||||
|
local_git "code.gitea.io/tea/modules/git"
|
||||||
|
"code.gitea.io/tea/modules/utils"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
git_config "github.com/go-git/go-git/v5/config"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdPullsClean removes the remote and local feature branches, if a PR is merged.
|
||||||
|
var CmdPullsClean = cli.Command{
|
||||||
|
Name: "clean",
|
||||||
|
Usage: "Deletes local & remote feature-branches for a closed pull request",
|
||||||
|
Description: `Deletes local & remote feature-branches for a closed pull request`,
|
||||||
|
ArgsUsage: "<pull index>",
|
||||||
|
Action: runPullsClean,
|
||||||
|
Flags: append([]cli.Flag{
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "ignore-sha",
|
||||||
|
Usage: "Find the local branch by name instead of commit hash (less precise)",
|
||||||
|
},
|
||||||
|
}, flags.AllDefaultFlags...),
|
||||||
|
}
|
||||||
|
|
||||||
|
func runPullsClean(ctx *cli.Context) error {
|
||||||
|
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||||
|
if ctx.Args().Len() != 1 {
|
||||||
|
return fmt.Errorf("Must specify a PR index")
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch PR source-repo & -branch from gitea
|
||||||
|
idx, err := utils.ArgToIndex(ctx.Args().First())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pr, _, err := login.Client().GetPullRequest(owner, repo, idx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if pr.State == gitea.StateOpen {
|
||||||
|
return fmt.Errorf("PR is still open, won't delete branches")
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDEA: abort if PR.Head.Repository.CloneURL does not match login.URL?
|
||||||
|
|
||||||
|
r, err := local_git.RepoForWorkdir()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// find a branch with matching sha or name, that has a remote matching the repo url
|
||||||
|
var branch *git_config.Branch
|
||||||
|
if ctx.Bool("ignore-sha") {
|
||||||
|
branch, err = r.TeaFindBranchByName(pr.Head.Ref, pr.Head.Repository.CloneURL)
|
||||||
|
} else {
|
||||||
|
branch, err = r.TeaFindBranchBySha(pr.Head.Sha, pr.Head.Repository.CloneURL)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if branch == nil {
|
||||||
|
if ctx.Bool("ignore-sha") {
|
||||||
|
return fmt.Errorf("Remote branch %s not found in local repo", pr.Head.Ref)
|
||||||
|
}
|
||||||
|
return fmt.Errorf(`Remote branch %s not found in local repo.
|
||||||
|
Either you don't track this PR, or the local branch has diverged from the remote.
|
||||||
|
If you still want to continue & are sure you don't loose any important commits,
|
||||||
|
call me again with the --ignore-sha flag`, pr.Head.Ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepare deletion of local branch:
|
||||||
|
headRef, err := r.Head()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if headRef.Name().Short() == branch.Name {
|
||||||
|
fmt.Printf("Checking out 'master' to delete local branch '%s'\n", branch.Name)
|
||||||
|
err = r.TeaCheckout("master")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove local & remote branch
|
||||||
|
fmt.Printf("Deleting local branch %s and remote branch %s\n", branch.Name, pr.Head.Ref)
|
||||||
|
url, err := r.TeaRemoteURL(branch.Remote)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
auth, err := local_git.GetAuthForURL(url, login.User, login.SSHKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return r.TeaDeleteBranch(branch, pr.Head.Ref, auth)
|
||||||
|
}
|
|
@ -0,0 +1,145 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package pulls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/tea/cmd/flags"
|
||||||
|
"code.gitea.io/tea/modules/config"
|
||||||
|
local_git "code.gitea.io/tea/modules/git"
|
||||||
|
"code.gitea.io/tea/modules/print"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
"github.com/go-git/go-git/v5"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdPullsCreate creates a pull request
|
||||||
|
var CmdPullsCreate = cli.Command{
|
||||||
|
Name: "create",
|
||||||
|
Usage: "Create a pull-request",
|
||||||
|
Description: "Create a pull-request",
|
||||||
|
Action: runPullsCreate,
|
||||||
|
Flags: append([]cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "head",
|
||||||
|
Usage: "Set head branch (default is current one)",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "base",
|
||||||
|
Aliases: []string{"b"},
|
||||||
|
Usage: "Set base branch (default is default branch)",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "title",
|
||||||
|
Aliases: []string{"t"},
|
||||||
|
Usage: "Set title of pull (default is head branch name)",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "description",
|
||||||
|
Aliases: []string{"d"},
|
||||||
|
Usage: "Set body of new pull",
|
||||||
|
},
|
||||||
|
}, flags.AllDefaultFlags...),
|
||||||
|
}
|
||||||
|
|
||||||
|
func runPullsCreate(ctx *cli.Context) error {
|
||||||
|
login, ownerArg, repoArg := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||||
|
client := login.Client()
|
||||||
|
|
||||||
|
repo, _, err := client.GetRepo(ownerArg, repoArg)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("could not fetch repo meta: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// open local git repo
|
||||||
|
localRepo, err := local_git.RepoForWorkdir()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("could not open local repo: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// push if possible
|
||||||
|
log.Println("git push")
|
||||||
|
err = localRepo.Push(&git.PushOptions{})
|
||||||
|
if err != nil && err != git.NoErrAlreadyUpToDate {
|
||||||
|
log.Printf("Error occurred during 'git push':\n%s\n", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
base := ctx.String("base")
|
||||||
|
// default is default branch
|
||||||
|
if len(base) == 0 {
|
||||||
|
base = repo.DefaultBranch
|
||||||
|
}
|
||||||
|
|
||||||
|
head := ctx.String("head")
|
||||||
|
// default is current one
|
||||||
|
if len(head) == 0 {
|
||||||
|
headBranch, err := localRepo.Head()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
sha := headBranch.Hash().String()
|
||||||
|
|
||||||
|
remote, err := localRepo.TeaFindBranchRemote("", sha)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("could not determine remote for current branch: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if remote == nil {
|
||||||
|
// if no remote branch is found for the local hash, we abort:
|
||||||
|
// user has probably not configured a remote for the local branch,
|
||||||
|
// or local branch does not represent remote state.
|
||||||
|
log.Fatal("no matching remote found for this branch. try git push -u <remote> <branch>")
|
||||||
|
}
|
||||||
|
|
||||||
|
branchName, err := localRepo.TeaGetCurrentBranchName()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
url, err := local_git.ParseURL(remote.Config().URLs[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
owner, _ := config.GetOwnerAndRepo(strings.TrimLeft(url.Path, "/"), "")
|
||||||
|
head = fmt.Sprintf("%s:%s", owner, branchName)
|
||||||
|
}
|
||||||
|
|
||||||
|
title := ctx.String("title")
|
||||||
|
// default is head branch name
|
||||||
|
if len(title) == 0 {
|
||||||
|
title = head
|
||||||
|
if strings.Contains(title, ":") {
|
||||||
|
title = strings.SplitN(title, ":", 2)[1]
|
||||||
|
}
|
||||||
|
title = strings.Replace(title, "-", " ", -1)
|
||||||
|
title = strings.Replace(title, "_", " ", -1)
|
||||||
|
title = strings.Title(strings.ToLower(title))
|
||||||
|
}
|
||||||
|
// title is required
|
||||||
|
if len(title) == 0 {
|
||||||
|
fmt.Printf("Title is required")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pr, _, err := client.CreatePullRequest(ownerArg, repoArg, gitea.CreatePullRequestOption{
|
||||||
|
Head: head,
|
||||||
|
Base: base,
|
||||||
|
Title: title,
|
||||||
|
Body: ctx.String("description"),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("could not create PR from %s to %s:%s: %s", head, ownerArg, base, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
print.PullDetails(pr)
|
||||||
|
|
||||||
|
fmt.Println(pr.HTMLURL)
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package pulls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"code.gitea.io/tea/cmd/flags"
|
||||||
|
"code.gitea.io/tea/modules/config"
|
||||||
|
"code.gitea.io/tea/modules/print"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdPullsList represents a sub command of issues to list pulls
|
||||||
|
var CmdPullsList = cli.Command{
|
||||||
|
Name: "ls",
|
||||||
|
Usage: "List pull requests of the repository",
|
||||||
|
Description: `List pull requests of the repository`,
|
||||||
|
Action: RunPullsList,
|
||||||
|
Flags: flags.IssuePRFlags,
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunPullsList return list of pulls
|
||||||
|
func RunPullsList(ctx *cli.Context) error {
|
||||||
|
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||||
|
|
||||||
|
state := gitea.StateOpen
|
||||||
|
switch ctx.String("state") {
|
||||||
|
case "all":
|
||||||
|
state = gitea.StateAll
|
||||||
|
case "open":
|
||||||
|
state = gitea.StateOpen
|
||||||
|
case "closed":
|
||||||
|
state = gitea.StateClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
prs, _, err := login.Client().ListRepoPullRequests(owner, repo, gitea.ListPullRequestsOptions{
|
||||||
|
State: state,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := []string{
|
||||||
|
"Index",
|
||||||
|
"Title",
|
||||||
|
"State",
|
||||||
|
"Author",
|
||||||
|
"Milestone",
|
||||||
|
"Updated",
|
||||||
|
}
|
||||||
|
|
||||||
|
var values [][]string
|
||||||
|
|
||||||
|
if len(prs) == 0 {
|
||||||
|
print.OutputList(flags.GlobalOutputValue, headers, values)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pr := range prs {
|
||||||
|
if pr == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
author := pr.Poster.FullName
|
||||||
|
if len(author) == 0 {
|
||||||
|
author = pr.Poster.UserName
|
||||||
|
}
|
||||||
|
mile := ""
|
||||||
|
if pr.Milestone != nil {
|
||||||
|
mile = pr.Milestone.Title
|
||||||
|
}
|
||||||
|
values = append(
|
||||||
|
values,
|
||||||
|
[]string{
|
||||||
|
strconv.FormatInt(pr.Index, 10),
|
||||||
|
pr.Title,
|
||||||
|
string(pr.State),
|
||||||
|
author,
|
||||||
|
mile,
|
||||||
|
pr.Updated.Format("2006-01-02 15:04:05"),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
print.OutputList(flags.GlobalOutputValue, headers, values)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
300
cmd/releases.go
300
cmd/releases.go
|
@ -5,307 +5,25 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"code.gitea.io/tea/cmd/flags"
|
||||||
"log"
|
"code.gitea.io/tea/cmd/releases"
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdReleases represents to login a gitea server.
|
// CmdReleases represents to login a gitea server.
|
||||||
|
// ToDo: ReleaseDetails
|
||||||
var CmdReleases = cli.Command{
|
var CmdReleases = cli.Command{
|
||||||
Name: "release",
|
Name: "release",
|
||||||
Aliases: []string{"releases"},
|
Aliases: []string{"releases"},
|
||||||
Usage: "Manage releases",
|
Usage: "Manage releases",
|
||||||
Description: "Manage releases",
|
Description: "Manage releases",
|
||||||
Action: runReleases,
|
Action: releases.RunReleasesList,
|
||||||
Subcommands: []*cli.Command{
|
Subcommands: []*cli.Command{
|
||||||
&CmdReleaseList,
|
&releases.CmdReleaseList,
|
||||||
&CmdReleaseCreate,
|
&releases.CmdReleaseCreate,
|
||||||
&CmdReleaseDelete,
|
&releases.CmdReleaseDelete,
|
||||||
&CmdReleaseEdit,
|
&releases.CmdReleaseEdit,
|
||||||
},
|
},
|
||||||
Flags: AllDefaultFlags,
|
Flags: flags.AllDefaultFlags,
|
||||||
}
|
|
||||||
|
|
||||||
// CmdReleaseList represents a sub command of Release to list releases
|
|
||||||
var CmdReleaseList = cli.Command{
|
|
||||||
Name: "ls",
|
|
||||||
Usage: "List Releases",
|
|
||||||
Description: "List Releases",
|
|
||||||
Action: runReleases,
|
|
||||||
Flags: append([]cli.Flag{
|
|
||||||
&PaginationPageFlag,
|
|
||||||
&PaginationLimitFlag,
|
|
||||||
}, AllDefaultFlags...),
|
|
||||||
}
|
|
||||||
|
|
||||||
func runReleases(ctx *cli.Context) error {
|
|
||||||
login, owner, repo := initCommand()
|
|
||||||
|
|
||||||
releases, _, err := login.Client().ListReleases(owner, repo, gitea.ListReleasesOptions{ListOptions: getListOptions(ctx)})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
headers := []string{
|
|
||||||
"Tag-Name",
|
|
||||||
"Title",
|
|
||||||
"Published At",
|
|
||||||
"Status",
|
|
||||||
"Tar URL",
|
|
||||||
}
|
|
||||||
|
|
||||||
var values [][]string
|
|
||||||
|
|
||||||
if len(releases) == 0 {
|
|
||||||
Output(outputValue, headers, values)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, release := range releases {
|
|
||||||
status := "released"
|
|
||||||
if release.IsDraft {
|
|
||||||
status = "draft"
|
|
||||||
} else if release.IsPrerelease {
|
|
||||||
status = "prerelease"
|
|
||||||
}
|
|
||||||
values = append(
|
|
||||||
values,
|
|
||||||
[]string{
|
|
||||||
release.TagName,
|
|
||||||
release.Title,
|
|
||||||
release.PublishedAt.Format("2006-01-02 15:04:05"),
|
|
||||||
status,
|
|
||||||
release.TarURL,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Output(outputValue, headers, values)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdReleaseCreate represents a sub command of Release to create release
|
|
||||||
var CmdReleaseCreate = cli.Command{
|
|
||||||
Name: "create",
|
|
||||||
Usage: "Create a release",
|
|
||||||
Description: `Create a release`,
|
|
||||||
Action: runReleaseCreate,
|
|
||||||
Flags: append([]cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "tag",
|
|
||||||
Usage: "Tag name",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "target",
|
|
||||||
Usage: "Target refs, branch name or commit id",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "title",
|
|
||||||
Aliases: []string{"t"},
|
|
||||||
Usage: "Release title",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "note",
|
|
||||||
Aliases: []string{"n"},
|
|
||||||
Usage: "Release notes",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "draft",
|
|
||||||
Aliases: []string{"d"},
|
|
||||||
Usage: "Is a draft",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "prerelease",
|
|
||||||
Aliases: []string{"p"},
|
|
||||||
Usage: "Is a pre-release",
|
|
||||||
},
|
|
||||||
&cli.StringSliceFlag{
|
|
||||||
Name: "asset",
|
|
||||||
Aliases: []string{"a"},
|
|
||||||
Usage: "List of files to attach",
|
|
||||||
},
|
|
||||||
}, AllDefaultFlags...),
|
|
||||||
}
|
|
||||||
|
|
||||||
func runReleaseCreate(ctx *cli.Context) error {
|
|
||||||
login, owner, repo := initCommand()
|
|
||||||
|
|
||||||
release, resp, err := login.Client().CreateRelease(owner, repo, gitea.CreateReleaseOption{
|
|
||||||
TagName: ctx.String("tag"),
|
|
||||||
Target: ctx.String("target"),
|
|
||||||
Title: ctx.String("title"),
|
|
||||||
Note: ctx.String("note"),
|
|
||||||
IsDraft: ctx.Bool("draft"),
|
|
||||||
IsPrerelease: ctx.Bool("prerelease"),
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if resp != nil && resp.StatusCode == http.StatusConflict {
|
|
||||||
fmt.Println("error: There already is a release for this tag")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, asset := range ctx.StringSlice("asset") {
|
|
||||||
var file *os.File
|
|
||||||
|
|
||||||
if file, err = os.Open(asset); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
filePath := filepath.Base(asset)
|
|
||||||
|
|
||||||
if _, _, err = login.Client().CreateReleaseAttachment(owner, repo, release.ID, file, filePath); err != nil {
|
|
||||||
file.Close()
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
file.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdReleaseDelete represents a sub command of Release to delete a release
|
|
||||||
var CmdReleaseDelete = cli.Command{
|
|
||||||
Name: "delete",
|
|
||||||
Usage: "Delete a release",
|
|
||||||
Description: `Delete a release`,
|
|
||||||
ArgsUsage: "<release tag>",
|
|
||||||
Action: runReleaseDelete,
|
|
||||||
Flags: AllDefaultFlags,
|
|
||||||
}
|
|
||||||
|
|
||||||
func runReleaseDelete(ctx *cli.Context) error {
|
|
||||||
login, owner, repo := initCommand()
|
|
||||||
client := login.Client()
|
|
||||||
|
|
||||||
tag := ctx.Args().First()
|
|
||||||
if len(tag) == 0 {
|
|
||||||
fmt.Println("Release tag needed to delete")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
release, err := getReleaseByTag(owner, repo, tag, client)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if release == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = client.DeleteRelease(owner, repo, release.ID)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func getReleaseByTag(owner, repo, tag string, client *gitea.Client) (*gitea.Release, error) {
|
|
||||||
rl, _, err := client.ListReleases(owner, repo, gitea.ListReleasesOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(rl) == 0 {
|
|
||||||
fmt.Println("Repo does not have any release")
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
for _, r := range rl {
|
|
||||||
if r.TagName == tag {
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt.Println("Release tag does not exist")
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdReleaseEdit represents a sub command of Release to edit releases
|
|
||||||
var CmdReleaseEdit = cli.Command{
|
|
||||||
Name: "edit",
|
|
||||||
Usage: "Edit a release",
|
|
||||||
Description: `Edit a release`,
|
|
||||||
ArgsUsage: "<release tag>",
|
|
||||||
Action: runReleaseEdit,
|
|
||||||
Flags: append([]cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "tag",
|
|
||||||
Usage: "Change Tag",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "target",
|
|
||||||
Usage: "Change Target",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "title",
|
|
||||||
Aliases: []string{"t"},
|
|
||||||
Usage: "Change Title",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "note",
|
|
||||||
Aliases: []string{"n"},
|
|
||||||
Usage: "Change Notes",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "draft",
|
|
||||||
Aliases: []string{"d"},
|
|
||||||
Usage: "Mark as Draft [True/false]",
|
|
||||||
DefaultText: "true",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "prerelease",
|
|
||||||
Aliases: []string{"p"},
|
|
||||||
Usage: "Mark as Pre-Release [True/false]",
|
|
||||||
DefaultText: "true",
|
|
||||||
},
|
|
||||||
}, AllDefaultFlags...),
|
|
||||||
}
|
|
||||||
|
|
||||||
func runReleaseEdit(ctx *cli.Context) error {
|
|
||||||
login, owner, repo := initCommand()
|
|
||||||
client := login.Client()
|
|
||||||
|
|
||||||
tag := ctx.Args().First()
|
|
||||||
if len(tag) == 0 {
|
|
||||||
fmt.Println("Release tag needed to edit")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
release, err := getReleaseByTag(owner, repo, tag, client)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if release == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var isDraft, isPre *bool
|
|
||||||
bTrue := true
|
|
||||||
bFalse := false
|
|
||||||
if ctx.IsSet("draft") {
|
|
||||||
isDraft = &bFalse
|
|
||||||
if strings.ToLower(ctx.String("draft"))[:1] == "t" {
|
|
||||||
isDraft = &bTrue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ctx.IsSet("prerelease") {
|
|
||||||
isPre = &bFalse
|
|
||||||
if strings.ToLower(ctx.String("prerelease"))[:1] == "t" {
|
|
||||||
isPre = &bTrue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, err = client.EditRelease(owner, repo, release.ID, gitea.EditReleaseOption{
|
|
||||||
TagName: ctx.String("tag"),
|
|
||||||
Target: ctx.String("target"),
|
|
||||||
Title: ctx.String("title"),
|
|
||||||
Note: ctx.String("note"),
|
|
||||||
IsDraft: isDraft,
|
|
||||||
IsPrerelease: isPre,
|
|
||||||
})
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package releases
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"code.gitea.io/tea/cmd/flags"
|
||||||
|
"code.gitea.io/tea/modules/config"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdReleaseCreate represents a sub command of Release to create release
|
||||||
|
var CmdReleaseCreate = cli.Command{
|
||||||
|
Name: "create",
|
||||||
|
Usage: "Create a release",
|
||||||
|
Description: `Create a release`,
|
||||||
|
Action: runReleaseCreate,
|
||||||
|
Flags: append([]cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "tag",
|
||||||
|
Usage: "Tag name",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "target",
|
||||||
|
Usage: "Target refs, branch name or commit id",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "title",
|
||||||
|
Aliases: []string{"t"},
|
||||||
|
Usage: "Release title",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "note",
|
||||||
|
Aliases: []string{"n"},
|
||||||
|
Usage: "Release notes",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "draft",
|
||||||
|
Aliases: []string{"d"},
|
||||||
|
Usage: "Is a draft",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "prerelease",
|
||||||
|
Aliases: []string{"p"},
|
||||||
|
Usage: "Is a pre-release",
|
||||||
|
},
|
||||||
|
&cli.StringSliceFlag{
|
||||||
|
Name: "asset",
|
||||||
|
Aliases: []string{"a"},
|
||||||
|
Usage: "List of files to attach",
|
||||||
|
},
|
||||||
|
}, flags.AllDefaultFlags...),
|
||||||
|
}
|
||||||
|
|
||||||
|
func runReleaseCreate(ctx *cli.Context) error {
|
||||||
|
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||||
|
|
||||||
|
release, resp, err := login.Client().CreateRelease(owner, repo, gitea.CreateReleaseOption{
|
||||||
|
TagName: ctx.String("tag"),
|
||||||
|
Target: ctx.String("target"),
|
||||||
|
Title: ctx.String("title"),
|
||||||
|
Note: ctx.String("note"),
|
||||||
|
IsDraft: ctx.Bool("draft"),
|
||||||
|
IsPrerelease: ctx.Bool("prerelease"),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if resp != nil && resp.StatusCode == http.StatusConflict {
|
||||||
|
fmt.Println("error: There already is a release for this tag")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, asset := range ctx.StringSlice("asset") {
|
||||||
|
var file *os.File
|
||||||
|
|
||||||
|
if file, err = os.Open(asset); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
filePath := filepath.Base(asset)
|
||||||
|
|
||||||
|
if _, _, err = login.Client().CreateReleaseAttachment(owner, repo, release.ID, file, filePath); err != nil {
|
||||||
|
file.Close()
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
file.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package releases
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"code.gitea.io/tea/cmd/flags"
|
||||||
|
"code.gitea.io/tea/modules/config"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdReleaseDelete represents a sub command of Release to delete a release
|
||||||
|
var CmdReleaseDelete = cli.Command{
|
||||||
|
Name: "delete",
|
||||||
|
Usage: "Delete a release",
|
||||||
|
Description: `Delete a release`,
|
||||||
|
ArgsUsage: "<release tag>",
|
||||||
|
Action: runReleaseDelete,
|
||||||
|
Flags: flags.AllDefaultFlags,
|
||||||
|
}
|
||||||
|
|
||||||
|
func runReleaseDelete(ctx *cli.Context) error {
|
||||||
|
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||||
|
client := login.Client()
|
||||||
|
|
||||||
|
tag := ctx.Args().First()
|
||||||
|
if len(tag) == 0 {
|
||||||
|
fmt.Println("Release tag needed to delete")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
release, err := getReleaseByTag(owner, repo, tag, client)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if release == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = client.DeleteRelease(owner, repo, release.ID)
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package releases
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/tea/cmd/flags"
|
||||||
|
"code.gitea.io/tea/modules/config"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdReleaseEdit represents a sub command of Release to edit releases
|
||||||
|
var CmdReleaseEdit = cli.Command{
|
||||||
|
Name: "edit",
|
||||||
|
Usage: "Edit a release",
|
||||||
|
Description: `Edit a release`,
|
||||||
|
ArgsUsage: "<release tag>",
|
||||||
|
Action: runReleaseEdit,
|
||||||
|
Flags: append([]cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "tag",
|
||||||
|
Usage: "Change Tag",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "target",
|
||||||
|
Usage: "Change Target",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "title",
|
||||||
|
Aliases: []string{"t"},
|
||||||
|
Usage: "Change Title",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "note",
|
||||||
|
Aliases: []string{"n"},
|
||||||
|
Usage: "Change Notes",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "draft",
|
||||||
|
Aliases: []string{"d"},
|
||||||
|
Usage: "Mark as Draft [True/false]",
|
||||||
|
DefaultText: "true",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "prerelease",
|
||||||
|
Aliases: []string{"p"},
|
||||||
|
Usage: "Mark as Pre-Release [True/false]",
|
||||||
|
DefaultText: "true",
|
||||||
|
},
|
||||||
|
}, flags.AllDefaultFlags...),
|
||||||
|
}
|
||||||
|
|
||||||
|
func runReleaseEdit(ctx *cli.Context) error {
|
||||||
|
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||||
|
client := login.Client()
|
||||||
|
|
||||||
|
tag := ctx.Args().First()
|
||||||
|
if len(tag) == 0 {
|
||||||
|
fmt.Println("Release tag needed to edit")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
release, err := getReleaseByTag(owner, repo, tag, client)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if release == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var isDraft, isPre *bool
|
||||||
|
bTrue := true
|
||||||
|
bFalse := false
|
||||||
|
if ctx.IsSet("draft") {
|
||||||
|
isDraft = &bFalse
|
||||||
|
if strings.ToLower(ctx.String("draft"))[:1] == "t" {
|
||||||
|
isDraft = &bTrue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ctx.IsSet("prerelease") {
|
||||||
|
isPre = &bFalse
|
||||||
|
if strings.ToLower(ctx.String("prerelease"))[:1] == "t" {
|
||||||
|
isPre = &bTrue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = client.EditRelease(owner, repo, release.ID, gitea.EditReleaseOption{
|
||||||
|
TagName: ctx.String("tag"),
|
||||||
|
Target: ctx.String("target"),
|
||||||
|
Title: ctx.String("title"),
|
||||||
|
Note: ctx.String("note"),
|
||||||
|
IsDraft: isDraft,
|
||||||
|
IsPrerelease: isPre,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package releases
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"code.gitea.io/tea/cmd/flags"
|
||||||
|
"code.gitea.io/tea/modules/config"
|
||||||
|
"code.gitea.io/tea/modules/print"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdReleaseList represents a sub command of Release to list releases
|
||||||
|
var CmdReleaseList = cli.Command{
|
||||||
|
Name: "ls",
|
||||||
|
Usage: "List Releases",
|
||||||
|
Description: "List Releases",
|
||||||
|
Action: RunReleasesList,
|
||||||
|
Flags: append([]cli.Flag{
|
||||||
|
&flags.PaginationPageFlag,
|
||||||
|
&flags.PaginationLimitFlag,
|
||||||
|
}, flags.AllDefaultFlags...),
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunReleasesList list releases
|
||||||
|
func RunReleasesList(ctx *cli.Context) error {
|
||||||
|
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||||
|
|
||||||
|
releases, _, err := login.Client().ListReleases(owner, repo, gitea.ListReleasesOptions{ListOptions: flags.GetListOptions(ctx)})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := []string{
|
||||||
|
"Tag-Name",
|
||||||
|
"Title",
|
||||||
|
"Published At",
|
||||||
|
"Status",
|
||||||
|
"Tar URL",
|
||||||
|
}
|
||||||
|
|
||||||
|
var values [][]string
|
||||||
|
|
||||||
|
if len(releases) == 0 {
|
||||||
|
print.OutputList(flags.GlobalOutputValue, headers, values)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, release := range releases {
|
||||||
|
status := "released"
|
||||||
|
if release.IsDraft {
|
||||||
|
status = "draft"
|
||||||
|
} else if release.IsPrerelease {
|
||||||
|
status = "prerelease"
|
||||||
|
}
|
||||||
|
values = append(
|
||||||
|
values,
|
||||||
|
[]string{
|
||||||
|
release.TagName,
|
||||||
|
release.Title,
|
||||||
|
release.PublishedAt.Format("2006-01-02 15:04:05"),
|
||||||
|
status,
|
||||||
|
release.TarURL,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
print.OutputList(flags.GlobalOutputValue, headers, values)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getReleaseByTag(owner, repo, tag string, client *gitea.Client) (*gitea.Release, error) {
|
||||||
|
rl, _, err := client.ListReleases(owner, repo, gitea.ListReleasesOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(rl) == 0 {
|
||||||
|
fmt.Println("Repo does not have any release")
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
for _, r := range rl {
|
||||||
|
if r.TagName == tag {
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println("Release tag does not exist")
|
||||||
|
return nil, nil
|
||||||
|
}
|
289
cmd/repos.go
289
cmd/repos.go
|
@ -5,12 +5,10 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"code.gitea.io/tea/cmd/flags"
|
||||||
"log"
|
"code.gitea.io/tea/cmd/repos"
|
||||||
"net/http"
|
"code.gitea.io/tea/modules/config"
|
||||||
"strings"
|
"code.gitea.io/tea/modules/print"
|
||||||
|
|
||||||
"code.gitea.io/tea/modules/utils"
|
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
@ -24,224 +22,23 @@ var CmdRepos = cli.Command{
|
||||||
ArgsUsage: "[<repo owner>/<repo name>]",
|
ArgsUsage: "[<repo owner>/<repo name>]",
|
||||||
Action: runRepos,
|
Action: runRepos,
|
||||||
Subcommands: []*cli.Command{
|
Subcommands: []*cli.Command{
|
||||||
&CmdReposList,
|
&repos.CmdReposList,
|
||||||
&CmdRepoCreate,
|
&repos.CmdRepoCreate,
|
||||||
},
|
},
|
||||||
Flags: LoginOutputFlags,
|
Flags: flags.LoginOutputFlags,
|
||||||
}
|
|
||||||
|
|
||||||
// CmdReposList represents a sub command of repos to list them
|
|
||||||
var CmdReposList = cli.Command{
|
|
||||||
Name: "ls",
|
|
||||||
Usage: "List available repositories",
|
|
||||||
Description: `List available repositories`,
|
|
||||||
Action: runReposList,
|
|
||||||
Flags: append([]cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "mode",
|
|
||||||
Aliases: []string{"m"},
|
|
||||||
Required: false,
|
|
||||||
Usage: "Filter by mode: fork, mirror, source",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "owner",
|
|
||||||
Aliases: []string{"O"},
|
|
||||||
Required: false,
|
|
||||||
Usage: "Filter by owner",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "private",
|
|
||||||
Required: false,
|
|
||||||
Usage: "Filter private repos (true|false)",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "archived",
|
|
||||||
Required: false,
|
|
||||||
Usage: "Filter archived repos (true|false)",
|
|
||||||
},
|
|
||||||
&PaginationPageFlag,
|
|
||||||
&PaginationLimitFlag,
|
|
||||||
}, LoginOutputFlags...),
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdRepoCreate represents a sub command of repos to create one
|
|
||||||
var CmdRepoCreate = cli.Command{
|
|
||||||
Name: "create",
|
|
||||||
Aliases: []string{"c"},
|
|
||||||
Usage: "Create a repository",
|
|
||||||
Description: "Create a repository",
|
|
||||||
Action: runRepoCreate,
|
|
||||||
Flags: append([]cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "name",
|
|
||||||
Aliases: []string{""},
|
|
||||||
Required: true,
|
|
||||||
Usage: "name of new repo",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "owner",
|
|
||||||
Aliases: []string{"O"},
|
|
||||||
Required: false,
|
|
||||||
Usage: "name of repo owner",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "private",
|
|
||||||
Required: false,
|
|
||||||
Value: false,
|
|
||||||
Usage: "make repo private",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "description",
|
|
||||||
Aliases: []string{"desc"},
|
|
||||||
Required: false,
|
|
||||||
Usage: "add description to repo",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "init",
|
|
||||||
Required: false,
|
|
||||||
Value: false,
|
|
||||||
Usage: "initialize repo",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "labels",
|
|
||||||
Required: false,
|
|
||||||
Usage: "name of label set to add",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "gitignores",
|
|
||||||
Aliases: []string{"git"},
|
|
||||||
Required: false,
|
|
||||||
Usage: "list of gitignore templates (need --init)",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "license",
|
|
||||||
Required: false,
|
|
||||||
Usage: "add license (need --init)",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "readme",
|
|
||||||
Required: false,
|
|
||||||
Usage: "use readme template (need --init)",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "branch",
|
|
||||||
Required: false,
|
|
||||||
Usage: "use custom default branch (need --init)",
|
|
||||||
},
|
|
||||||
}, LoginOutputFlags...),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runRepos(ctx *cli.Context) error {
|
func runRepos(ctx *cli.Context) error {
|
||||||
if ctx.Args().Len() == 1 {
|
if ctx.Args().Len() == 1 {
|
||||||
return runRepoDetail(ctx, ctx.Args().First())
|
return runRepoDetail(ctx.Args().First())
|
||||||
}
|
}
|
||||||
return runReposList(ctx)
|
return repos.RunReposList(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// runReposList list repositories
|
func runRepoDetail(path string) error {
|
||||||
func runReposList(ctx *cli.Context) error {
|
login := config.InitCommandLoginOnly(flags.GlobalLoginValue)
|
||||||
login := initCommandLoginOnly()
|
|
||||||
client := login.Client()
|
client := login.Client()
|
||||||
|
repoOwner, repoName := config.GetOwnerAndRepo(path, login.User)
|
||||||
var ownerID int64
|
|
||||||
if ctx.IsSet("owner") {
|
|
||||||
// test if owner is a organisation
|
|
||||||
org, resp, err := client.GetOrg(ctx.String("owner"))
|
|
||||||
if err != nil {
|
|
||||||
if resp == nil || resp.StatusCode != http.StatusNotFound {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// if owner is no org, its a user
|
|
||||||
user, _, err := client.GetUserInfo(ctx.String("owner"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ownerID = user.ID
|
|
||||||
} else {
|
|
||||||
ownerID = org.ID
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
me, _, err := client.GetMyUserInfo()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ownerID = me.ID
|
|
||||||
}
|
|
||||||
|
|
||||||
var isArchived *bool
|
|
||||||
if ctx.IsSet("archived") {
|
|
||||||
archived := strings.ToLower(ctx.String("archived"))[:1] == "t"
|
|
||||||
isArchived = &archived
|
|
||||||
}
|
|
||||||
|
|
||||||
var isPrivate *bool
|
|
||||||
if ctx.IsSet("private") {
|
|
||||||
private := strings.ToLower(ctx.String("private"))[:1] == "t"
|
|
||||||
isArchived = &private
|
|
||||||
}
|
|
||||||
|
|
||||||
mode := gitea.RepoTypeNone
|
|
||||||
switch ctx.String("mode") {
|
|
||||||
case "fork":
|
|
||||||
mode = gitea.RepoTypeFork
|
|
||||||
case "mirror":
|
|
||||||
mode = gitea.RepoTypeMirror
|
|
||||||
case "source":
|
|
||||||
mode = gitea.RepoTypeSource
|
|
||||||
}
|
|
||||||
|
|
||||||
rps, _, err := client.SearchRepos(gitea.SearchRepoOptions{
|
|
||||||
ListOptions: getListOptions(ctx),
|
|
||||||
OwnerID: ownerID,
|
|
||||||
IsPrivate: isPrivate,
|
|
||||||
IsArchived: isArchived,
|
|
||||||
Type: mode,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(rps) == 0 {
|
|
||||||
log.Fatal("No repositories found", rps)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
headers := []string{
|
|
||||||
"Name",
|
|
||||||
"Type",
|
|
||||||
"SSH",
|
|
||||||
"Owner",
|
|
||||||
}
|
|
||||||
var values [][]string
|
|
||||||
|
|
||||||
for _, rp := range rps {
|
|
||||||
var mode = "source"
|
|
||||||
if rp.Fork {
|
|
||||||
mode = "fork"
|
|
||||||
}
|
|
||||||
if rp.Mirror {
|
|
||||||
mode = "mirror"
|
|
||||||
}
|
|
||||||
|
|
||||||
values = append(
|
|
||||||
values,
|
|
||||||
[]string{
|
|
||||||
rp.FullName,
|
|
||||||
mode,
|
|
||||||
rp.SSHURL,
|
|
||||||
rp.Owner.UserName,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Output(outputValue, headers, values)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func runRepoDetail(_ *cli.Context, path string) error {
|
|
||||||
login := initCommandLoginOnly()
|
|
||||||
client := login.Client()
|
|
||||||
repoOwner, repoName := getOwnerAndRepo(path, login.User)
|
|
||||||
repo, _, err := client.GetRepo(repoOwner, repoName)
|
repo, _, err := client.GetRepo(repoOwner, repoName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -251,66 +48,6 @@ func runRepoDetail(_ *cli.Context, path string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
output := repo.FullName
|
print.RepoDetails(repo, topics)
|
||||||
if repo.Mirror {
|
|
||||||
output += " (mirror)"
|
|
||||||
}
|
|
||||||
if repo.Fork {
|
|
||||||
output += " (fork)"
|
|
||||||
}
|
|
||||||
if repo.Archived {
|
|
||||||
output += " (archived)"
|
|
||||||
}
|
|
||||||
if repo.Empty {
|
|
||||||
output += " (empty)"
|
|
||||||
}
|
|
||||||
output += "\n"
|
|
||||||
if len(topics) != 0 {
|
|
||||||
output += "Topics: " + strings.Join(topics, ", ") + "\n"
|
|
||||||
}
|
|
||||||
output += "\n"
|
|
||||||
output += repo.Description + "\n\n"
|
|
||||||
output += fmt.Sprintf(
|
|
||||||
"Open Issues: %d, Stars: %d, Forks: %d, Size: %s\n\n",
|
|
||||||
repo.OpenIssues,
|
|
||||||
repo.Stars,
|
|
||||||
repo.Forks,
|
|
||||||
utils.FormatSize(int64(repo.Size)),
|
|
||||||
)
|
|
||||||
|
|
||||||
fmt.Print(output)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func runRepoCreate(ctx *cli.Context) error {
|
|
||||||
login := initCommandLoginOnly()
|
|
||||||
client := login.Client()
|
|
||||||
var (
|
|
||||||
repo *gitea.Repository
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
opts := gitea.CreateRepoOption{
|
|
||||||
Name: ctx.String("name"),
|
|
||||||
Description: ctx.String("description"),
|
|
||||||
Private: ctx.Bool("private"),
|
|
||||||
AutoInit: ctx.Bool("init"),
|
|
||||||
IssueLabels: ctx.String("labels"),
|
|
||||||
Gitignores: ctx.String("gitignores"),
|
|
||||||
License: ctx.String("license"),
|
|
||||||
Readme: ctx.String("readme"),
|
|
||||||
DefaultBranch: ctx.String("branch"),
|
|
||||||
}
|
|
||||||
if len(ctx.String("owner")) != 0 {
|
|
||||||
repo, _, err = client.CreateOrgRepo(ctx.String("owner"), opts)
|
|
||||||
} else {
|
|
||||||
repo, _, err = client.CreateRepo(opts)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = runRepoDetail(ctx, repo.FullName); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Printf("%s\n", repo.HTMLURL)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package repos
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"code.gitea.io/tea/cmd/flags"
|
||||||
|
"code.gitea.io/tea/modules/config"
|
||||||
|
"code.gitea.io/tea/modules/print"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdRepoCreate represents a sub command of repos to create one
|
||||||
|
var CmdRepoCreate = cli.Command{
|
||||||
|
Name: "create",
|
||||||
|
Aliases: []string{"c"},
|
||||||
|
Usage: "Create a repository",
|
||||||
|
Description: "Create a repository",
|
||||||
|
Action: runRepoCreate,
|
||||||
|
Flags: append([]cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "name",
|
||||||
|
Aliases: []string{""},
|
||||||
|
Required: true,
|
||||||
|
Usage: "name of new repo",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "owner",
|
||||||
|
Aliases: []string{"O"},
|
||||||
|
Required: false,
|
||||||
|
Usage: "name of repo owner",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "private",
|
||||||
|
Required: false,
|
||||||
|
Value: false,
|
||||||
|
Usage: "make repo private",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "description",
|
||||||
|
Aliases: []string{"desc"},
|
||||||
|
Required: false,
|
||||||
|
Usage: "add description to repo",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "init",
|
||||||
|
Required: false,
|
||||||
|
Value: false,
|
||||||
|
Usage: "initialize repo",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "labels",
|
||||||
|
Required: false,
|
||||||
|
Usage: "name of label set to add",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "gitignores",
|
||||||
|
Aliases: []string{"git"},
|
||||||
|
Required: false,
|
||||||
|
Usage: "list of gitignore templates (need --init)",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "license",
|
||||||
|
Required: false,
|
||||||
|
Usage: "add license (need --init)",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "readme",
|
||||||
|
Required: false,
|
||||||
|
Usage: "use readme template (need --init)",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "branch",
|
||||||
|
Required: false,
|
||||||
|
Usage: "use custom default branch (need --init)",
|
||||||
|
},
|
||||||
|
}, flags.LoginOutputFlags...),
|
||||||
|
}
|
||||||
|
|
||||||
|
func runRepoCreate(ctx *cli.Context) error {
|
||||||
|
login := config.InitCommandLoginOnly(flags.GlobalLoginValue)
|
||||||
|
client := login.Client()
|
||||||
|
var (
|
||||||
|
repo *gitea.Repository
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
opts := gitea.CreateRepoOption{
|
||||||
|
Name: ctx.String("name"),
|
||||||
|
Description: ctx.String("description"),
|
||||||
|
Private: ctx.Bool("private"),
|
||||||
|
AutoInit: ctx.Bool("init"),
|
||||||
|
IssueLabels: ctx.String("labels"),
|
||||||
|
Gitignores: ctx.String("gitignores"),
|
||||||
|
License: ctx.String("license"),
|
||||||
|
Readme: ctx.String("readme"),
|
||||||
|
DefaultBranch: ctx.String("branch"),
|
||||||
|
}
|
||||||
|
if len(ctx.String("owner")) != 0 {
|
||||||
|
repo, _, err = client.CreateOrgRepo(ctx.String("owner"), opts)
|
||||||
|
} else {
|
||||||
|
repo, _, err = client.CreateRepo(opts)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
topics, _, err := client.ListRepoTopics(repo.Owner.UserName, repo.Name, gitea.ListRepoTopicsOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
print.RepoDetails(repo, topics)
|
||||||
|
|
||||||
|
fmt.Printf("%s\n", repo.HTMLURL)
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,152 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package repos
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/tea/cmd/flags"
|
||||||
|
"code.gitea.io/tea/modules/config"
|
||||||
|
"code.gitea.io/tea/modules/print"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdReposList represents a sub command of repos to list them
|
||||||
|
var CmdReposList = cli.Command{
|
||||||
|
Name: "ls",
|
||||||
|
Usage: "List available repositories",
|
||||||
|
Description: `List available repositories`,
|
||||||
|
Action: RunReposList,
|
||||||
|
Flags: append([]cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "mode",
|
||||||
|
Aliases: []string{"m"},
|
||||||
|
Required: false,
|
||||||
|
Usage: "Filter by mode: fork, mirror, source",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "owner",
|
||||||
|
Aliases: []string{"O"},
|
||||||
|
Required: false,
|
||||||
|
Usage: "Filter by owner",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "private",
|
||||||
|
Required: false,
|
||||||
|
Usage: "Filter private repos (true|false)",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "archived",
|
||||||
|
Required: false,
|
||||||
|
Usage: "Filter archived repos (true|false)",
|
||||||
|
},
|
||||||
|
&flags.PaginationPageFlag,
|
||||||
|
&flags.PaginationLimitFlag,
|
||||||
|
}, flags.LoginOutputFlags...),
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunReposList list repositories
|
||||||
|
func RunReposList(ctx *cli.Context) error {
|
||||||
|
login := config.InitCommandLoginOnly(flags.GlobalLoginValue)
|
||||||
|
client := login.Client()
|
||||||
|
|
||||||
|
var ownerID int64
|
||||||
|
if ctx.IsSet("owner") {
|
||||||
|
// test if owner is a organisation
|
||||||
|
org, resp, err := client.GetOrg(ctx.String("owner"))
|
||||||
|
if err != nil {
|
||||||
|
if resp == nil || resp.StatusCode != http.StatusNotFound {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// if owner is no org, its a user
|
||||||
|
user, _, err := client.GetUserInfo(ctx.String("owner"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ownerID = user.ID
|
||||||
|
} else {
|
||||||
|
ownerID = org.ID
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
me, _, err := client.GetMyUserInfo()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ownerID = me.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
var isArchived *bool
|
||||||
|
if ctx.IsSet("archived") {
|
||||||
|
archived := strings.ToLower(ctx.String("archived"))[:1] == "t"
|
||||||
|
isArchived = &archived
|
||||||
|
}
|
||||||
|
|
||||||
|
var isPrivate *bool
|
||||||
|
if ctx.IsSet("private") {
|
||||||
|
private := strings.ToLower(ctx.String("private"))[:1] == "t"
|
||||||
|
isArchived = &private
|
||||||
|
}
|
||||||
|
|
||||||
|
mode := gitea.RepoTypeNone
|
||||||
|
switch ctx.String("mode") {
|
||||||
|
case "fork":
|
||||||
|
mode = gitea.RepoTypeFork
|
||||||
|
case "mirror":
|
||||||
|
mode = gitea.RepoTypeMirror
|
||||||
|
case "source":
|
||||||
|
mode = gitea.RepoTypeSource
|
||||||
|
}
|
||||||
|
|
||||||
|
rps, _, err := client.SearchRepos(gitea.SearchRepoOptions{
|
||||||
|
ListOptions: flags.GetListOptions(ctx),
|
||||||
|
OwnerID: ownerID,
|
||||||
|
IsPrivate: isPrivate,
|
||||||
|
IsArchived: isArchived,
|
||||||
|
Type: mode,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(rps) == 0 {
|
||||||
|
log.Fatal("No repositories found", rps)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := []string{
|
||||||
|
"Name",
|
||||||
|
"Type",
|
||||||
|
"SSH",
|
||||||
|
"Owner",
|
||||||
|
}
|
||||||
|
var values [][]string
|
||||||
|
|
||||||
|
for _, rp := range rps {
|
||||||
|
var mode = "source"
|
||||||
|
if rp.Fork {
|
||||||
|
mode = "fork"
|
||||||
|
}
|
||||||
|
if rp.Mirror {
|
||||||
|
mode = "mirror"
|
||||||
|
}
|
||||||
|
|
||||||
|
values = append(
|
||||||
|
values,
|
||||||
|
[]string{
|
||||||
|
rp.FullName,
|
||||||
|
mode,
|
||||||
|
rp.SSHURL,
|
||||||
|
rp.Owner.UserName,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
print.OutputList(flags.GlobalOutputValue, headers, values)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
192
cmd/times.go
192
cmd/times.go
|
@ -6,13 +6,16 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/tea/cmd/flags"
|
||||||
|
"code.gitea.io/tea/cmd/times"
|
||||||
|
"code.gitea.io/tea/modules/config"
|
||||||
|
"code.gitea.io/tea/modules/print"
|
||||||
|
"code.gitea.io/tea/modules/utils"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
"github.com/araddon/dateparse"
|
"github.com/araddon/dateparse"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
@ -28,9 +31,9 @@ var CmdTrackedTimes = cli.Command{
|
||||||
ArgsUsage: "[username | #issue]",
|
ArgsUsage: "[username | #issue]",
|
||||||
Action: runTrackedTimes,
|
Action: runTrackedTimes,
|
||||||
Subcommands: []*cli.Command{
|
Subcommands: []*cli.Command{
|
||||||
&CmdTrackedTimesAdd,
|
×.CmdTrackedTimesAdd,
|
||||||
&CmdTrackedTimesDelete,
|
×.CmdTrackedTimesDelete,
|
||||||
&CmdTrackedTimesReset,
|
×.CmdTrackedTimesReset,
|
||||||
},
|
},
|
||||||
Flags: append([]cli.Flag{
|
Flags: append([]cli.Flag{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
|
@ -48,11 +51,11 @@ var CmdTrackedTimes = cli.Command{
|
||||||
Aliases: []string{"t"},
|
Aliases: []string{"t"},
|
||||||
Usage: "Print the total duration at the end",
|
Usage: "Print the total duration at the end",
|
||||||
},
|
},
|
||||||
}, AllDefaultFlags...),
|
}, flags.AllDefaultFlags...),
|
||||||
}
|
}
|
||||||
|
|
||||||
func runTrackedTimes(ctx *cli.Context) error {
|
func runTrackedTimes(ctx *cli.Context) error {
|
||||||
login, owner, repo := initCommand()
|
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||||
client := login.Client()
|
client := login.Client()
|
||||||
|
|
||||||
if err := client.CheckServerVersionConstraint(">= 1.11"); err != nil {
|
if err := client.CheckServerVersionConstraint(">= 1.11"); err != nil {
|
||||||
|
@ -69,7 +72,7 @@ func runTrackedTimes(ctx *cli.Context) error {
|
||||||
times, _, err = client.GetRepoTrackedTimes(owner, repo)
|
times, _, err = client.GetRepoTrackedTimes(owner, repo)
|
||||||
} else if strings.HasPrefix(user, "#") {
|
} else if strings.HasPrefix(user, "#") {
|
||||||
// get all tracked times on the specified issue
|
// get all tracked times on the specified issue
|
||||||
issue, err := argToIndex(user)
|
issue, err := utils.ArgToIndex(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -97,175 +100,6 @@ func runTrackedTimes(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
printTrackedTimes(times, outputValue, from, until, ctx.Bool("total"))
|
print.TrackedTimesList(times, flags.GlobalOutputValue, from, until, ctx.Bool("total"))
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatDuration(seconds int64, outputType string) string {
|
|
||||||
switch outputType {
|
|
||||||
case "yaml":
|
|
||||||
case "csv":
|
|
||||||
return fmt.Sprint(seconds)
|
|
||||||
}
|
|
||||||
return time.Duration(1e9 * seconds).String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func printTrackedTimes(times []*gitea.TrackedTime, outputType string, from, until time.Time, printTotal bool) {
|
|
||||||
var outputValues [][]string
|
|
||||||
var totalDuration int64
|
|
||||||
|
|
||||||
localLoc, err := time.LoadLocation("Local") // local timezone for time formatting
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, t := range times {
|
|
||||||
if !from.IsZero() && from.After(t.Created) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !until.IsZero() && until.Before(t.Created) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
totalDuration += t.Time
|
|
||||||
|
|
||||||
outputValues = append(
|
|
||||||
outputValues,
|
|
||||||
[]string{
|
|
||||||
t.Created.In(localLoc).Format("2006-01-02 15:04:05"),
|
|
||||||
"#" + strconv.FormatInt(t.Issue.Index, 10),
|
|
||||||
t.UserName,
|
|
||||||
formatDuration(t.Time, outputType),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if printTotal {
|
|
||||||
outputValues = append(outputValues, []string{
|
|
||||||
"TOTAL", "", "", formatDuration(totalDuration, outputType),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
headers := []string{
|
|
||||||
"Created",
|
|
||||||
"Issue",
|
|
||||||
"User",
|
|
||||||
"Duration",
|
|
||||||
}
|
|
||||||
Output(outputType, headers, outputValues)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdTrackedTimesAdd represents a sub command of times to add time to an issue
|
|
||||||
var CmdTrackedTimesAdd = cli.Command{
|
|
||||||
Name: "add",
|
|
||||||
Usage: "Track spent time on an issue",
|
|
||||||
UsageText: "tea times add <issue> <duration>",
|
|
||||||
Description: `Track spent time on an issue
|
|
||||||
Example:
|
|
||||||
tea times add 1 1h25m
|
|
||||||
`,
|
|
||||||
Action: runTrackedTimesAdd,
|
|
||||||
Flags: LoginRepoFlags,
|
|
||||||
}
|
|
||||||
|
|
||||||
func runTrackedTimesAdd(ctx *cli.Context) error {
|
|
||||||
login, owner, repo := initCommand()
|
|
||||||
|
|
||||||
if ctx.Args().Len() < 2 {
|
|
||||||
return fmt.Errorf("No issue or duration specified.\nUsage:\t%s", ctx.Command.UsageText)
|
|
||||||
}
|
|
||||||
|
|
||||||
issue, err := argToIndex(ctx.Args().First())
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
duration, err := time.ParseDuration(strings.Join(ctx.Args().Tail(), ""))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, err = login.Client().AddTime(owner, repo, issue, gitea.AddTimeOption{
|
|
||||||
Time: int64(duration.Seconds()),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdTrackedTimesDelete is a sub command of CmdTrackedTimes, and removes time from an issue
|
|
||||||
var CmdTrackedTimesDelete = cli.Command{
|
|
||||||
Name: "delete",
|
|
||||||
Aliases: []string{"rm"},
|
|
||||||
Usage: "Delete a single tracked time on an issue",
|
|
||||||
UsageText: "tea times delete <issue> <time ID>",
|
|
||||||
Action: runTrackedTimesDelete,
|
|
||||||
Flags: LoginRepoFlags,
|
|
||||||
}
|
|
||||||
|
|
||||||
func runTrackedTimesDelete(ctx *cli.Context) error {
|
|
||||||
login, owner, repo := initCommand()
|
|
||||||
client := login.Client()
|
|
||||||
|
|
||||||
if err := client.CheckServerVersionConstraint(">= 1.11"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.Args().Len() < 2 {
|
|
||||||
return fmt.Errorf("No issue or time ID specified.\nUsage:\t%s", ctx.Command.UsageText)
|
|
||||||
}
|
|
||||||
|
|
||||||
issue, err := argToIndex(ctx.Args().First())
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
timeID, err := strconv.ParseInt(ctx.Args().Get(1), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = client.DeleteTime(owner, repo, issue, timeID)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdTrackedTimesReset is a subcommand of CmdTrackedTimes, and
|
|
||||||
// clears all tracked times on an issue.
|
|
||||||
var CmdTrackedTimesReset = cli.Command{
|
|
||||||
Name: "reset",
|
|
||||||
Usage: "Reset tracked time on an issue",
|
|
||||||
UsageText: "tea times reset <issue>",
|
|
||||||
Action: runTrackedTimesReset,
|
|
||||||
Flags: LoginRepoFlags,
|
|
||||||
}
|
|
||||||
|
|
||||||
func runTrackedTimesReset(ctx *cli.Context) error {
|
|
||||||
login, owner, repo := initCommand()
|
|
||||||
client := login.Client()
|
|
||||||
|
|
||||||
if err := client.CheckServerVersionConstraint(">= 1.11"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.Args().Len() != 1 {
|
|
||||||
return fmt.Errorf("No issue specified.\nUsage:\t%s", ctx.Command.UsageText)
|
|
||||||
}
|
|
||||||
|
|
||||||
issue, err := argToIndex(ctx.Args().First())
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = client.ResetIssueTime(owner, repo, issue)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package times
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.gitea.io/tea/cmd/flags"
|
||||||
|
"code.gitea.io/tea/modules/config"
|
||||||
|
"code.gitea.io/tea/modules/utils"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdTrackedTimesAdd represents a sub command of times to add time to an issue
|
||||||
|
var CmdTrackedTimesAdd = cli.Command{
|
||||||
|
Name: "add",
|
||||||
|
Usage: "Track spent time on an issue",
|
||||||
|
UsageText: "tea times add <issue> <duration>",
|
||||||
|
Description: `Track spent time on an issue
|
||||||
|
Example:
|
||||||
|
tea times add 1 1h25m
|
||||||
|
`,
|
||||||
|
Action: runTrackedTimesAdd,
|
||||||
|
Flags: flags.LoginRepoFlags,
|
||||||
|
}
|
||||||
|
|
||||||
|
func runTrackedTimesAdd(ctx *cli.Context) error {
|
||||||
|
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||||
|
|
||||||
|
if ctx.Args().Len() < 2 {
|
||||||
|
return fmt.Errorf("No issue or duration specified.\nUsage:\t%s", ctx.Command.UsageText)
|
||||||
|
}
|
||||||
|
|
||||||
|
issue, err := utils.ArgToIndex(ctx.Args().First())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
duration, err := time.ParseDuration(strings.Join(ctx.Args().Tail(), ""))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = login.Client().AddTime(owner, repo, issue, gitea.AddTimeOption{
|
||||||
|
Time: int64(duration.Seconds()),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package times
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"code.gitea.io/tea/cmd/flags"
|
||||||
|
"code.gitea.io/tea/modules/config"
|
||||||
|
"code.gitea.io/tea/modules/utils"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdTrackedTimesDelete is a sub command of CmdTrackedTimes, and removes time from an issue
|
||||||
|
var CmdTrackedTimesDelete = cli.Command{
|
||||||
|
Name: "delete",
|
||||||
|
Aliases: []string{"rm"},
|
||||||
|
Usage: "Delete a single tracked time on an issue",
|
||||||
|
UsageText: "tea times delete <issue> <time ID>",
|
||||||
|
Action: runTrackedTimesDelete,
|
||||||
|
Flags: flags.LoginRepoFlags,
|
||||||
|
}
|
||||||
|
|
||||||
|
func runTrackedTimesDelete(ctx *cli.Context) error {
|
||||||
|
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||||
|
client := login.Client()
|
||||||
|
|
||||||
|
if err := client.CheckServerVersionConstraint(">= 1.11"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.Args().Len() < 2 {
|
||||||
|
return fmt.Errorf("No issue or time ID specified.\nUsage:\t%s", ctx.Command.UsageText)
|
||||||
|
}
|
||||||
|
|
||||||
|
issue, err := utils.ArgToIndex(ctx.Args().First())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
timeID, err := strconv.ParseInt(ctx.Args().Get(1), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = client.DeleteTime(owner, repo, issue, timeID)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package times
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"code.gitea.io/tea/cmd/flags"
|
||||||
|
"code.gitea.io/tea/modules/config"
|
||||||
|
"code.gitea.io/tea/modules/utils"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdTrackedTimesReset is a subcommand of CmdTrackedTimes, and
|
||||||
|
// clears all tracked times on an issue.
|
||||||
|
var CmdTrackedTimesReset = cli.Command{
|
||||||
|
Name: "reset",
|
||||||
|
Usage: "Reset tracked time on an issue",
|
||||||
|
UsageText: "tea times reset <issue>",
|
||||||
|
Action: runTrackedTimesReset,
|
||||||
|
Flags: flags.LoginRepoFlags,
|
||||||
|
}
|
||||||
|
|
||||||
|
func runTrackedTimesReset(ctx *cli.Context) error {
|
||||||
|
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||||
|
client := login.Client()
|
||||||
|
|
||||||
|
if err := client.CheckServerVersionConstraint(">= 1.11"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.Args().Len() != 1 {
|
||||||
|
return fmt.Errorf("No issue specified.\nUsage:\t%s", ctx.Command.UsageText)
|
||||||
|
}
|
||||||
|
|
||||||
|
issue, err := utils.ArgToIndex(ctx.Args().First())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = client.ResetIssueTime(owner, repo, issue)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,160 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/tea/modules/git"
|
||||||
|
"code.gitea.io/tea/modules/utils"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LocalConfig represents local configurations
|
||||||
|
type LocalConfig struct {
|
||||||
|
Logins []Login `yaml:"logins"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Config contain if loaded local tea config
|
||||||
|
Config LocalConfig
|
||||||
|
yamlConfigPath string
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: do not use init function to detect the tea configuration, use GetConfigPath()
|
||||||
|
func init() {
|
||||||
|
homeDir, err := utils.Home()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Retrieve home dir failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
dir := filepath.Join(homeDir, ".tea")
|
||||||
|
err = os.MkdirAll(dir, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Init tea config dir " + dir + " failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
yamlConfigPath = filepath.Join(dir, "tea.yml")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfigPath return path to tea config file
|
||||||
|
func GetConfigPath() string {
|
||||||
|
return yamlConfigPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadConfig load config into global Config var
|
||||||
|
func LoadConfig() error {
|
||||||
|
ymlPath := GetConfigPath()
|
||||||
|
exist, _ := utils.FileExist(ymlPath)
|
||||||
|
if exist {
|
||||||
|
fmt.Println("Found config file", ymlPath)
|
||||||
|
bs, err := ioutil.ReadFile(ymlPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = yaml.Unmarshal(bs, &Config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveConfig save config from global Config var into config file
|
||||||
|
func SaveConfig() error {
|
||||||
|
ymlPath := GetConfigPath()
|
||||||
|
bs, err := yaml.Marshal(Config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return ioutil.WriteFile(ymlPath, bs, 0660)
|
||||||
|
}
|
||||||
|
|
||||||
|
func curGitRepoPath(repoValue, remoteValue string) (*Login, string, error) {
|
||||||
|
var err error
|
||||||
|
var repo *git.TeaRepo
|
||||||
|
if len(repoValue) == 0 {
|
||||||
|
repo, err = git.RepoForWorkdir()
|
||||||
|
} else {
|
||||||
|
repo, err = git.RepoFromPath(repoValue)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
gitConfig, err := repo.Config()
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// if no remote
|
||||||
|
if len(gitConfig.Remotes) == 0 {
|
||||||
|
return nil, "", errors.New("No remote(s) found in this Git repository")
|
||||||
|
}
|
||||||
|
|
||||||
|
// if only one remote exists
|
||||||
|
if len(gitConfig.Remotes) >= 1 && len(remoteValue) == 0 {
|
||||||
|
for remote := range gitConfig.Remotes {
|
||||||
|
remoteValue = remote
|
||||||
|
}
|
||||||
|
if len(gitConfig.Remotes) > 1 {
|
||||||
|
// if master branch is present, use it as the default remote
|
||||||
|
masterBranch, ok := gitConfig.Branches["master"]
|
||||||
|
if ok {
|
||||||
|
if len(masterBranch.Remote) > 0 {
|
||||||
|
remoteValue = masterBranch.Remote
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
remoteConfig, ok := gitConfig.Remotes[remoteValue]
|
||||||
|
if !ok || remoteConfig == nil {
|
||||||
|
return nil, "", errors.New("Remote " + remoteValue + " not found in this Git repository")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, l := range Config.Logins {
|
||||||
|
for _, u := range remoteConfig.URLs {
|
||||||
|
p, err := git.ParseURL(strings.TrimSpace(u))
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", fmt.Errorf("Git remote URL parse failed: %s", err.Error())
|
||||||
|
}
|
||||||
|
if strings.EqualFold(p.Scheme, "http") || strings.EqualFold(p.Scheme, "https") {
|
||||||
|
if strings.HasPrefix(u, l.URL) {
|
||||||
|
ps := strings.Split(p.Path, "/")
|
||||||
|
path := strings.Join(ps[len(ps)-2:], "/")
|
||||||
|
return &l, strings.TrimSuffix(path, ".git"), nil
|
||||||
|
}
|
||||||
|
} else if strings.EqualFold(p.Scheme, "ssh") {
|
||||||
|
if l.GetSSHHost() == strings.Split(p.Host, ":")[0] {
|
||||||
|
return &l, strings.TrimLeft(strings.TrimSuffix(p.Path, ".git"), "/"), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, "", errors.New("No Gitea login found. You might want to specify --repo (and --login) to work outside of a repository")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOwnerAndRepo return repoOwner and repoName
|
||||||
|
// based on relative path and default owner (if not in path)
|
||||||
|
func GetOwnerAndRepo(repoPath, user string) (string, string) {
|
||||||
|
if len(repoPath) == 0 {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
p := strings.Split(repoPath, "/")
|
||||||
|
if len(p) >= 2 {
|
||||||
|
return p[0], p[1]
|
||||||
|
}
|
||||||
|
return user, repoPath
|
||||||
|
}
|
|
@ -0,0 +1,282 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/http/cookiejar"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.gitea.io/tea/modules/utils"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Login represents a login to a gitea server, you even could add multiple logins for one gitea server
|
||||||
|
type Login struct {
|
||||||
|
Name string `yaml:"name"`
|
||||||
|
URL string `yaml:"url"`
|
||||||
|
Token string `yaml:"token"`
|
||||||
|
Default bool `yaml:"default"`
|
||||||
|
SSHHost string `yaml:"ssh_host"`
|
||||||
|
// optional path to the private key
|
||||||
|
SSHKey string `yaml:"ssh_key"`
|
||||||
|
Insecure bool `yaml:"insecure"`
|
||||||
|
// optional gitea username
|
||||||
|
User string `yaml:"user"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client returns a client to operate Gitea API
|
||||||
|
func (l *Login) Client() *gitea.Client {
|
||||||
|
httpClient := &http.Client{}
|
||||||
|
if l.Insecure {
|
||||||
|
cookieJar, _ := cookiejar.New(nil)
|
||||||
|
|
||||||
|
httpClient = &http.Client{
|
||||||
|
Jar: cookieJar,
|
||||||
|
Transport: &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := gitea.NewClient(l.URL,
|
||||||
|
gitea.SetToken(l.Token),
|
||||||
|
gitea.SetHTTPClient(httpClient),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSSHHost returns SSH host name
|
||||||
|
func (l *Login) GetSSHHost() string {
|
||||||
|
if l.SSHHost != "" {
|
||||||
|
return l.SSHHost
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(l.URL)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return u.Hostname()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDefaultLogin return the default login
|
||||||
|
func GetDefaultLogin() (*Login, error) {
|
||||||
|
if len(Config.Logins) == 0 {
|
||||||
|
return nil, errors.New("No available login")
|
||||||
|
}
|
||||||
|
for _, l := range Config.Logins {
|
||||||
|
if l.Default {
|
||||||
|
return &l, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Config.Logins[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLoginByName get login by name
|
||||||
|
func GetLoginByName(name string) *Login {
|
||||||
|
for _, l := range Config.Logins {
|
||||||
|
if l.Name == name {
|
||||||
|
return &l
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddLogin add login to config ( global var & file)
|
||||||
|
func AddLogin(name, token, user, passwd, sshKey, giteaURL string, insecure bool) error {
|
||||||
|
|
||||||
|
if len(giteaURL) == 0 {
|
||||||
|
log.Fatal("You have to input Gitea server URL")
|
||||||
|
}
|
||||||
|
if len(token) == 0 && (len(user)+len(passwd)) == 0 {
|
||||||
|
log.Fatal("No token set")
|
||||||
|
} else if len(user) != 0 && len(passwd) == 0 {
|
||||||
|
log.Fatal("No password set")
|
||||||
|
} else if len(user) == 0 && len(passwd) != 0 {
|
||||||
|
log.Fatal("No user set")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := LoadConfig()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
httpClient := &http.Client{}
|
||||||
|
if insecure {
|
||||||
|
cookieJar, _ := cookiejar.New(nil)
|
||||||
|
httpClient = &http.Client{
|
||||||
|
Jar: cookieJar,
|
||||||
|
Transport: &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
client, err := gitea.NewClient(giteaURL,
|
||||||
|
gitea.SetToken(token),
|
||||||
|
gitea.SetBasicAuth(user, passwd),
|
||||||
|
gitea.SetHTTPClient(httpClient),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
u, _, err := client.GetMyUserInfo()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(token) == 0 {
|
||||||
|
// create token
|
||||||
|
host, _ := os.Hostname()
|
||||||
|
tl, _, err := client.ListAccessTokens(gitea.ListAccessTokensOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tokenName := host + "-tea"
|
||||||
|
for i := range tl {
|
||||||
|
if tl[i].Name == tokenName {
|
||||||
|
tokenName += time.Now().Format("2006-01-02_15-04-05")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t, _, err := client.CreateAccessToken(gitea.CreateAccessTokenOption{Name: tokenName})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
token = t.Token
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Login successful! Login name " + u.UserName)
|
||||||
|
|
||||||
|
if len(name) == 0 {
|
||||||
|
parsedURL, err := url.Parse(giteaURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
name = strings.ReplaceAll(strings.Title(parsedURL.Host), ".", "")
|
||||||
|
for _, l := range Config.Logins {
|
||||||
|
if l.Name == name {
|
||||||
|
name += "_" + u.UserName
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = addLoginToConfig(Login{
|
||||||
|
Name: name,
|
||||||
|
URL: giteaURL,
|
||||||
|
Token: token,
|
||||||
|
Insecure: insecure,
|
||||||
|
SSHKey: sshKey,
|
||||||
|
User: u.UserName,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = SaveConfig()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// addLoginToConfig add a login to global Config var
|
||||||
|
func addLoginToConfig(login Login) error {
|
||||||
|
for _, l := range Config.Logins {
|
||||||
|
if l.Name == login.Name {
|
||||||
|
if l.URL == login.URL && l.Token == login.Token {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New("Login name has already been used")
|
||||||
|
}
|
||||||
|
if l.URL == login.URL && l.Token == login.Token {
|
||||||
|
return errors.New("URL has been added")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(login.URL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if login.SSHHost == "" {
|
||||||
|
login.SSHHost = u.Hostname()
|
||||||
|
}
|
||||||
|
Config.Logins = append(Config.Logins, login)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitCommand returns repository and *Login based on flags
|
||||||
|
func InitCommand(repoValue, loginValue, remoteValue string) (*Login, string, string) {
|
||||||
|
var login *Login
|
||||||
|
|
||||||
|
err := LoadConfig()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("load config file failed ", yamlConfigPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
if login, err = GetDefaultLogin(); err != nil {
|
||||||
|
log.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
exist, err := utils.PathExists(repoValue)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if exist || len(repoValue) == 0 {
|
||||||
|
login, repoValue, err = curGitRepoPath(repoValue, remoteValue)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if loginValue != "" {
|
||||||
|
login = GetLoginByName(loginValue)
|
||||||
|
if login == nil {
|
||||||
|
log.Fatal("Login name " + loginValue + " does not exist")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
owner, repo := GetOwnerAndRepo(repoValue, login.User)
|
||||||
|
return login, owner, repo
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitCommandLoginOnly return *Login based on flags
|
||||||
|
func InitCommandLoginOnly(loginValue string) *Login {
|
||||||
|
err := LoadConfig()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("load config file failed ", yamlConfigPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
var login *Login
|
||||||
|
if loginValue == "" {
|
||||||
|
login, err = GetDefaultLogin()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
login = GetLoginByName(loginValue)
|
||||||
|
if login == nil {
|
||||||
|
log.Fatal("Login name " + loginValue + " does not exist")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return login
|
||||||
|
}
|
|
@ -10,10 +10,10 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/tea/modules/utils"
|
||||||
|
|
||||||
git_transport "github.com/go-git/go-git/v5/plumbing/transport"
|
git_transport "github.com/go-git/go-git/v5/plumbing/transport"
|
||||||
gogit_http "github.com/go-git/go-git/v5/plumbing/transport/http"
|
gogit_http "github.com/go-git/go-git/v5/plumbing/transport/http"
|
||||||
gogit_ssh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
|
gogit_ssh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
|
||||||
|
@ -67,9 +67,9 @@ func GetAuthForURL(remoteURL *url.URL, httpUser, keyFile string) (auth git_trans
|
||||||
|
|
||||||
func readSSHPrivKey(keyFile string) (sig ssh.Signer, err error) {
|
func readSSHPrivKey(keyFile string) (sig ssh.Signer, err error) {
|
||||||
if keyFile != "" {
|
if keyFile != "" {
|
||||||
keyFile, err = absPathWithExpansion(keyFile)
|
keyFile, err = utils.AbsPathWithExpansion(keyFile)
|
||||||
} else {
|
} else {
|
||||||
keyFile, err = absPathWithExpansion("~/.ssh/id_rsa")
|
keyFile, err = utils.AbsPathWithExpansion("~/.ssh/id_rsa")
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -104,17 +104,3 @@ func promptPass(domain string) (string, error) {
|
||||||
pass, err := terminal.ReadPassword(0)
|
pass, err := terminal.ReadPassword(0)
|
||||||
return string(pass), err
|
return string(pass), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func absPathWithExpansion(p string) (string, error) {
|
|
||||||
u, err := user.Current()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if p == "~" {
|
|
||||||
return u.HomeDir, nil
|
|
||||||
} else if strings.HasPrefix(p, "~/") {
|
|
||||||
return filepath.Join(u.HomeDir, p[2:]), nil
|
|
||||||
} else {
|
|
||||||
return filepath.Abs(p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package interact
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/tea/modules/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateLogin create an login interactive
|
||||||
|
func CreateLogin() error {
|
||||||
|
var stdin, name, token, user, passwd, sshKey, giteaURL string
|
||||||
|
var insecure = false
|
||||||
|
|
||||||
|
fmt.Print("URL of Gitea instance: ")
|
||||||
|
if _, err := fmt.Scanln(&stdin); err != nil {
|
||||||
|
stdin = ""
|
||||||
|
}
|
||||||
|
giteaURL = strings.TrimSpace(stdin)
|
||||||
|
if len(giteaURL) == 0 {
|
||||||
|
fmt.Println("URL is required!")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedURL, err := url.Parse(giteaURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
name = strings.ReplaceAll(strings.Title(parsedURL.Host), ".", "")
|
||||||
|
|
||||||
|
fmt.Print("Name of new Login [" + name + "]: ")
|
||||||
|
if _, err := fmt.Scanln(&stdin); err != nil {
|
||||||
|
stdin = ""
|
||||||
|
}
|
||||||
|
if len(strings.TrimSpace(stdin)) != 0 {
|
||||||
|
name = strings.TrimSpace(stdin)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Print("Do you have a token [Yes/no]: ")
|
||||||
|
if _, err := fmt.Scanln(&stdin); err != nil {
|
||||||
|
stdin = ""
|
||||||
|
}
|
||||||
|
if len(stdin) != 0 && strings.ToLower(stdin[:1]) == "n" {
|
||||||
|
fmt.Print("Username: ")
|
||||||
|
if _, err := fmt.Scanln(&stdin); err != nil {
|
||||||
|
stdin = ""
|
||||||
|
}
|
||||||
|
user = strings.TrimSpace(stdin)
|
||||||
|
|
||||||
|
fmt.Print("Password: ")
|
||||||
|
if _, err := fmt.Scanln(&stdin); err != nil {
|
||||||
|
stdin = ""
|
||||||
|
}
|
||||||
|
passwd = strings.TrimSpace(stdin)
|
||||||
|
} else {
|
||||||
|
fmt.Print("Token: ")
|
||||||
|
if _, err := fmt.Scanln(&stdin); err != nil {
|
||||||
|
stdin = ""
|
||||||
|
}
|
||||||
|
token = strings.TrimSpace(stdin)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Print("Set Optional settings [yes/No]: ")
|
||||||
|
if _, err := fmt.Scanln(&stdin); err != nil {
|
||||||
|
stdin = ""
|
||||||
|
}
|
||||||
|
if len(stdin) != 0 && strings.ToLower(stdin[:1]) == "y" {
|
||||||
|
fmt.Print("SSH Key Path: ")
|
||||||
|
if _, err := fmt.Scanln(&stdin); err != nil {
|
||||||
|
stdin = ""
|
||||||
|
}
|
||||||
|
sshKey = strings.TrimSpace(stdin)
|
||||||
|
|
||||||
|
fmt.Print("Allow Insecure connections [yes/No]: ")
|
||||||
|
if _, err := fmt.Scanln(&stdin); err != nil {
|
||||||
|
stdin = ""
|
||||||
|
}
|
||||||
|
insecure = len(stdin) != 0 && strings.ToLower(stdin[:1]) == "y"
|
||||||
|
}
|
||||||
|
|
||||||
|
return config.AddLogin(name, token, user, passwd, sshKey, giteaURL, insecure)
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package print
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
"github.com/charmbracelet/glamour"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IssueDetails print an issue rendered to stdout
|
||||||
|
func IssueDetails(issue *gitea.Issue) {
|
||||||
|
|
||||||
|
in := fmt.Sprintf("# #%d %s (%s)\n%s created %s\n\n%s\n", issue.Index,
|
||||||
|
issue.Title,
|
||||||
|
issue.State,
|
||||||
|
issue.Poster.UserName,
|
||||||
|
issue.Created.Format("2006-01-02 15:04:05"),
|
||||||
|
issue.Body,
|
||||||
|
)
|
||||||
|
out, err := glamour.Render(in, getGlamourTheme())
|
||||||
|
if err != nil {
|
||||||
|
// TODO: better Error handling
|
||||||
|
fmt.Printf("Error:\n%v\n\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Print(out)
|
||||||
|
}
|
|
@ -2,7 +2,7 @@
|
||||||
// Use of this source code is governed by a MIT-style
|
// Use of this source code is governed by a MIT-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package cmd
|
package print
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -17,27 +17,8 @@ var (
|
||||||
showLog bool
|
showLog bool
|
||||||
)
|
)
|
||||||
|
|
||||||
// Println println content according the flag
|
// errorf printf content as an error information
|
||||||
func Println(a ...interface{}) {
|
func errorf(format string, a ...interface{}) {
|
||||||
if showLog {
|
|
||||||
fmt.Println(a...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Printf printf content according the flag
|
|
||||||
func Printf(format string, a ...interface{}) {
|
|
||||||
if showLog {
|
|
||||||
fmt.Printf(format, a...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error println content as an error information
|
|
||||||
func Error(a ...interface{}) {
|
|
||||||
fmt.Println(a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Errorf printf content as an error information
|
|
||||||
func Errorf(format string, a ...interface{}) {
|
|
||||||
fmt.Printf(format, a...)
|
fmt.Printf(format, a...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,9 +72,9 @@ func outputyaml(headers []string, values [][]string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output provides general function to convert given information
|
// OutputList provides general function to convert given list of items
|
||||||
// into several outputs
|
// into several outputs (table, csv, simple, tsv, yaml)
|
||||||
func Output(output string, headers []string, values [][]string) {
|
func OutputList(output string, headers []string, values [][]string) {
|
||||||
switch {
|
switch {
|
||||||
case output == "" || output == "table":
|
case output == "" || output == "table":
|
||||||
outputtable(headers, values)
|
outputtable(headers, values)
|
||||||
|
@ -106,6 +87,6 @@ func Output(output string, headers []string, values [][]string) {
|
||||||
case output == "yaml":
|
case output == "yaml":
|
||||||
outputyaml(headers, values)
|
outputyaml(headers, values)
|
||||||
default:
|
default:
|
||||||
Errorf("unknown output type '" + output + "', available types are:\n- csv: comma-separated values\n- simple: space-separated values\n- table: auto-aligned table format (default)\n- tsv: tab-separated values\n- yaml: YAML format\n")
|
errorf("unknown output type '" + output + "', available types are:\n- csv: comma-separated values\n- simple: space-separated values\n- table: auto-aligned table format (default)\n- tsv: tab-separated values\n- yaml: YAML format\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package print
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MilestoneDetails print an milestone formatted to stdout
|
||||||
|
func MilestoneDetails(milestone *gitea.Milestone) {
|
||||||
|
fmt.Printf("%s\n",
|
||||||
|
milestone.Title,
|
||||||
|
)
|
||||||
|
if len(milestone.Description) != 0 {
|
||||||
|
fmt.Printf("\n%s\n", milestone.Description)
|
||||||
|
}
|
||||||
|
if milestone.Deadline != nil && !milestone.Deadline.IsZero() {
|
||||||
|
fmt.Printf("\nDeadline: %s\n", milestone.Deadline.Format("2006-01-02 15:04:05"))
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,12 +2,23 @@
|
||||||
// Use of this source code is governed by a MIT-style
|
// Use of this source code is governed by a MIT-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package utils
|
package print
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
// FormatSize get kb in int and return string
|
"github.com/muesli/termenv"
|
||||||
func FormatSize(kb int64) string {
|
)
|
||||||
|
|
||||||
|
func getGlamourTheme() string {
|
||||||
|
if termenv.HasDarkBackground() {
|
||||||
|
return "dark"
|
||||||
|
}
|
||||||
|
return "light"
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatSize get kb in int and return string
|
||||||
|
func formatSize(kb int64) string {
|
||||||
if kb < 1024 {
|
if kb < 1024 {
|
||||||
return fmt.Sprintf("%d Kb", kb)
|
return fmt.Sprintf("%d Kb", kb)
|
||||||
}
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package print
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
"github.com/charmbracelet/glamour"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PullDetails print an pull rendered to stdout
|
||||||
|
func PullDetails(pr *gitea.PullRequest) {
|
||||||
|
|
||||||
|
in := fmt.Sprintf("# #%d %s (%s)\n%s created %s\n\n%s\n", pr.Index,
|
||||||
|
pr.Title,
|
||||||
|
pr.State,
|
||||||
|
pr.Poster.UserName,
|
||||||
|
pr.Created.Format("2006-01-02 15:04:05"),
|
||||||
|
pr.Body,
|
||||||
|
)
|
||||||
|
out, err := glamour.Render(in, getGlamourTheme())
|
||||||
|
if err != nil {
|
||||||
|
// TODO: better Error handling
|
||||||
|
fmt.Printf("Error:\n%v\n\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Print(out)
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package print
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RepoDetails print an repo formatted to stdout
|
||||||
|
func RepoDetails(repo *gitea.Repository, topics []string) {
|
||||||
|
output := repo.FullName
|
||||||
|
if repo.Mirror {
|
||||||
|
output += " (mirror)"
|
||||||
|
}
|
||||||
|
if repo.Fork {
|
||||||
|
output += " (fork)"
|
||||||
|
}
|
||||||
|
if repo.Archived {
|
||||||
|
output += " (archived)"
|
||||||
|
}
|
||||||
|
if repo.Empty {
|
||||||
|
output += " (empty)"
|
||||||
|
}
|
||||||
|
output += "\n"
|
||||||
|
if len(topics) != 0 {
|
||||||
|
output += "Topics: " + strings.Join(topics, ", ") + "\n"
|
||||||
|
}
|
||||||
|
output += "\n"
|
||||||
|
output += repo.Description + "\n\n"
|
||||||
|
output += fmt.Sprintf(
|
||||||
|
"Open Issues: %d, Stars: %d, Forks: %d, Size: %s\n\n",
|
||||||
|
repo.OpenIssues,
|
||||||
|
repo.Stars,
|
||||||
|
repo.Forks,
|
||||||
|
formatSize(int64(repo.Size)),
|
||||||
|
)
|
||||||
|
|
||||||
|
fmt.Print(output)
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package print
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
)
|
||||||
|
|
||||||
|
func formatDuration(seconds int64, outputType string) string {
|
||||||
|
switch outputType {
|
||||||
|
case "yaml":
|
||||||
|
case "csv":
|
||||||
|
return fmt.Sprint(seconds)
|
||||||
|
}
|
||||||
|
return time.Duration(1e9 * seconds).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrackedTimesList print list of tracked times to stdout
|
||||||
|
func TrackedTimesList(times []*gitea.TrackedTime, outputType string, from, until time.Time, printTotal bool) {
|
||||||
|
var outputValues [][]string
|
||||||
|
var totalDuration int64
|
||||||
|
|
||||||
|
localLoc, err := time.LoadLocation("Local") // local timezone for time formatting
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, t := range times {
|
||||||
|
if !from.IsZero() && from.After(t.Created) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !until.IsZero() && until.Before(t.Created) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
totalDuration += t.Time
|
||||||
|
|
||||||
|
outputValues = append(
|
||||||
|
outputValues,
|
||||||
|
[]string{
|
||||||
|
t.Created.In(localLoc).Format("2006-01-02 15:04:05"),
|
||||||
|
"#" + strconv.FormatInt(t.Issue.Index, 10),
|
||||||
|
t.UserName,
|
||||||
|
formatDuration(t.Time, outputType),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if printTotal {
|
||||||
|
outputValues = append(outputValues, []string{
|
||||||
|
"TOTAL", "", "", formatDuration(totalDuration, outputType),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := []string{
|
||||||
|
"Created",
|
||||||
|
"Issue",
|
||||||
|
"User",
|
||||||
|
"Duration",
|
||||||
|
}
|
||||||
|
OutputList(outputType, headers, outputValues)
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ArgToIndex take issue/pull index as string and return int64
|
||||||
|
func ArgToIndex(arg string) (int64, error) {
|
||||||
|
if strings.HasPrefix(arg, "#") {
|
||||||
|
arg = arg[1:]
|
||||||
|
}
|
||||||
|
return strconv.ParseInt(arg, 10, 64)
|
||||||
|
}
|
|
@ -5,7 +5,11 @@
|
||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PathExists returns whether the given file or directory exists or not
|
// PathExists returns whether the given file or directory exists or not
|
||||||
|
@ -19,3 +23,33 @@ func PathExists(path string) (bool, error) {
|
||||||
}
|
}
|
||||||
return true, err
|
return true, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FileExist returns whether the given file exists or not
|
||||||
|
func FileExist(fileName string) (bool, error) {
|
||||||
|
f, err := os.Stat(fileName)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if f.IsDir() {
|
||||||
|
return false, errors.New("A directory with the same name exists")
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AbsPathWithExpansion expand path beginning with "~/" to absolute path
|
||||||
|
func AbsPathWithExpansion(p string) (string, error) {
|
||||||
|
u, err := user.Current()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if p == "~" {
|
||||||
|
return u.HomeDir, nil
|
||||||
|
} else if strings.HasPrefix(p, "~/") {
|
||||||
|
return filepath.Join(u.HomeDir, p[2:]), nil
|
||||||
|
} else {
|
||||||
|
return filepath.Abs(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue