This commit is contained in:
Jingwen Owen Ou 2013-12-07 19:27:53 -08:00
Родитель 23f68481e4
Коммит 5ddd2d45c7
17 изменённых файлов: 259 добавлений и 128 удалений

Просмотреть файл

@ -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)
}

126
github/configs.go Normal file
Просмотреть файл

@ -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
}

24
github/configs_test.go Normal file
Просмотреть файл

@ -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())