/* $ 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) } }
func tranformFetchArgs(args *Args) error { names := parseRemoteNames(args) localRepo := github.LocalRepo() projects := make(map[*github.Project]bool) ownerRegexp := regexp.MustCompile(OwnerRe) for _, name := range names { if ownerRegexp.MatchString(name) { _, err := localRepo.RemoteByName(name) if err != nil { project := github.NewProject(name, "", "") gh := github.NewClient(project.Host) repo, err := gh.Repository(project) if err != nil { continue } projects[project] = repo.Private } } } for project, private := range projects { args.Before("git", "remote", "add", project.Owner, project.GitURL("", "", private)) } return nil }
func transformCheckoutArgs(args *Args) error { words := args.Words() if len(words) == 0 { return nil } checkoutURL := words[0] url, err := github.ParseURL(checkoutURL) if err != nil { return nil } var newBranchName string if len(words) > 1 { newBranchName = words[1] } pullURLRegex := regexp.MustCompile("^pull/(\\d+)") projectPath := url.ProjectPath() if !pullURLRegex.MatchString(projectPath) { return nil } id := pullURLRegex.FindStringSubmatch(projectPath)[1] gh := github.NewClient(url.Project.Host) pullRequest, err := gh.PullRequest(url.Project, id) if err != nil { return err } if idx := args.IndexOfParam(newBranchName); idx >= 0 { args.RemoveParam(idx) } user, branch := parseUserBranchFromPR(pullRequest) if pullRequest.Head.Repo.ID == 0 { return fmt.Errorf("Error: %s's fork is not available anymore", user) } if newBranchName == "" { newBranchName = fmt.Sprintf("%s-%s", user, branch) } repo := github.LocalRepo() _, err = repo.RemoteByName(user) if err == nil { args.Before("git", "remote", "set-branches", "--add", user, branch) remoteURL := fmt.Sprintf("+refs/heads/%s:refs/remotes/%s/%s", branch, user, branch) args.Before("git", "fetch", user, remoteURL) } else { u := url.Project.GitURL("", user, pullRequest.Head.Repo.Private) args.Before("git", "remote", "add", "-f", "-t", branch, user, u) } idx := args.IndexOfParam(checkoutURL) args.RemoveParam(idx) args.InsertParam(idx, "--track", "-B", newBranchName, fmt.Sprintf("%s/%s", user, branch)) return nil }
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) }
/* $ 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 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 transformRemoteArgs(args *Args) { ownerWithName := args.LastParam() owner, name := parseRepoNameOwner(ownerWithName) if owner == "" { return } localRepo := github.LocalRepo() var repoName string if name == "" { project, err := localRepo.MainProject() if err == nil { repoName = project.Name } else { repoName, err = utils.DirName() utils.Check(err) } name = repoName } words := args.Words() isPriavte := parseRemotePrivateFlag(args) if len(words) == 2 && words[1] == "origin" { // gh add origin credentials := github.CurrentConfigs().DefaultCredentials() owner = credentials.User name = repoName } else if len(words) == 2 { // gh remote add jingweno foo/bar if idx := args.IndexOfParam(words[1]); idx != -1 { args.ReplaceParam(idx, owner) } } else { args.RemoveParam(args.ParamsSize() - 1) } project := github.NewProject(owner, name, "") // for GitHub Enterprise isPriavte = isPriavte || project.Host != github.GitHubHost url := project.GitURL(name, owner, isPriavte) args.AppendParams(url) }
func transformPushArgs(args *Args) { refs := []string{} if args.ParamsSize() > 1 { refs = args.Params[1:] } remotes := strings.Split(args.FirstParam(), ",") args.ReplaceParam(0, remotes[0]) if len(refs) == 0 { localRepo := github.LocalRepo() head, err := localRepo.CurrentBranch() utils.Check(err) refs = []string{head.ShortName()} args.AppendParams(refs...) } for _, remote := range remotes[1:] { afterCmd := []string{"git", "push", remote} afterCmd = append(afterCmd, refs...) args.After(afterCmd...) } }
/* $ gh fork [ repo forked on GitHub ] > git remote add -f YOUR_USER [email protected]:YOUR_USER/CURRENT_REPO.git $ gh fork --no-remote [ repo forked on GitHub ] */ func fork(cmd *Command, args *Args) { localRepo := github.LocalRepo() project, err := localRepo.MainProject() utils.Check(err) configs := github.CurrentConfigs() credentials := configs.PromptFor(project.Host) forkProject := github.NewProject(credentials.User, project.Name, project.Host) client := github.NewClient(project.Host) existingRepo, err := client.Repository(forkProject) if err == nil { var parentURL *github.URL if parent := existingRepo.Parent; parent != nil { parentURL, _ = github.ParseURL(parent.HTMLURL) } if parentURL == nil || !reflect.DeepEqual(parentURL.Project, project) { err = fmt.Errorf("Error creating fork: %s already exists on %s", forkProject, forkProject.Host) utils.Check(err) } } else { if !args.Noop { _, err := client.ForkRepository(project) utils.Check(err) } } if flagForkNoRemote { os.Exit(0) } else { u := forkProject.GitURL("", "", true) args.Replace("git", "remote", "add", "-f", forkProject.Owner, u) args.After("echo", fmt.Sprintf("new remote: %s", forkProject.Owner)) } }
func parseCherryPickProjectAndSha(ref string) (project *github.Project, sha string) { url, err := github.ParseURL(ref) if err == nil { commitRegex := regexp.MustCompile("^commit\\/([a-f0-9]{7,40})") projectPath := url.ProjectPath() if commitRegex.MatchString(projectPath) { sha = commitRegex.FindStringSubmatch(projectPath)[1] project = url.Project return } } ownerWithShaRegexp := regexp.MustCompile("^(%s)@([a-f0-9]{7,40})$") if ownerWithShaRegexp.MatchString(ref) { matches := ownerWithShaRegexp.FindStringSubmatch(ref) sha = matches[2] project, err := github.LocalRepo().CurrentProject() utils.Check(err) project.Owner = matches[1] } return }
/* # 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 -m "title\n\nbody" [ create pull request with title & body ] $ gh pull-request -i 123 [ attached pull request to issue #123 ] $ gh pull-request https://github.com/jingweno/gh/pull/123 [ attached pull request to issue #123 ] $ gh pull-request -F FILE [ create pull request with title & body from FILE ] */ func pullRequest(cmd *Command, args *Args) { localRepo := github.LocalRepo() currentBranch, err := localRepo.CurrentBranch() utils.Check(err) baseProject, err := localRepo.MainProject() utils.Check(err) client := github.NewClient(baseProject.Host) trackedBranch, headProject, err := localRepo.RemoteBranchAndProject(client.Credentials.User) utils.Check(err) var ( base, head string force bool ) force = flagPullRequestForce if flagPullRequestBase != "" { baseProject, base = parsePullRequestProject(baseProject, flagPullRequestBase) } if flagPullRequestHead != "" { headProject, head = parsePullRequestProject(headProject, flagPullRequestHead) } if args.ParamsSize() == 1 { arg := args.RemoveParam(0) flagPullRequestIssue = parsePullRequestIssueNumber(arg) } if base == "" { masterBranch := localRepo.MasterBranch() base = masterBranch.ShortName() } if head == "" { if !trackedBranch.IsRemote() { // the current branch tracking another branch // pretend there's no upstream at all trackedBranch = nil } else { if reflect.DeepEqual(baseProject, headProject) && base == trackedBranch.ShortName() { e := fmt.Errorf(`Aborted: head branch is the same as base ("%s")`, base) e = fmt.Errorf("%s\n(use `-h <branch>` to specify an explicit pull request head)", e) utils.Check(e) } } if trackedBranch == nil { head = currentBranch.ShortName() } else { head = trackedBranch.ShortName() } } title, body, err := getTitleAndBodyFromFlags(flagPullRequestMessage, flagPullRequestFile) utils.Check(err) fullBase := fmt.Sprintf("%s:%s", baseProject.Owner, base) fullHead := fmt.Sprintf("%s:%s", headProject.Owner, head) if !force && trackedBranch != nil { remoteCommits, _ := git.RefList(trackedBranch.LongName(), "") if len(remoteCommits) > 0 { err = fmt.Errorf("Aborted: %d commits are not yet pushed to %s", len(remoteCommits), trackedBranch.LongName()) err = fmt.Errorf("%s\n(use `-f` to force submit a pull request anyway)", err) utils.Check(err) } } if title == "" && flagPullRequestIssue == "" { commits, _ := git.RefList(base, head) title, body, err = writePullRequestTitleAndBody(base, head, fullBase, fullHead, commits) utils.Check(err) } 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", fullBase, fullHead), "") pullRequestURL = "PULL_REQUEST_URL" } else { if title != "" { pr, err := client.CreatePullRequest(baseProject, base, fullHead, title, body) utils.Check(err) pullRequestURL = pr.HTMLURL } if flagPullRequestIssue != "" { pr, err := client.CreatePullRequestForIssue(baseProject, base, fullHead, flagPullRequestIssue) utils.Check(err) pullRequestURL = pr.HTMLURL } } args.Replace("echo", "", pullRequestURL) if flagPullRequestIssue != "" { args.After("echo", "Warning: Issue to pull request conversion is deprecated and might not work in the future.") } }