зеркало из https://github.com/mislav/hub.git
Load multiple configs
This commit is contained in:
Родитель
23f68481e4
Коммит
5ddd2d45c7
|
@ -52,7 +52,9 @@ func browse(command *Command, args *Args) {
|
|||
subpage = args.RemoveParam(0)
|
||||
}
|
||||
|
||||
project := github.CurrentProject()
|
||||
project, err := github.LocalRepo().CurrentProject()
|
||||
utils.Check(err)
|
||||
|
||||
if subpage == "tree" || subpage == "commits" {
|
||||
repo := project.LocalRepo()
|
||||
subpage = utils.ConcatPaths(subpage, repo.Head)
|
||||
|
|
|
@ -2,6 +2,7 @@ package commands
|
|||
|
||||
import (
|
||||
"github.com/jingweno/gh/github"
|
||||
"github.com/jingweno/gh/utils"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
|
@ -67,7 +68,8 @@ func parseCherryPickProjectAndSha(ref string) (project *github.Project, sha stri
|
|||
if ownerWithShaRegexp.MatchString(ref) {
|
||||
matches := ownerWithShaRegexp.FindStringSubmatch(ref)
|
||||
sha = matches[2]
|
||||
project = github.CurrentProject()
|
||||
project, err := github.LocalRepo().CurrentProject()
|
||||
utils.Check(err)
|
||||
project.Owner = matches[1]
|
||||
}
|
||||
|
||||
|
|
|
@ -3,16 +3,11 @@ package commands
|
|||
import (
|
||||
"github.com/bmizerany/assert"
|
||||
"github.com/jingweno/gh/github"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTransformCloneArgs(t *testing.T) {
|
||||
github.DefaultConfigFile = "./test_support/clone_gh"
|
||||
config := github.Config{User: "jingweno", Token: "123"}
|
||||
github.SaveConfig(&config)
|
||||
defer os.RemoveAll(filepath.Dir(github.DefaultConfigFile))
|
||||
github.CreateTestConfig("jingweno", "123")
|
||||
|
||||
args := NewArgs([]string{"clone", "foo/gh"})
|
||||
transformCloneArgs(args)
|
||||
|
|
|
@ -36,7 +36,8 @@ func init() {
|
|||
> open https://github.com/other-user/REPO/compare/patch
|
||||
*/
|
||||
func compare(command *Command, args *Args) {
|
||||
project := github.CurrentProject()
|
||||
project, err := github.LocalRepo().CurrentProject()
|
||||
utils.Check(err)
|
||||
|
||||
var r string
|
||||
if args.IsParamsEmpty() {
|
||||
|
|
|
@ -3,17 +3,12 @@ package commands
|
|||
import (
|
||||
"github.com/bmizerany/assert"
|
||||
"github.com/jingweno/gh/github"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTransformInitArgs(t *testing.T) {
|
||||
github.DefaultConfigFile = "./test_support/gh"
|
||||
config := github.Config{User: "jingweno", Token: "123"}
|
||||
github.SaveConfig(&config)
|
||||
defer os.RemoveAll(filepath.Dir(github.DefaultConfigFile))
|
||||
github.CreateTestConfig("jingweno", "123")
|
||||
|
||||
args := NewArgs([]string{"init"})
|
||||
err := transformInitArgs(args)
|
||||
|
|
|
@ -98,7 +98,7 @@ func pullRequest(cmd *Command, args *Args) {
|
|||
if !strings.Contains(split[0], "/") {
|
||||
name = baseProject.Name
|
||||
}
|
||||
baseProject = github.NewProjectFromOwnerAndName(split[0], name)
|
||||
baseProject = github.NewProject(split[0], name, baseProject.Host)
|
||||
} else {
|
||||
base = flagPullRequestBase
|
||||
}
|
||||
|
@ -108,7 +108,7 @@ func pullRequest(cmd *Command, args *Args) {
|
|||
if strings.Contains(flagPullRequestHead, ":") {
|
||||
split := strings.SplitN(flagPullRequestHead, ":", 2)
|
||||
head = split[1]
|
||||
headProject = github.NewProjectFromOwnerAndName(split[0], headProject.Name)
|
||||
headProject = github.NewProject(split[0], headProject.Name, headProject.Host)
|
||||
explicitOwner = true
|
||||
} else {
|
||||
head = flagPullRequestHead
|
||||
|
@ -154,11 +154,10 @@ func pullRequest(cmd *Command, args *Args) {
|
|||
}
|
||||
}
|
||||
|
||||
gh := github.NewWithoutProject()
|
||||
gh.Project = baseProject
|
||||
client := github.NewClient(baseProject)
|
||||
|
||||
// when no tracking, assume remote branch is published under active user's fork
|
||||
if tberr != nil && !explicitOwner && gh.Config.User != headProject.Owner {
|
||||
if tberr != nil && !explicitOwner && client.Config.User != headProject.Owner {
|
||||
headProject = github.NewProjectFromOwnerAndName("", headProject.Name)
|
||||
}
|
||||
|
||||
|
@ -208,13 +207,13 @@ func pullRequest(cmd *Command, args *Args) {
|
|||
pullRequestURL = "PULL_REQUEST_URL"
|
||||
} else {
|
||||
if title != "" {
|
||||
pr, err := gh.CreatePullRequest(base, fullHead, title, body)
|
||||
pr, err := client.CreatePullRequest(base, fullHead, title, body)
|
||||
utils.Check(err)
|
||||
pullRequestURL = pr.HTMLURL
|
||||
}
|
||||
|
||||
if flagPullRequestIssue != "" {
|
||||
pr, err := gh.CreatePullRequestForIssue(base, fullHead, flagPullRequestIssue)
|
||||
pr, err := client.CreatePullRequestForIssue(base, fullHead, flagPullRequestIssue)
|
||||
utils.Check(err)
|
||||
pullRequestURL = pr.HTMLURL
|
||||
}
|
||||
|
|
|
@ -3,8 +3,6 @@ package commands
|
|||
import (
|
||||
"github.com/bmizerany/assert"
|
||||
"github.com/jingweno/gh/github"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"testing"
|
||||
)
|
||||
|
@ -28,10 +26,7 @@ func TestTransformRemoteArgs(t *testing.T) {
|
|||
reg = regexp.MustCompile("^git@github.com:jingweno/.+\\.git$")
|
||||
assert.T(t, reg.MatchString(args.GetParam(2)))
|
||||
|
||||
github.DefaultConfigFile = "./test_support/gh"
|
||||
config := github.Config{User: "jingweno", Token: "123"}
|
||||
github.SaveConfig(&config)
|
||||
defer os.RemoveAll(filepath.Dir(github.DefaultConfigFile))
|
||||
github.CreateTestConfig("jingweno", "123")
|
||||
|
||||
args = NewArgs([]string{"remote", "add", "origin"})
|
||||
transformRemoteArgs(args)
|
||||
|
|
|
@ -3,16 +3,11 @@ package commands
|
|||
import (
|
||||
"github.com/bmizerany/assert"
|
||||
"github.com/jingweno/gh/github"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTransformSubmoduleArgs(t *testing.T) {
|
||||
github.DefaultConfigFile = "./test_support/submodule_gh"
|
||||
config := github.Config{User: "jingweno", Token: "123"}
|
||||
github.SaveConfig(&config)
|
||||
defer os.RemoveAll(filepath.Dir(github.DefaultConfigFile))
|
||||
github.CreateTestConfig("jingweno", "123")
|
||||
|
||||
args := NewArgs([]string{"submodule", "add", "jingweno/gh", "vendor/gh"})
|
||||
transformSubmoduleArgs(args)
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
package github
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/howeyc/gopass"
|
||||
"github.com/jingweno/gh/utils"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"io/ioutil"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
|
@ -75,17 +72,14 @@ func (c *Config) FetchCredentials() {
|
|||
}
|
||||
|
||||
if changed {
|
||||
err := SaveConfig(c)
|
||||
err := saveTo(configsFile(), c)
|
||||
utils.Check(err)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
DefaultConfigFile = filepath.Join(os.Getenv("HOME"), ".config", "gh")
|
||||
)
|
||||
|
||||
func CurrentConfig() *Config {
|
||||
config, err := loadConfig()
|
||||
var config Config
|
||||
err := loadFrom(configsFile(), &config)
|
||||
if err != nil {
|
||||
config = Config{}
|
||||
}
|
||||
|
@ -94,59 +88,13 @@ func CurrentConfig() *Config {
|
|||
return &config
|
||||
}
|
||||
|
||||
func loadConfig() (Config, error) {
|
||||
configFile := os.Getenv("GH_CONFIG")
|
||||
if configFile == "" {
|
||||
configFile = DefaultConfigFile
|
||||
}
|
||||
return loadFrom(configFile)
|
||||
}
|
||||
|
||||
func loadFrom(filename string) (Config, error) {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
|
||||
return doLoadFrom(f)
|
||||
}
|
||||
|
||||
func doLoadFrom(f *os.File) (Config, error) {
|
||||
defer f.Close()
|
||||
|
||||
reader := bufio.NewReader(f)
|
||||
dec := json.NewDecoder(reader)
|
||||
|
||||
var c Config
|
||||
err := dec.Decode(&c)
|
||||
if err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func SaveConfig(config *Config) error {
|
||||
return saveTo(DefaultConfigFile, config)
|
||||
}
|
||||
|
||||
func saveTo(filename string, config *Config) error {
|
||||
err := os.MkdirAll(filepath.Dir(filename), 0771)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return doSaveTo(f, config)
|
||||
}
|
||||
|
||||
func doSaveTo(f *os.File, config *Config) error {
|
||||
defer f.Close()
|
||||
|
||||
enc := json.NewEncoder(f)
|
||||
return enc.Encode(config)
|
||||
// Public for testing purpose
|
||||
func CreateTestConfig(user, token string) *Config {
|
||||
f, _ := ioutil.TempFile("", "test-config")
|
||||
defaultConfigsFile = f.Name()
|
||||
|
||||
config := Config{User: "jingweno", Token: "123"}
|
||||
saveTo(f.Name(), &config)
|
||||
|
||||
return &config
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ func TestSaveConfig(t *testing.T) {
|
|||
err := saveTo(file, &config)
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
config, err = loadFrom(file)
|
||||
err = loadFrom(file, &config)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, "jingweno", config.User)
|
||||
assert.Equal(t, "123", config.Token)
|
||||
|
@ -24,7 +24,7 @@ func TestSaveConfig(t *testing.T) {
|
|||
err = saveTo(file, &newConfig)
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
config, err = loadFrom(file)
|
||||
err = loadFrom(file, &config)
|
||||
assert.Equal(t, "foo", config.User)
|
||||
assert.Equal(t, "456", config.Token)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
package github
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/howeyc/gopass"
|
||||
"github.com/jingweno/gh/utils"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultConfigsFile = filepath.Join(os.Getenv("HOME"), ".config", "gh")
|
||||
)
|
||||
|
||||
type Credentials struct {
|
||||
Host string `json:"host"`
|
||||
User string `json:"user"`
|
||||
AccessToken string `json:"access_token"`
|
||||
}
|
||||
|
||||
type Configs struct {
|
||||
Credentials []Credentials
|
||||
}
|
||||
|
||||
func (c *Configs) PromptFor(host string) *Credentials {
|
||||
t := c.Find(host)
|
||||
if t == nil {
|
||||
user := c.PromptForUser()
|
||||
pass := c.PromptForPassword(host, user)
|
||||
token, err := findOrCreateToken(user, pass, "")
|
||||
if err != nil {
|
||||
re := regexp.MustCompile("two-factor authentication OTP code")
|
||||
if re.MatchString(fmt.Sprintf("%s", err)) {
|
||||
code := c.PromptForOTP()
|
||||
token, err = findOrCreateToken(user, pass, code)
|
||||
}
|
||||
}
|
||||
utils.Check(err)
|
||||
|
||||
cc := Credentials{Host: host, User: user, AccessToken: token}
|
||||
c.Credentials = append(c.Credentials, cc)
|
||||
err = saveTo(configsFile(), c)
|
||||
utils.Check(err)
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
func (c *Configs) PromptForUser() string {
|
||||
var user string
|
||||
fmt.Printf("%s username: ", GitHubHost)
|
||||
fmt.Scanln(&user)
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
func (c *Configs) PromptForPassword(host, user string) string {
|
||||
fmt.Printf("%s password for %s (never stored): ", host, user)
|
||||
return string(gopass.GetPasswd())
|
||||
}
|
||||
|
||||
func (c *Configs) PromptForOTP() string {
|
||||
var code string
|
||||
fmt.Print("two-factor authentication code: ")
|
||||
fmt.Scanln(&code)
|
||||
|
||||
return code
|
||||
}
|
||||
|
||||
func (c *Configs) Find(host string) *Credentials {
|
||||
for _, t := range c.Credentials {
|
||||
if t.Host == host {
|
||||
return &t
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func saveTo(filename string, v interface{}) error {
|
||||
err := os.MkdirAll(filepath.Dir(filename), 0771)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
enc := json.NewEncoder(f)
|
||||
return enc.Encode(v)
|
||||
}
|
||||
|
||||
func loadFrom(filename string, v interface{}) error {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
dec := json.NewDecoder(f)
|
||||
return dec.Decode(v)
|
||||
}
|
||||
|
||||
func configsFile() string {
|
||||
configsFile := os.Getenv("GH_CONFIG")
|
||||
if configsFile == "" {
|
||||
configsFile = defaultConfigsFile
|
||||
}
|
||||
|
||||
return configsFile
|
||||
}
|
||||
|
||||
func CurrentConfigs() *Configs {
|
||||
var configs Configs
|
||||
err := loadFrom(configsFile(), &configs)
|
||||
if err != nil {
|
||||
configs = Configs{make([]Credentials, 0)}
|
||||
}
|
||||
|
||||
return &configs
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package github
|
||||
|
||||
import (
|
||||
"github.com/bmizerany/assert"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSaveCredentials(t *testing.T) {
|
||||
c := Credentials{Host: "github.com", User: "jingweno", AccessToken: "123"}
|
||||
file := "./test_support/test"
|
||||
defer os.RemoveAll(filepath.Dir(file))
|
||||
|
||||
err := saveTo(file, &c)
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
var cc Credentials
|
||||
err = loadFrom(file, &cc)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, "github.com", cc.Host)
|
||||
assert.Equal(t, "jingweno", cc.User)
|
||||
assert.Equal(t, "123", cc.AccessToken)
|
||||
}
|
|
@ -249,12 +249,21 @@ func (gh *GitHub) octokit() (c *octokit.Client) {
|
|||
return
|
||||
}
|
||||
|
||||
func New() *GitHub {
|
||||
project := CurrentProject()
|
||||
c := CurrentConfig()
|
||||
c.FetchUser()
|
||||
//func (gh *GitHub) apiHost() string {
|
||||
//gh.
|
||||
//}
|
||||
|
||||
return &GitHub{project, c}
|
||||
func New() *GitHub {
|
||||
p, _ := LocalRepo().CurrentProject()
|
||||
|
||||
return NewClient(p)
|
||||
}
|
||||
|
||||
func NewClient(p *Project) *GitHub {
|
||||
gh := NewWithoutProject()
|
||||
gh.Project = p
|
||||
|
||||
return gh
|
||||
}
|
||||
|
||||
// TODO: detach project from GitHub
|
||||
|
|
|
@ -2,7 +2,9 @@ package github
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/jingweno/gh/git"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Hosts []string
|
||||
|
@ -18,16 +20,23 @@ func (h Hosts) Include(host string) bool {
|
|||
}
|
||||
|
||||
func KnownHosts() (hosts Hosts) {
|
||||
host := os.Getenv("GITHUB_HOST")
|
||||
var mainHost string
|
||||
if host != "" {
|
||||
mainHost = host
|
||||
} else {
|
||||
mainHost = GitHubHost
|
||||
ghHosts, _ := git.Config("gh.host")
|
||||
for _, ghHost := range strings.Split(ghHosts, "\n") {
|
||||
hosts = append(hosts, ghHost)
|
||||
}
|
||||
|
||||
hosts = append(hosts, mainHost)
|
||||
hosts = append(hosts, fmt.Sprintf("ssh.%s", mainHost))
|
||||
defaultHost := defaultHost()
|
||||
hosts = append(hosts, defaultHost)
|
||||
hosts = append(hosts, fmt.Sprintf("ssh.%s", defaultHost))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func defaultHost() string {
|
||||
defaultHost := os.Getenv("GITHUB_HOST")
|
||||
if defaultHost == "" {
|
||||
defaultHost = GitHubHost
|
||||
}
|
||||
|
||||
return defaultHost
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
type Project struct {
|
||||
Name string
|
||||
Owner string
|
||||
Host string
|
||||
}
|
||||
|
||||
func (p Project) String() string {
|
||||
|
@ -77,24 +78,59 @@ func CurrentProject() *Project {
|
|||
|
||||
owner, name := parseOwnerAndName(remote.URL.String())
|
||||
|
||||
return &Project{name, owner}
|
||||
return &Project{Name: name, Owner: owner}
|
||||
}
|
||||
|
||||
func NewProjectFromURL(url *url.URL) (*Project, error) {
|
||||
func NewProjectFromURL(url *url.URL) (p *Project, err error) {
|
||||
if !KnownHosts().Include(url.Host) {
|
||||
return nil, fmt.Errorf("Invalid GitHub URL: %s", url)
|
||||
err = fmt.Errorf("Invalid GitHub URL: %s", url)
|
||||
return
|
||||
}
|
||||
|
||||
parts := strings.SplitN(url.Path, "/", 4)
|
||||
if len(parts) < 2 {
|
||||
return nil, fmt.Errorf("Invalid GitHub URL: %s", url)
|
||||
err = fmt.Errorf("Invalid GitHub URL: %s", url)
|
||||
return
|
||||
}
|
||||
|
||||
name := strings.TrimRight(parts[2], ".git")
|
||||
p = &Project{Name: name, Owner: parts[1], Host: url.Host}
|
||||
|
||||
return &Project{Name: name, Owner: parts[1]}, nil
|
||||
return
|
||||
}
|
||||
|
||||
func NewProject(owner, name, host string) *Project {
|
||||
if strings.Contains(owner, "/") {
|
||||
result := strings.SplitN(owner, "/", 2)
|
||||
owner = result[0]
|
||||
if name == "" {
|
||||
name = result[1]
|
||||
}
|
||||
} else if strings.Contains(name, "/") {
|
||||
result := strings.SplitN(name, "/", 2)
|
||||
if owner == "" {
|
||||
owner = result[0]
|
||||
}
|
||||
name = result[1]
|
||||
}
|
||||
|
||||
if host == "" {
|
||||
host = GitHubHost
|
||||
}
|
||||
|
||||
if owner == "" {
|
||||
c := CurrentConfigs().PromptFor(host)
|
||||
owner = c.User
|
||||
}
|
||||
|
||||
if name == "" {
|
||||
name, _ = utils.DirName()
|
||||
}
|
||||
|
||||
return &Project{Name: name, Owner: owner}
|
||||
}
|
||||
|
||||
// Deprecated:
|
||||
// NewProjectFromOwnerAndName creates a new Project from a specified owner and name
|
||||
//
|
||||
// If the owner or the name string is in the format of OWNER/NAME, it's split and used as the owner and name of the Project.
|
||||
|
|
|
@ -3,16 +3,11 @@ package github
|
|||
import (
|
||||
"github.com/bmizerany/assert"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewProjectOwnerAndName(t *testing.T) {
|
||||
DefaultConfigFile = "./test_support/clone_gh"
|
||||
config := Config{User: "jingweno", Token: "123"}
|
||||
SaveConfig(&config)
|
||||
defer os.RemoveAll(filepath.Dir(DefaultConfigFile))
|
||||
CreateTestConfig("jingweno", "123")
|
||||
|
||||
project := NewProjectFromOwnerAndName("", "mojombo/gh")
|
||||
assert.Equal(t, "mojombo", project.Owner)
|
||||
|
@ -44,7 +39,7 @@ func TestNewProjectOwnerAndName(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestWebURL(t *testing.T) {
|
||||
project := Project{"foo", "bar"}
|
||||
project := Project{Name: "foo", Owner: "bar"}
|
||||
url := project.WebURL("", "", "baz")
|
||||
assert.Equal(t, "https://github.com/bar/foo/baz", url)
|
||||
|
||||
|
@ -53,7 +48,7 @@ func TestWebURL(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGitURL(t *testing.T) {
|
||||
project := Project{"foo", "bar"}
|
||||
project := Project{Name: "foo", Owner: "bar"}
|
||||
url := project.GitURL("gh", "jingweno", false)
|
||||
assert.Equal(t, "git://github.com/jingweno/gh.git", url)
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
)
|
||||
|
||||
func TestFullBaseAndFullHead(t *testing.T) {
|
||||
project := Project{"name", "owner"}
|
||||
project := Project{Name: "name", Owner: "owner"}
|
||||
repo := Repo{"base", "head", &project}
|
||||
|
||||
assert.Equal(t, "owner:base", repo.FullBase())
|
||||
|
|
Загрузка…
Ссылка в новой задаче