func (c *Configs) PromptFor(host string) *Credentials { cc := c.find(host) if cc == nil { user := c.PromptForUser() pass := c.PromptForPassword(host, user) // Create Client with a stub Credentials client := Client{Credentials: &Credentials{Host: host}} token, err := client.FindOrCreateToken(user, pass, "") if err != nil { if ce, ok := err.(*ClientError); ok && ce.Is2FAError() { code := c.PromptForOTP() token, err = client.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 cc }
func (c *Config) FetchCredentials() { var changed bool if c.User == "" { c.FetchUser() changed = true } if c.Token == "" { password := c.FetchPassword() token, err := findOrCreateToken(c.User, password, "") // TODO: return an two factor auth failure error if err != nil { re := regexp.MustCompile("two-factor authentication OTP code") if re.MatchString(fmt.Sprintf("%s", err)) { code := c.FetchTwoFactorCode() token, err = findOrCreateToken(c.User, password, code) } } utils.Check(err) c.Token = token changed = true } if changed { err := SaveConfig(c) utils.Check(err) } }
/* $ gh ci-status > (prints CI state of HEAD and exits with appropriate code) > One of: success (0), error (1), failure (1), pending (2), no status (3) $ gh ci-status -v > (prints CI state of HEAD, the URL to the CI build results and exits with appropriate code) > One of: success (0), error (1), failure (1), pending (2), no status (3) $ gh ci-status BRANCH > (prints CI state of BRANCH and exits with appropriate code) > One of: success (0), error (1), failure (1), pending (2), no status (3) $ gh ci-status SHA > (prints CI state of SHA and exits with appropriate code) > One of: success (0), error (1), failure (1), pending (2), no status (3) */ func ciStatus(cmd *Command, args *Args) { ref := "HEAD" if !args.IsParamsEmpty() { ref = args.RemoveParam(0) } localRepo := github.LocalRepo() project, err := localRepo.MainProject() utils.Check(err) sha, err := git.Ref(ref) if err != nil { err = fmt.Errorf("Aborted: no revision could be determined from '%s'", ref) } utils.Check(err) if args.Noop { fmt.Printf("Would request CI status for %s\n", sha) } else { state, targetURL, exitCode, err := fetchCiStatus(project, sha) utils.Check(err) if flagCiStatusVerbose && targetURL != "" { fmt.Printf("%s: %s\n", state, targetURL) } else { fmt.Println(state) } os.Exit(exitCode) } }
/* $ gh ci-status > (prints CI state of HEAD and exits with appropriate code) > One of: success (0), error (1), failure (1), pending (2), no status (3) $ gh ci-status BRANCH > (prints CI state of BRANCH and exits with appropriate code) > One of: success (0), error (1), failure (1), pending (2), no status (3) $ gh ci-status SHA > (prints CI state of SHA and exits with appropriate code) > One of: success (0), error (1), failure (1), pending (2), no status (3) */ func ciStatus(cmd *Command, args *Args) { ref := "HEAD" if !args.IsParamsEmpty() { ref = args.RemoveParam(0) } ref, err := git.Ref(ref) utils.Check(err) args.Replace("", "") if args.Noop { fmt.Printf("Would request CI status for %s\n", ref) } else { state, targetURL, desc, exitCode, err := fetchCiStatus(ref) utils.Check(err) fmt.Println(state) if targetURL != "" { fmt.Println(targetURL) } if desc != "" { fmt.Println(desc) } os.Exit(exitCode) } }
func parseOwnerAndName() (name, remote string) { remote, err := git.Remote() utils.Check(err) url, err := mustMatchGitHubURL(remote) utils.Check(err) return url[1], url[2] }
/* $ gh create ... create repo on github ... > git remote add -f origin [email protected]:YOUR_USER/CURRENT_REPO.git # with description: $ gh create -d 'It shall be mine, all mine!' $ gh create recipes [ repo created on GitHub ] > git remote add origin [email protected]:YOUR_USER/recipes.git $ gh create sinatra/recipes [ repo created in GitHub organization ] > git remote add origin [email protected]:sinatra/recipes.git */ func create(command *Command, args *Args) { _, err := git.Dir() if err != nil { err = fmt.Errorf("'create' must be run from inside a git repository") utils.Check(err) } var newRepoName string if args.IsParamsEmpty() { newRepoName, err = utils.DirName() utils.Check(err) } else { reg := regexp.MustCompile("^[^-]") if !reg.MatchString(args.FirstParam()) { err = fmt.Errorf("invalid argument: %s", args.FirstParam()) utils.Check(err) } newRepoName = args.FirstParam() } configs := github.CurrentConfigs() credentials := configs.DefaultCredentials() owner := credentials.User if strings.Contains(newRepoName, "/") { split := strings.SplitN(newRepoName, "/", 2) owner = split[0] newRepoName = split[1] } project := github.NewProject(owner, newRepoName, credentials.Host) gh := github.NewClient(project.Host) var action string if gh.IsRepositoryExist(project) { fmt.Printf("%s already exists on %s\n", project, project.Host) action = "set remote origin" } else { action = "created repository" if !args.Noop { repo, err := gh.CreateRepository(project, flagCreateDescription, flagCreateHomepage, flagCreatePrivate) utils.Check(err) project = github.NewProject(repo.FullName, "", project.Host) } } remote, _ := github.OriginRemote() if remote == nil { url := project.GitURL("", "", true) args.Replace("git", "remote", "add", "-f", "origin", url) } else { args.Replace("git", "remote", "-v") } args.After("echo", fmt.Sprintf("%s:", action), project.String()) }
func alias(command *Command, args *Args) { var shell string if args.ParamsSize() > 0 { shell = args.FirstParam() } else { shell = os.Getenv("SHELL") } if shell == "" { utils.Check(fmt.Errorf("Unknown shell")) } shells := []string{"bash", "zsh", "sh", "ksh", "csh", "fish"} shell = filepath.Base(shell) var validShell bool for _, s := range shells { if s == shell { validShell = true break } } if !validShell { err := fmt.Errorf("Unsupported shell. Supported shell: %s.", strings.Join(shells, ", ")) utils.Check(err) } if flagAliasScript { fmt.Println("alias git=gh") if "zsh" == shell { fmt.Println("if type compdef > /dev/null; then") fmt.Println(" compdef gh=git") fmt.Println("fi") } } else { var profile string switch shell { case "bash": profile = "~/.bash_profile" case "zsh": profile = "~/.zshrc" case "ksh": profile = "~/.profile" default: profile = "your profile" } msg := fmt.Sprintf("# Wrap git automatically by adding the following to %s\n", profile) fmt.Println(msg) fmt.Println("eval \"$(gh alias -s)\"") } os.Exit(0) }
func report(reportedError error, stack string) { title, body, err := reportTitleAndBody(reportedError, stack) utils.Check(err) project := NewProject(ghProjectOwner, ghProjectName, GitHubHost) gh := NewClient(project.Host) issue, err := gh.CreateIssue(project, title, body, []string{"Crash Report"}) utils.Check(err) fmt.Println(issue.HTMLURL) }
/* $ gh browse > open https://github.com/YOUR_USER/CURRENT_REPO $ gh browse commit/SHA > open https://github.com/YOUR_USER/CURRENT_REPO/commit/SHA $ gh browse issues > open https://github.com/YOUR_USER/CURRENT_REPO/issues $ gh browse -p jingweno/gh > open https://github.com/jingweno/gh $ gh browse -p jingweno/gh commit/SHA > open https://github.com/jingweno/gh/commit/SHA $ gh browse -p resque > open https://github.com/YOUR_USER/resque $ gh browse -p resque network > open https://github.com/YOUR_USER/resque/network */ func browse(command *Command, args *Args) { var ( project *github.Project branch *github.Branch err error ) localRepo := github.LocalRepo() if flagBrowseProject != "" { // gh browse -p jingweno/gh // gh browse -p gh project = github.NewProject("", flagBrowseProject, "") } else { // gh browse branch, project, err = localRepo.RemoteBranchAndProject("") utils.Check(err) } if project == nil { err := fmt.Errorf(command.FormattedUsage()) utils.Check(err) } master := localRepo.MasterBranch() if branch == nil { branch = master } var subpage string if !args.IsParamsEmpty() { subpage = args.RemoveParam(0) } if subpage == "commits" { subpage = fmt.Sprintf("commits/%s", branchInURL(branch)) } else if subpage == "tree" || subpage == "" { if !reflect.DeepEqual(branch, master) && branch.IsRemote() { subpage = fmt.Sprintf("tree/%s", branchInURL(branch)) } } pageUrl := project.WebURL("", "", subpage) launcher, err := utils.BrowserLauncher() utils.Check(err) if flagBrowseURLOnly { args.Replace("echo", pageUrl) } else { args.Replace(launcher[0], "", launcher[1:]...) args.AppendParams(pageUrl) } }
/* $ gh issue */ func issue(cmd *Command, args *Args) { gh := github.New() // list all issues if args.Noop { fmt.Printf("Would request list of issues for %s\n", gh.Project) } else { issues, err := gh.Issues() utils.Check(err) for _, issue := range issues { var url string // use the pull request URL if we have one if issue.PullRequest.HTMLURL != "" { url = issue.PullRequest.HTMLURL } else { url = issue.HTMLURL } // "nobody" should have more than 1 million github issues fmt.Printf("% 7d] %s ( %s )\n", issue.Number, issue.Title, url) } } os.Exit(0) }
func tranformFetchArgs(args *Args) { remotes, err := git.Remotes() utils.Check(err) names := parseRemoteNames(args) gh := github.New() projects := []github.Project{} ownerRegexp := regexp.MustCompile(OwnerRe) for _, name := range names { if ownerRegexp.MatchString(name) && !isRemoteExist(remotes, name) { project := github.NewProjectFromNameAndOwner("", name) repo, err := gh.Repository(project) if err != nil { continue } project = github.NewProjectFromNameAndOwner("", repo.FullName) projects = append(projects, project) } } for _, project := range projects { var isSSH bool if project.Owner == gh.Config.FetchUser() { isSSH = true } args.Before("git", "remote", "add", project.Owner, project.GitURL("", "", isSSH)) } }
func transformRemoteArgs(args *Args) { ownerWithName := args.LastParam() owner, name, match := parseRepoNameOwner(ownerWithName) if !match { return } var err error if name == "" { name, err = utils.DirName() utils.Check(err) } isPriavte := parseRemotePrivateFlag(args) if owner == "origin" { owner = github.CurrentConfig().FetchUser() } else if args.ParamsSize() > 2 { // `git remote add jingweno foo/bar` args.RemoveParam(args.ParamsSize() - 1) } project := github.Project{Owner: owner, Name: name} url := project.GitURL(name, owner, isPriavte) args.AppendParams(url) }
func CurrentProject() *Project { remote, err := git.OriginRemote() utils.Check(err) owner, name := parseOwnerAndName(remote.URL) return &Project{name, owner} }
func ci(cmd *Command, args []string) { ref := "HEAD" if len(args) > 0 { ref = args[0] } ref, err := git.Ref(ref) utils.Check(err) gh := github.New() status, err := gh.CiStatus(ref) utils.Check(err) var state string var targetURL string var desc string var exitCode int if status == nil { state = "no status" } else { state = status.State targetURL = status.TargetURL desc = status.Description } switch state { case "success": exitCode = 0 case "failure", "error": exitCode = 1 case "pending": exitCode = 2 default: exitCode = 3 } fmt.Println(state) if targetURL != "" { fmt.Println(targetURL) } if desc != "" { fmt.Println(desc) } os.Exit(exitCode) }
func detectContentType(path string, fi os.FileInfo) string { file, err := os.Open(path) utils.Check(err) defer file.Close() fileHeader := &bytes.Buffer{} headerSize := int64(512) if fi.Size() < headerSize { headerSize = fi.Size() } // The content type detection only uses 512 bytes at most. // This way we avoid copying the whole content for big files. _, err = io.CopyN(fileHeader, file, headerSize) utils.Check(err) return http.DetectContentType(fileHeader.Bytes()) }
func (gh *GitHub) client() *octokat.Client { config := gh.config if config.User == "" { config.FetchUser() } if config.Token == "" { password := config.FetchPassword() token, err := findOrCreateToken(config.User, password) utils.Check(err) config.Token = token err = saveConfig(config) utils.Check(err) } return octokat.NewClient().WithToken(config.Token) }
func runVersion(cmd *Command, args []string) { gitVersion, err := git.Version() utils.Check(err) ghVersion := fmt.Sprintf("gh version %s", Version) fmt.Println(gitVersion) fmt.Println(ghVersion) }
func runInLocalRepo(fn func(localRepo *github.GitHubRepo, project *github.Project, client *github.Client)) { localRepo := github.LocalRepo() project, err := localRepo.CurrentProject() utils.Check(err) client := github.NewClient(project.Host) fn(localRepo, project, client) os.Exit(0) }
// Remove the scheme from host when the credential url is absolute. func rawHost(host string) string { u, err := url.Parse(host) utils.Check(err) if u.IsAbs() { return u.Host } else { return u.Path } }
func uploadReleaseAssets(gh *github.Client, release *octokit.Release, assetsDir string) { var wg sync.WaitGroup var totalAssets, countAssets uint64 filepath.Walk(assetsDir, func(path string, fi os.FileInfo, err error) error { if !fi.IsDir() { totalAssets += 1 } return nil }) printUploadProgress(&countAssets, totalAssets) filepath.Walk(assetsDir, func(path string, fi os.FileInfo, err error) error { if !fi.IsDir() { wg.Add(1) go func() { defer func() { atomic.AddUint64(&countAssets, uint64(1)) printUploadProgress(&countAssets, totalAssets) wg.Done() }() uploadUrl, err := release.UploadURL.Expand(octokit.M{"name": fi.Name()}) utils.Check(err) contentType := detectContentType(path, fi) file, err := os.Open(path) utils.Check(err) defer file.Close() err = gh.UploadReleaseAsset(uploadUrl, file, contentType) utils.Check(err) }() } return nil }) wg.Wait() }
/* $ gh compare refactor > open https://github.com/CURRENT_REPO/compare/refactor $ gh compare 1.0..1.1 > open https://github.com/CURRENT_REPO/compare/1.0...1.1 $ gh compare -u other-user patch > open https://github.com/other-user/REPO/compare/patch */ func compare(command *Command, args *Args) { localRepo := github.LocalRepo() var ( branch *github.Branch project *github.Project r string err error ) branch, project, err = localRepo.RemoteBranchAndProject("") utils.Check(err) if args.IsParamsEmpty() { master := localRepo.MasterBranch() if master.ShortName() == branch.ShortName() { err = fmt.Errorf(command.FormattedUsage()) utils.Check(err) } else { r = branch.ShortName() } } else { r = parseCompareRange(args.RemoveParam(args.ParamsSize() - 1)) if args.IsParamsEmpty() { project, err = localRepo.CurrentProject() utils.Check(err) } else { project = github.NewProject(args.RemoveParam(args.ParamsSize()-1), "", "") } } r = strings.Replace(r, "/", ";", -1) subpage := utils.ConcatPaths("compare", r) url := project.WebURL("", "", subpage) launcher, err := utils.BrowserLauncher() utils.Check(err) if flagCompareURLOnly { args.Replace("echo", url) } else { args.Replace(launcher[0], "", launcher[1:]...) args.AppendParams(url) } }
func hasGitRemote(name string) bool { remotes, err := github.Remotes() utils.Check(err) for _, remote := range remotes { if remote.Name == name { return true } } return false }
func New(cmd string) *Cmd { cmds, err := shellquote.Split(cmd) utils.Check(err) name := cmds[0] args := make([]string, 0) for _, arg := range cmds[1:] { args = append(args, arg) } return &Cmd{Name: name, Args: args} }
func (c *Config) FetchPassword() string { msg := fmt.Sprintf("%s password for %s (never stored): ", GitHubHost, c.User) fmt.Print(msg) pass := gopass.GetPasswd() if len(pass) == 0 { utils.Check(errors.New("Password cannot be empty")) } return string(pass) }
func fork(cmd *Command, args []string) { gh := github.New() project := gh.Project newRemote, err := gh.ForkRepository(project.Name, project.Owner, flagForkNoRemote) utils.Check(err) if !flagForkNoRemote && newRemote != "" { fmt.Printf("New remote: %s\n", newRemote) } }
func runVersion(cmd *Command, args *Args) { gitVersion, err := git.Version() utils.Check(err) ghVersion := fmt.Sprintf("gh version %s", Version) fmt.Println(gitVersion) fmt.Println(ghVersion) os.Exit(0) }
func createIssue(cmd *Command, args *Args) { runInLocalRepo(func(localRepo *github.GitHubRepo, project *github.Project, gh *github.Client) { if args.Noop { fmt.Printf("Would create an issue for %s\n", project) } else { title, body, err := getTitleAndBodyFromFlags(flagIssueMessage, flagIssueFile) utils.Check(err) if title == "" { title, body, err = writeIssueTitleAndBody(project) utils.Check(err) } issue, err := gh.CreateIssue(project, title, body, flagIssueLabels) utils.Check(err) fmt.Println(issue.HTMLURL) } }) }
func (p *Project) LocalRepoWith(base, head string) *Repo { if base == "" { base = "master" } if head == "" { headBranch, err := git.Head() utils.Check(err) head = headBranch.ShortName() } return &Repo{base, head, p} }
func (c *Config) FetchCredentials() { var changed bool if c.User == "" { c.FetchUser() changed = true } if c.Token == "" { password := c.FetchPassword() token, err := findOrCreateToken(c.User, password) utils.Check(err) c.Token = token changed = true } if changed { err := SaveConfig(c) utils.Check(err) } }
/* # while on a topic branch called "feature": $ gh pull-request [ opens text editor to edit title & body for the request ] [ opened pull request on GitHub for "YOUR_USER:feature" ] # explicit pull base & head: $ gh pull-request -b jingweno:master -h jingweno:feature $ gh pull-request -i 123 [ attached pull request to issue #123 ] */ func pullRequest(cmd *Command, args *Args) { var title, body string if args.ParamsSize() == 1 { title = args.RemoveParam(0) } gh := github.New() repo := gh.Project.LocalRepoWith(flagPullRequestBase, flagPullRequestHead) if title == "" && flagPullRequestIssue == "" { t, b, err := writePullRequestTitleAndBody(repo) utils.Check(err) title = t body = b } if title == "" && flagPullRequestIssue == "" { utils.Check(fmt.Errorf("Aborting due to empty pull request title")) } var pullRequestURL string if args.Noop { args.Before(fmt.Sprintf("Would request a pull request to %s from %s", repo.FullBase(), repo.FullHead()), "") pullRequestURL = "PULL_REQUEST_URL" } else { if title != "" { pr, err := gh.CreatePullRequest(repo.Base, repo.Head, title, body) utils.Check(err) pullRequestURL = pr.HTMLURL } if flagPullRequestIssue != "" { pr, err := gh.CreatePullRequestForIssue(repo.Base, repo.Head, flagPullRequestIssue) utils.Check(err) pullRequestURL = pr.HTMLURL } } args.Replace("echo", "", pullRequestURL) }