2018-09-03 08:43:00 +02:00
|
|
|
// 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"
|
2020-04-19 05:09:03 +02:00
|
|
|
"code.gitea.io/tea/modules/git"
|
2018-09-03 08:43:00 +02:00
|
|
|
"code.gitea.io/tea/modules/utils"
|
|
|
|
|
2020-09-19 18:00:50 +02:00
|
|
|
"github.com/muesli/termenv"
|
2020-09-27 14:07:46 +02:00
|
|
|
"github.com/urfave/cli/v2"
|
2020-04-28 05:39:36 +02:00
|
|
|
"gopkg.in/yaml.v2"
|
2018-09-03 08:43:00 +02:00
|
|
|
)
|
|
|
|
|
2019-04-25 19:06:53 +02:00
|
|
|
// Login represents a login to a gitea server, you even could add multiple logins for one gitea server
|
2018-09-03 08:43:00 +02:00
|
|
|
type Login struct {
|
2020-04-19 05:09:03 +02:00
|
|
|
Name string `yaml:"name"`
|
|
|
|
URL string `yaml:"url"`
|
|
|
|
Token string `yaml:"token"`
|
2020-09-23 16:23:27 +02:00
|
|
|
Default bool `yaml:"default"`
|
2020-04-19 05:09:03 +02:00
|
|
|
SSHHost string `yaml:"ssh_host"`
|
|
|
|
// optional path to the private key
|
|
|
|
SSHKey string `yaml:"ssh_key"`
|
2018-09-03 08:43:00 +02:00
|
|
|
Insecure bool `yaml:"insecure"`
|
2020-04-19 05:09:03 +02:00
|
|
|
// optional gitea username
|
|
|
|
User string `yaml:"user"`
|
2018-09-03 08:43:00 +02:00
|
|
|
}
|
|
|
|
|
2019-04-25 19:06:53 +02:00
|
|
|
// Client returns a client to operate Gitea API
|
2018-09-03 08:43:00 +02:00
|
|
|
func (l *Login) Client() *gitea.Client {
|
2020-09-16 04:01:41 +02:00
|
|
|
httpClient := &http.Client{}
|
2018-09-03 08:43:00 +02:00
|
|
|
if l.Insecure {
|
|
|
|
cookieJar, _ := cookiejar.New(nil)
|
|
|
|
|
2020-09-16 04:01:41 +02:00
|
|
|
httpClient = &http.Client{
|
2018-09-03 08:43:00 +02:00
|
|
|
Jar: cookieJar,
|
|
|
|
Transport: &http.Transport{
|
|
|
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
2020-09-16 04:01:41 +02:00
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
|
|
|
client, err := gitea.NewClient(l.URL,
|
|
|
|
gitea.SetToken(l.Token),
|
|
|
|
gitea.SetHTTPClient(httpClient),
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
2018-09-03 08:43:00 +02:00
|
|
|
}
|
|
|
|
return client
|
|
|
|
}
|
|
|
|
|
2019-04-25 19:06:53 +02:00
|
|
|
// GetSSHHost returns SSH host name
|
2018-09-03 08:43:00 +02:00
|
|
|
func (l *Login) GetSSHHost() string {
|
|
|
|
if l.SSHHost != "" {
|
|
|
|
return l.SSHHost
|
|
|
|
}
|
|
|
|
|
|
|
|
u, err := url.Parse(l.URL)
|
|
|
|
if err != nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
return u.Hostname()
|
|
|
|
}
|
|
|
|
|
2019-04-25 19:06:53 +02:00
|
|
|
// Config reprensents local configurations
|
2018-09-03 08:43:00 +02:00
|
|
|
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 {
|
2019-11-03 21:34:41 +01:00
|
|
|
log.Fatal("Init tea config dir " + dir + " failed")
|
2018-09-03 08:43:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
yamlConfigPath = filepath.Join(dir, "tea.yml")
|
|
|
|
}
|
|
|
|
|
2020-09-19 18:00:50 +02:00
|
|
|
func getGlamourTheme() string {
|
|
|
|
if termenv.HasDarkBackground() {
|
|
|
|
return "dark"
|
|
|
|
}
|
|
|
|
return "light"
|
|
|
|
}
|
|
|
|
|
2020-09-16 15:47:52 +02:00
|
|
|
func getOwnerAndRepo(repoPath, user string) (string, string) {
|
|
|
|
if len(repoPath) == 0 {
|
|
|
|
return "", ""
|
|
|
|
}
|
2018-09-03 08:43:00 +02:00
|
|
|
p := strings.Split(repoPath, "/")
|
|
|
|
if len(p) >= 2 {
|
|
|
|
return p[0], p[1]
|
|
|
|
}
|
2020-09-16 15:47:52 +02:00
|
|
|
return user, repoPath
|
2018-09-03 08:43:00 +02:00
|
|
|
}
|
|
|
|
|
2020-09-23 16:23:27 +02:00
|
|
|
func getDefaultLogin() (*Login, error) {
|
2018-09-03 08:43:00 +02:00
|
|
|
if len(config.Logins) == 0 {
|
|
|
|
return nil, errors.New("No available login")
|
|
|
|
}
|
|
|
|
for _, l := range config.Logins {
|
2020-09-23 16:23:27 +02:00
|
|
|
if l.Default {
|
2018-09-03 08:43:00 +02:00
|
|
|
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
|
|
|
|
}
|
2019-11-03 21:34:41 +01:00
|
|
|
return errors.New("Login name has already been used")
|
2018-09-03 08:43:00 +02:00
|
|
|
}
|
|
|
|
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() {
|
2019-11-03 21:34:41 +01:00
|
|
|
return false, errors.New("A directory with the same name exists")
|
2018-09-03 08:43:00 +02:00
|
|
|
}
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2020-07-17 18:36:48 +02:00
|
|
|
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)
|
2019-04-25 19:06:53 +02:00
|
|
|
}
|
2020-07-20 05:09:34 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, "", err
|
|
|
|
}
|
2020-04-19 05:09:03 +02:00
|
|
|
gitConfig, err := repo.Config()
|
2019-10-28 22:10:20 +01:00
|
|
|
if err != nil {
|
2019-04-25 19:06:53 +02:00
|
|
|
return nil, "", err
|
|
|
|
}
|
2019-10-26 23:29:37 +02:00
|
|
|
|
|
|
|
// if no remote
|
|
|
|
if len(gitConfig.Remotes) == 0 {
|
2019-11-03 21:34:41 +01:00
|
|
|
return nil, "", errors.New("No remote(s) found in this Git repository")
|
2019-10-26 23:29:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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]
|
2019-04-25 19:06:53 +02:00
|
|
|
if !ok || remoteConfig == nil {
|
2019-11-03 21:34:41 +01:00
|
|
|
return nil, "", errors.New("Remote " + remoteValue + " not found in this Git repository")
|
2018-09-03 08:43:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, l := range config.Logins {
|
2019-04-25 19:06:53 +02:00
|
|
|
for _, u := range remoteConfig.URLs {
|
2020-04-19 05:09:03 +02:00
|
|
|
p, err := git.ParseURL(strings.TrimSpace(u))
|
2019-04-25 19:06:53 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, "", fmt.Errorf("Git remote URL parse failed: %s", err.Error())
|
2018-09-03 08:43:00 +02:00
|
|
|
}
|
2019-04-25 19:06:53 +02:00
|
|
|
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") {
|
2019-10-28 22:10:20 +01:00
|
|
|
if l.GetSSHHost() == strings.Split(p.Host, ":")[0] {
|
2019-04-25 19:06:53 +02:00
|
|
|
return &l, strings.TrimLeft(strings.TrimSuffix(p.Path, ".git"), "/"), nil
|
|
|
|
}
|
2018-09-03 08:43:00 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-03 21:34:41 +01:00
|
|
|
return nil, "", errors.New("No Gitea login found. You might want to specify --repo (and --login) to work outside of a repository")
|
2018-09-03 08:43:00 +02:00
|
|
|
}
|
2020-09-27 14:07:46 +02:00
|
|
|
|
|
|
|
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,
|
|
|
|
}
|
|
|
|
}
|