diff --git a/cmd/config.go b/cmd/config.go deleted file mode 100644 index 875f245..0000000 --- a/cmd/config.go +++ /dev/null @@ -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, - } -} diff --git a/cmd/flags.go b/cmd/flags/flags.go similarity index 65% rename from cmd/flags.go rename to cmd/flags/flags.go index 133e12f..aba9dad 100644 --- a/cmd/flags.go +++ b/cmd/flags/flags.go @@ -2,23 +2,24 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package cmd +package flags import ( - "log" - - "code.gitea.io/tea/modules/utils" - + "code.gitea.io/sdk/gitea" "github.com/urfave/cli/v2" ) // create global variables for global Flags to simplify // access to the options without requiring cli.Context var ( - loginValue string - repoValue string - outputValue string - remoteValue string + // GlobalLoginValue contain value of --login|-l arg + GlobalLoginValue string + // GlobalRepoValue contain value of --repo|-r arg + 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 @@ -26,7 +27,7 @@ var LoginFlag = cli.StringFlag{ Name: "login", Aliases: []string{"l"}, Usage: "Use a different Gitea login. Optional", - Destination: &loginValue, + Destination: &GlobalLoginValue, } // RepoFlag provides flag to specify repository @@ -34,7 +35,7 @@ var RepoFlag = cli.StringFlag{ Name: "repo", Aliases: []string{"r"}, Usage: "Repository to interact with. Optional", - Destination: &repoValue, + Destination: &GlobalRepoValue, } // RemoteFlag provides flag to specify remote repository @@ -42,7 +43,7 @@ var RemoteFlag = cli.StringFlag{ Name: "remote", Aliases: []string{"R"}, Usage: "Discover Gitea login from remote. Optional", - Destination: &remoteValue, + Destination: &GlobalRemoteValue, } // OutputFlag provides flag to specify output type @@ -50,7 +51,7 @@ var OutputFlag = cli.StringFlag{ Name: "output", Aliases: []string{"o"}, Usage: "Output format. (csv, simple, table, tsv, yaml)", - Destination: &outputValue, + Destination: &GlobalOutputValue, } // StateFlag provides flag to specify issue/pr state, defaulting to "open" @@ -109,61 +110,15 @@ var IssuePRFlags = append([]cli.Flag{ &PaginationLimitFlag, }, AllDefaultFlags...) -// initCommand returns repository and *Login based on flags -func initCommand() (*Login, string, string) { - var login *Login - - err := loadConfig(yamlConfigPath) - if err != nil { - log.Fatal("load config file failed ", yamlConfigPath) +// GetListOptions return ListOptions based on PaginationFlags +func GetListOptions(ctx *cli.Context) gitea.ListOptions { + page := ctx.Int("page") + limit := ctx.Int("limit") + if limit != 0 && page == 0 { + page = 1 } - - if login, err = getDefaultLogin(); err != nil { - log.Fatal(err.Error()) + return gitea.ListOptions{ + Page: page, + 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 } diff --git a/cmd/issues.go b/cmd/issues.go index a957e60..9987360 100644 --- a/cmd/issues.go +++ b/cmd/issues.go @@ -5,12 +5,12 @@ package cmd import ( - "fmt" - "log" - "strconv" + "code.gitea.io/tea/cmd/flags" + "code.gitea.io/tea/cmd/issues" + "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" ) @@ -22,34 +22,25 @@ var CmdIssues = cli.Command{ ArgsUsage: "[]", Action: runIssues, Subcommands: []*cli.Command{ - &CmdIssuesList, - &CmdIssuesCreate, - &CmdIssuesReopen, - &CmdIssuesClose, + &issues.CmdIssuesList, + &issues.CmdIssuesCreate, + &issues.CmdIssuesReopen, + &issues.CmdIssuesClose, }, - 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, + Flags: flags.IssuePRFlags, } func runIssues(ctx *cli.Context) error { 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 { - login, owner, repo := initCommand() +func runIssueDetail(index string) error { + login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue) - idx, err := argToIndex(index) + idx, err := utils.ArgToIndex(index) if err != nil { return err } @@ -57,165 +48,6 @@ func runIssueDetail(ctx *cli.Context, index string) error { if err != nil { return err } - - 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) + print.IssueDetails(issue) 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: "", - 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: "", - 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 -} diff --git a/cmd/issues/close.go b/cmd/issues/close.go new file mode 100644 index 0000000..850dde4 --- /dev/null +++ b/cmd/issues/close.go @@ -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: "", + 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 +} diff --git a/cmd/issues/create.go b/cmd/issues/create.go new file mode 100644 index 0000000..60b3b21 --- /dev/null +++ b/cmd/issues/create.go @@ -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 +} diff --git a/cmd/issues/list.go b/cmd/issues/list.go new file mode 100644 index 0000000..1f7e7b8 --- /dev/null +++ b/cmd/issues/list.go @@ -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 +} diff --git a/cmd/issues/reopen.go b/cmd/issues/reopen.go new file mode 100644 index 0000000..2d78990 --- /dev/null +++ b/cmd/issues/reopen.go @@ -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: "", + Action: func(ctx *cli.Context) error { + var s = gitea.StateOpen + return editIssueState(ctx, gitea.EditIssueOption{State: &s}) + }, + Flags: flags.AllDefaultFlags, +} diff --git a/cmd/labels.go b/cmd/labels.go index 6daf842..4b9a67b 100644 --- a/cmd/labels.go +++ b/cmd/labels.go @@ -5,15 +5,17 @@ package cmd import ( - "bufio" "fmt" "log" "os" "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" - "github.com/muesli/termenv" "github.com/urfave/cli/v2" ) @@ -25,9 +27,9 @@ var CmdLabels = cli.Command{ Description: `Manage issue labels`, Action: runLabels, Subcommands: []*cli.Command{ - &CmdLabelCreate, - &CmdLabelUpdate, - &CmdLabelDelete, + &labels.CmdLabelCreate, + &labels.CmdLabelUpdate, + &labels.CmdLabelDelete, }, Flags: append([]cli.Flag{ &cli.StringFlag{ @@ -35,13 +37,13 @@ var CmdLabels = cli.Command{ Aliases: []string{"s"}, Usage: "Save all the labels as a file", }, - &PaginationPageFlag, - &PaginationLimitFlag, - }, AllDefaultFlags...), + &flags.PaginationPageFlag, + &flags.PaginationLimitFlag, + }, flags.AllDefaultFlags...), } func runLabels(ctx *cli.Context) error { - login, owner, repo := initCommand() + login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue) headers := []string{ "Index", @@ -52,13 +54,13 @@ func runLabels(ctx *cli.Context) error { 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 { log.Fatal(err) } if len(labels) == 0 { - Output(outputValue, headers, values) + print.OutputList(flags.GlobalOutputValue, headers, values) return nil } @@ -89,184 +91,7 @@ func runLabels(ctx *cli.Context) error { }, ) } - Output(outputValue, 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) + print.OutputList(flags.GlobalOutputValue, headers, values) } return nil diff --git a/cmd/labels/create.go b/cmd/labels/create.go new file mode 100644 index 0000000..83fdd97 --- /dev/null +++ b/cmd/labels/create.go @@ -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 +} diff --git a/cmd/labels_test.go b/cmd/labels/create_test.go similarity index 98% rename from cmd/labels_test.go rename to cmd/labels/create_test.go index fd6b5b6..17e6e27 100644 --- a/cmd/labels_test.go +++ b/cmd/labels/create_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package cmd +package labels import ( "bufio" diff --git a/cmd/labels/delete.go b/cmd/labels/delete.go new file mode 100644 index 0000000..26e69b8 --- /dev/null +++ b/cmd/labels/delete.go @@ -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 +} diff --git a/cmd/labels/update.go b/cmd/labels/update.go new file mode 100644 index 0000000..3731452 --- /dev/null +++ b/cmd/labels/update.go @@ -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 +} diff --git a/cmd/login.go b/cmd/login.go index 97d6053..f1031e1 100644 --- a/cmd/login.go +++ b/cmd/login.go @@ -5,19 +5,8 @@ package cmd import ( - "crypto/tls" - "fmt" - "log" - "net/http" - "net/http/cookiejar" - "net/url" - "os" - "strings" - "time" + "code.gitea.io/tea/cmd/login" - "code.gitea.io/sdk/gitea" - - "github.com/skratchdot/open-golang/open" "github.com/urfave/cli/v2" ) @@ -26,337 +15,11 @@ var CmdLogin = cli.Command{ Name: "login", Usage: "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{ - &cmdLoginList, - &cmdLoginAdd, - &cmdLoginEdit, - &cmdLoginSetDefault, + &login.CmdLoginList, + &login.CmdLoginAdd, + &login.CmdLoginEdit, + &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: "", - 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 -} diff --git a/cmd/login/add.go b/cmd/login/add.go new file mode 100644 index 0000000..2b1cc17 --- /dev/null +++ b/cmd/login/add.go @@ -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() +} diff --git a/cmd/login/default.go b/cmd/login/default.go new file mode 100644 index 0000000..7e8e60f --- /dev/null +++ b/cmd/login/default.go @@ -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: "", + 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() +} diff --git a/cmd/login/edit.go b/cmd/login/edit.go new file mode 100644 index 0000000..c8ffd1e --- /dev/null +++ b/cmd/login/edit.go @@ -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()) +} diff --git a/cmd/login/list.go b/cmd/login/list.go new file mode 100644 index 0000000..7402677 --- /dev/null +++ b/cmd/login/list.go @@ -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 +} diff --git a/cmd/logout.go b/cmd/logout.go index 192d519..23e5afe 100644 --- a/cmd/logout.go +++ b/cmd/logout.go @@ -9,6 +9,8 @@ import ( "log" "os" + "code.gitea.io/tea/modules/config" + "github.com/urfave/cli/v2" ) @@ -37,23 +39,23 @@ func runLogout(ctx *cli.Context) error { return errors.New("Please specify a login name") } - err := loadConfig(yamlConfigPath) + err := config.LoadConfig() if err != nil { - log.Fatal("Unable to load config file " + yamlConfigPath) + log.Fatal(err) } var idx = -1 - for i, l := range config.Logins { + for i, l := range config.Config.Logins { if l.Name == name { idx = i break } } if idx > -1 { - config.Logins = append(config.Logins[:idx], config.Logins[idx+1:]...) - err = saveConfig(yamlConfigPath) + config.Config.Logins = append(config.Config.Logins[:idx], config.Config.Logins[idx+1:]...) + err = config.SaveConfig() if err != nil { - log.Fatal("Unable to save config file " + yamlConfigPath) + log.Fatal(err) } } diff --git a/cmd/milestones.go b/cmd/milestones.go index 311cf11..5a14864 100644 --- a/cmd/milestones.go +++ b/cmd/milestones.go @@ -5,10 +5,11 @@ package cmd import ( - "fmt" - "log" + "code.gitea.io/tea/cmd/flags" + "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" ) @@ -21,42 +22,25 @@ var CmdMilestones = cli.Command{ ArgsUsage: "[]", Action: runMilestones, Subcommands: []*cli.Command{ - &CmdMilestonesList, - &CmdMilestonesCreate, - &CmdMilestonesClose, - &CmdMilestonesDelete, - &CmdMilestonesReopen, - &CmdMilestonesIssues, + &milestones.CmdMilestonesList, + &milestones.CmdMilestonesCreate, + &milestones.CmdMilestonesClose, + &milestones.CmdMilestonesDelete, + &milestones.CmdMilestonesReopen, + &milestones.CmdMilestonesIssues, }, - 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...), + Flags: flags.AllDefaultFlags, } func runMilestones(ctx *cli.Context) error { 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 { - login, owner, repo := initCommand() +func runMilestoneDetail(name string) error { + login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue) client := login.Client() milestone, _, err := client.GetMilestoneByName(owner, repo, name) @@ -64,192 +48,6 @@ func runMilestoneDetail(ctx *cli.Context, name string) error { return err } - 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")) - } + print.MilestoneDetails(milestone) 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: "", - 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: "", - 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: "", - Action: func(ctx *cli.Context) error { - return editMilestoneStatus(ctx, false) - }, - Flags: AllDefaultFlags, -} diff --git a/cmd/milestones/close.go b/cmd/milestones/close.go new file mode 100644 index 0000000..1ce65ef --- /dev/null +++ b/cmd/milestones/close.go @@ -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: "", + 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...), +} diff --git a/cmd/milestones/create.go b/cmd/milestones/create.go new file mode 100644 index 0000000..637c666 --- /dev/null +++ b/cmd/milestones/create.go @@ -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 +} diff --git a/cmd/milestones/delete.go b/cmd/milestones/delete.go new file mode 100644 index 0000000..639272c --- /dev/null +++ b/cmd/milestones/delete.go @@ -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: "", + 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 +} diff --git a/cmd/milestone_issues.go b/cmd/milestones/issues.go similarity index 82% rename from cmd/milestone_issues.go rename to cmd/milestones/issues.go index 0043356..2a01372 100644 --- a/cmd/milestone_issues.go +++ b/cmd/milestones/issues.go @@ -2,12 +2,17 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package cmd +package milestones import ( "fmt" "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" "github.com/urfave/cli/v2" ) @@ -34,9 +39,9 @@ var CmdMilestonesIssues = cli.Command{ Name: "kind", Usage: "Filter by kind (issue|pull)", }, - &PaginationPageFlag, - &PaginationLimitFlag, - }, AllDefaultFlags...), + &flags.PaginationPageFlag, + &flags.PaginationLimitFlag, + }, flags.AllDefaultFlags...), } // 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", ArgsUsage: " ", Action: runMilestoneIssueAdd, - Flags: AllDefaultFlags, + Flags: flags.AllDefaultFlags, } // 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", ArgsUsage: " ", Action: runMilestoneIssueRemove, - Flags: AllDefaultFlags, + Flags: flags.AllDefaultFlags, } func runMilestoneIssueList(ctx *cli.Context) error { - login, owner, repo := initCommand() + login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue) client := login.Client() state := gitea.StateOpen @@ -91,7 +96,7 @@ func runMilestoneIssueList(ctx *cli.Context) error { } issues, _, err := client.ListRepoIssues(owner, repo, gitea.ListIssueOption{ - ListOptions: getListOptions(ctx), + ListOptions: flags.GetListOptions(ctx), Milestones: []string{milestone}, Type: kind, State: state, @@ -112,7 +117,7 @@ func runMilestoneIssueList(ctx *cli.Context) error { var values [][]string if len(issues) == 0 { - Output(outputValue, headers, values) + print.OutputList(flags.GlobalOutputValue, headers, values) return nil } @@ -137,12 +142,12 @@ func runMilestoneIssueList(ctx *cli.Context) error { }, ) } - Output(outputValue, headers, values) + print.OutputList(flags.GlobalOutputValue, headers, values) return nil } func runMilestoneIssueAdd(ctx *cli.Context) error { - login, owner, repo := initCommand() + login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue) client := login.Client() if ctx.Args().Len() == 0 { return fmt.Errorf("need two arguments") @@ -150,7 +155,7 @@ func runMilestoneIssueAdd(ctx *cli.Context) error { mileName := ctx.Args().Get(0) issueIndex := ctx.Args().Get(1) - idx, err := argToIndex(issueIndex) + idx, err := utils.ArgToIndex(issueIndex) if err != nil { return err } @@ -168,7 +173,7 @@ func runMilestoneIssueAdd(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() if ctx.Args().Len() == 0 { return fmt.Errorf("need two arguments") @@ -176,7 +181,7 @@ func runMilestoneIssueRemove(ctx *cli.Context) error { mileName := ctx.Args().Get(0) issueIndex := ctx.Args().Get(1) - idx, err := argToIndex(issueIndex) + idx, err := utils.ArgToIndex(issueIndex) if err != nil { return err } diff --git a/cmd/milestones/list.go b/cmd/milestones/list.go new file mode 100644 index 0000000..d3fe60e --- /dev/null +++ b/cmd/milestones/list.go @@ -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 +} diff --git a/cmd/milestones/reopen.go b/cmd/milestones/reopen.go new file mode 100644 index 0000000..d984f18 --- /dev/null +++ b/cmd/milestones/reopen.go @@ -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: "", + 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 +} diff --git a/cmd/notifications.go b/cmd/notifications.go index a393ae3..2aefb91 100644 --- a/cmd/notifications.go +++ b/cmd/notifications.go @@ -8,6 +8,10 @@ import ( "log" "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" ) @@ -34,16 +38,16 @@ var CmdNotifications = cli.Command{ Aliases: []string{"pd"}, Usage: "show pinned notifications instead unread", }, - &PaginationPageFlag, - &PaginationLimitFlag, - }, AllDefaultFlags...), + &flags.PaginationPageFlag, + &flags.PaginationLimitFlag, + }, flags.AllDefaultFlags...), } func runNotifications(ctx *cli.Context) error { var news []*gitea.NotificationThread var err error - listOpts := getListOptions(ctx) + listOpts := flags.GetListOptions(ctx) if listOpts.Page == 0 { listOpts.Page = 1 } @@ -57,13 +61,13 @@ func runNotifications(ctx *cli.Context) error { } if ctx.Bool("all") { - login := initCommandLoginOnly() + login := config.InitCommandLoginOnly(flags.GlobalLoginValue) news, _, err = login.Client().ListNotifications(gitea.ListNotificationOptions{ ListOptions: listOpts, Status: status, }) } 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{ ListOptions: listOpts, Status: status, @@ -107,7 +111,7 @@ func runNotifications(ctx *cli.Context) error { } if len(values) != 0 { - Output(outputValue, headers, values) + print.OutputList(flags.GlobalOutputValue, headers, values) } return nil } diff --git a/cmd/open.go b/cmd/open.go index fbe67b4..85811ed 100644 --- a/cmd/open.go +++ b/cmd/open.go @@ -9,6 +9,8 @@ import ( "path" "strings" + "code.gitea.io/tea/cmd/flags" + "code.gitea.io/tea/modules/config" local_git "code.gitea.io/tea/modules/git" "github.com/skratchdot/open-golang/open" @@ -21,11 +23,11 @@ var CmdOpen = cli.Command{ Usage: "Open something of the repository on web browser", Description: `Open something of the repository on web browser`, Action: runOpen, - Flags: append([]cli.Flag{}, LoginRepoFlags...), + Flags: append([]cli.Flag{}, flags.LoginRepoFlags...), } func runOpen(ctx *cli.Context) error { - login, owner, repo := initCommand() + login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue) var suffix string number := ctx.Args().Get(0) diff --git a/cmd/pulls.go b/cmd/pulls.go index cb053d8..7cd8079 100644 --- a/cmd/pulls.go +++ b/cmd/pulls.go @@ -6,16 +6,12 @@ package cmd import ( "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" ) @@ -27,35 +23,26 @@ var CmdPulls = cli.Command{ Description: `List, create, checkout and clean pull requests`, ArgsUsage: "[]", Action: runPulls, - Flags: IssuePRFlags, + Flags: flags.IssuePRFlags, Subcommands: []*cli.Command{ - &CmdPullsList, - &CmdPullsCheckout, - &CmdPullsClean, - &CmdPullsCreate, + &pulls.CmdPullsList, + &pulls.CmdPullsCheckout, + &pulls.CmdPullsClean, + &pulls.CmdPullsCreate, }, } func runPulls(ctx *cli.Context) error { 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 -var CmdPullsList = cli.Command{ - Name: "ls", - Usage: "List pull requests of the repository", - Description: `List pull requests of the repository`, - Action: runPullsList, - Flags: IssuePRFlags, -} +func runPullDetail(index string) error { + login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue) -func runPullDetail(ctx *cli.Context, index string) error { - login, owner, repo := initCommand() - - idx, err := argToIndex(index) + idx, err := utils.ArgToIndex(index) if err != nil { return err } @@ -73,375 +60,3 @@ func runPullDetail(ctx *cli.Context, index string) error { ) 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: "", - 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: "", - 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 ") - } - - 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) -} diff --git a/cmd/pulls/checkout.go b/cmd/pulls/checkout.go new file mode 100644 index 0000000..389df33 --- /dev/null +++ b/cmd/pulls/checkout.go @@ -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: "", + 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 +} diff --git a/cmd/pulls/clean.go b/cmd/pulls/clean.go new file mode 100644 index 0000000..98c9f4b --- /dev/null +++ b/cmd/pulls/clean.go @@ -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: "", + 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) +} diff --git a/cmd/pulls/create.go b/cmd/pulls/create.go new file mode 100644 index 0000000..ee6564c --- /dev/null +++ b/cmd/pulls/create.go @@ -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 ") + } + + 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 +} diff --git a/cmd/pulls/list.go b/cmd/pulls/list.go new file mode 100644 index 0000000..833dbe6 --- /dev/null +++ b/cmd/pulls/list.go @@ -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 +} diff --git a/cmd/releases.go b/cmd/releases.go index 506664c..7db2a86 100644 --- a/cmd/releases.go +++ b/cmd/releases.go @@ -5,307 +5,25 @@ package cmd import ( - "fmt" - "log" - "net/http" - "os" - "path/filepath" - "strings" - - "code.gitea.io/sdk/gitea" + "code.gitea.io/tea/cmd/flags" + "code.gitea.io/tea/cmd/releases" "github.com/urfave/cli/v2" ) // CmdReleases represents to login a gitea server. +// ToDo: ReleaseDetails var CmdReleases = cli.Command{ Name: "release", Aliases: []string{"releases"}, Usage: "Manage releases", Description: "Manage releases", - Action: runReleases, + Action: releases.RunReleasesList, Subcommands: []*cli.Command{ - &CmdReleaseList, - &CmdReleaseCreate, - &CmdReleaseDelete, - &CmdReleaseEdit, + &releases.CmdReleaseList, + &releases.CmdReleaseCreate, + &releases.CmdReleaseDelete, + &releases.CmdReleaseEdit, }, - 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: "", - 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: "", - 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 + Flags: flags.AllDefaultFlags, } diff --git a/cmd/releases/create.go b/cmd/releases/create.go new file mode 100644 index 0000000..6af1177 --- /dev/null +++ b/cmd/releases/create.go @@ -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 +} diff --git a/cmd/releases/delete.go b/cmd/releases/delete.go new file mode 100644 index 0000000..8c4f06f --- /dev/null +++ b/cmd/releases/delete.go @@ -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: "", + 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 +} diff --git a/cmd/releases/edit.go b/cmd/releases/edit.go new file mode 100644 index 0000000..7e69981 --- /dev/null +++ b/cmd/releases/edit.go @@ -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: "", + 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 +} diff --git a/cmd/releases/list.go b/cmd/releases/list.go new file mode 100644 index 0000000..9e34903 --- /dev/null +++ b/cmd/releases/list.go @@ -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 +} diff --git a/cmd/repos.go b/cmd/repos.go index 33ca875..208a3b6 100644 --- a/cmd/repos.go +++ b/cmd/repos.go @@ -5,12 +5,10 @@ package cmd import ( - "fmt" - "log" - "net/http" - "strings" - - "code.gitea.io/tea/modules/utils" + "code.gitea.io/tea/cmd/flags" + "code.gitea.io/tea/cmd/repos" + "code.gitea.io/tea/modules/config" + "code.gitea.io/tea/modules/print" "code.gitea.io/sdk/gitea" "github.com/urfave/cli/v2" @@ -24,224 +22,23 @@ var CmdRepos = cli.Command{ ArgsUsage: "[/]", Action: runRepos, Subcommands: []*cli.Command{ - &CmdReposList, - &CmdRepoCreate, + &repos.CmdReposList, + &repos.CmdRepoCreate, }, - 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...), + Flags: flags.LoginOutputFlags, } func runRepos(ctx *cli.Context) error { 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 runReposList(ctx *cli.Context) error { - login := initCommandLoginOnly() +func runRepoDetail(path string) 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: 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) + repoOwner, repoName := config.GetOwnerAndRepo(path, login.User) repo, _, err := client.GetRepo(repoOwner, repoName) if err != nil { return err @@ -251,66 +48,6 @@ func runRepoDetail(_ *cli.Context, path string) error { return err } - 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, - 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) + print.RepoDetails(repo, topics) return nil } diff --git a/cmd/repos/create.go b/cmd/repos/create.go new file mode 100644 index 0000000..1a21a3c --- /dev/null +++ b/cmd/repos/create.go @@ -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 +} diff --git a/cmd/repos/list.go b/cmd/repos/list.go new file mode 100644 index 0000000..482f5b2 --- /dev/null +++ b/cmd/repos/list.go @@ -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 +} diff --git a/cmd/times.go b/cmd/times.go index d3d34b7..01da736 100644 --- a/cmd/times.go +++ b/cmd/times.go @@ -6,13 +6,16 @@ package cmd import ( "fmt" - "log" - "strconv" "strings" "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/urfave/cli/v2" ) @@ -28,9 +31,9 @@ var CmdTrackedTimes = cli.Command{ ArgsUsage: "[username | #issue]", Action: runTrackedTimes, Subcommands: []*cli.Command{ - &CmdTrackedTimesAdd, - &CmdTrackedTimesDelete, - &CmdTrackedTimesReset, + ×.CmdTrackedTimesAdd, + ×.CmdTrackedTimesDelete, + ×.CmdTrackedTimesReset, }, Flags: append([]cli.Flag{ &cli.StringFlag{ @@ -48,11 +51,11 @@ var CmdTrackedTimes = cli.Command{ Aliases: []string{"t"}, Usage: "Print the total duration at the end", }, - }, AllDefaultFlags...), + }, flags.AllDefaultFlags...), } func runTrackedTimes(ctx *cli.Context) error { - login, owner, repo := initCommand() + login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue) client := login.Client() if err := client.CheckServerVersionConstraint(">= 1.11"); err != nil { @@ -69,7 +72,7 @@ func runTrackedTimes(ctx *cli.Context) error { times, _, err = client.GetRepoTrackedTimes(owner, repo) } else if strings.HasPrefix(user, "#") { // get all tracked times on the specified issue - issue, err := argToIndex(user) + issue, err := utils.ArgToIndex(user) if err != nil { return err } @@ -97,175 +100,6 @@ func runTrackedTimes(ctx *cli.Context) error { } } - printTrackedTimes(times, outputValue, 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 ", - 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