func tranformFetchArgs(args *Args) error { names := parseRemoteNames(args) localRepo, err := github.LocalRepo() utils.Check(err) currentProject, currentProjectErr := localRepo.CurrentProject() projects := make(map[*github.Project]bool) ownerRegexp := regexp.MustCompile(fmt.Sprintf("^%s$", OwnerRe)) for _, name := range names { if ownerRegexp.MatchString(name) && !isCloneable(name) { _, err := localRepo.RemoteByName(name) if err != nil { utils.Check(currentProjectErr) project := github.NewProject(name, currentProject.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 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, err := github.LocalRepo() utils.Check(err) 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...) } }
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("^([a-zA-Z0-9][a-zA-Z0-9-]*)@([a-f0-9]{7,40})$") if ownerWithShaRegexp.MatchString(ref) { matches := ownerWithShaRegexp.FindStringSubmatch(ref) sha = matches[2] localRepo, err := github.LocalRepo() utils.Check(err) project, err = localRepo.CurrentProject() utils.Check(err) project.Owner = matches[1] } return }
func downloadRelease(cmd *Command, args *Args) { tagName := cmd.Arg(0) if tagName == "" { utils.Check(fmt.Errorf("Missing argument TAG")) } localRepo, err := github.LocalRepo() utils.Check(err) project, err := localRepo.MainProject() utils.Check(err) gh := github.NewClient(project.Host) release, err := gh.FetchRelease(project, tagName) utils.Check(err) for _, asset := range release.Assets { ui.Printf("Downloading %s ...\n", asset.Name) err := downloadReleaseAsset(asset, gh) utils.Check(err) } args.NoForward() }
/* $ 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, err := github.LocalRepo() utils.Check(err) 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 { ui.Printf("Would request CI status for %s\n", sha) } else { state, targetURL, exitCode, err := fetchCiStatus(project, sha) utils.Check(err) if flagCiStatusVerbose && targetURL != "" { ui.Printf("%s: %s\n", state, targetURL) } else { ui.Println(state) } os.Exit(exitCode) } }
func uploadAssets(gh *github.Client, release *github.Release, assets []string, args *Args) { for _, asset := range assets { var label string parts := strings.SplitN(asset, "#", 2) asset = parts[0] if len(parts) > 1 { label = parts[1] } if args.Noop { if label == "" { ui.Errorf("Would attach release asset `%s'\n", asset) } else { ui.Errorf("Would attach release asset `%s' with label `%s'\n", asset, label) } } else { for _, existingAsset := range release.Assets { if existingAsset.Name == filepath.Base(asset) { err := gh.DeleteReleaseAsset(&existingAsset) utils.Check(err) break } } ui.Errorf("Attaching release asset `%s'...\n", asset) _, err := gh.UploadReleaseAsset(release, asset, label) utils.Check(err) } } }
func transformRemoteArgs(args *Args) { ownerWithName := args.LastParam() owner, name := parseRepoNameOwner(ownerWithName) if owner == "" { return } localRepo, err := github.LocalRepo() utils.Check(err) var repoName, host string if name == "" { project, err := localRepo.MainProject() if err == nil { repoName = project.Name host = project.Host } else { dirName, err := git.WorkdirName() utils.Check(err) repoName = github.SanitizeProjectName(dirName) } name = repoName } hostConfig, err := github.CurrentConfig().DefaultHost() if err != nil { utils.Check(github.FormatError("adding remote", err)) } words := args.Words() isPrivate := parseRemotePrivateFlag(args) if len(words) == 2 && words[1] == "origin" { // Origin special case triggers default user/repo owner = hostConfig.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) } if strings.ToLower(owner) == strings.ToLower(hostConfig.User) { owner = hostConfig.User isPrivate = true } project := github.NewProject(owner, name, host) // for GitHub Enterprise isPrivate = isPrivate || project.Host != github.GitHubHost url := project.GitURL(name, owner, isPrivate) args.AppendParams(url) }
func ciStatus(cmd *Command, args *Args) { ref := "HEAD" if !args.IsParamsEmpty() { ref = args.RemoveParam(0) } localRepo, err := github.LocalRepo() utils.Check(err) 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 { ui.Printf("Would request CI status for %s\n", sha) } else { gh := github.NewClient(project.Host) response, err := gh.FetchCIStatus(project, sha) utils.Check(err) state := response.State if len(response.Statuses) == 0 { state = "" } var exitCode int switch state { case "success": exitCode = 0 case "failure", "error": exitCode = 1 case "pending": exitCode = 2 default: exitCode = 3 } if flagCiStatusVerbose && len(response.Statuses) > 0 { verboseFormat(response.Statuses) } else { if state != "" { ui.Println(state) } else { ui.Println("no status") } } os.Exit(exitCode) } }
func (c *Config) PromptForHost(host string) (h *Host, err error) { token := c.DetectToken() tokenFromEnv := token != "" h = c.Find(host) if h != nil { if h.User == "" { utils.Check(CheckWriteable(configsFile())) // User is missing from the config: this is a broken config probably // because it was created with an old (broken) version of hub. Let's fix // it now. See issue #1007 for details. user := c.PromptForUser(host) if user == "" { utils.Check(fmt.Errorf("missing user")) } h.User = user err := newConfigService().Save(configsFile(), c) utils.Check(err) } if tokenFromEnv { h.AccessToken = token } else { return } } else { h = &Host{ Host: host, AccessToken: token, Protocol: "https", } c.Hosts = append(c.Hosts, h) } client := NewClientWithHost(h) if !tokenFromEnv { utils.Check(CheckWriteable(configsFile())) err = c.authorizeClient(client, host) if err != nil { return } } currentUser, err := client.CurrentUser() if err != nil { return } h.User = currentUser.Login if !tokenFromEnv { err = newConfigService().Save(configsFile(), c) } return }
/* $ 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, err := github.LocalRepo() utils.Check(err) var ( branch *github.Branch project *github.Project r string ) branch, project, err = localRepo.RemoteBranchAndProject("", false) utils.Check(err) usageHelp := func() { utils.Check(fmt.Errorf("Usage: hub compare [-u] [-b <BASE>] [<USER>] [[<START>...]<END>]")) } if args.IsParamsEmpty() { if branch == nil || (branch.IsMaster() && flagCompareBase == "") || (flagCompareBase == branch.ShortName()) { usageHelp() } else { r = branch.ShortName() if flagCompareBase != "" { r = parseCompareRange(flagCompareBase + "..." + r) } } } else { if flagCompareBase != "" { usageHelp() } 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), "", "") } } } subpage := utils.ConcatPaths("compare", rangeQueryEscape(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 runInLocalRepo(fn func(localRepo *github.GitHubRepo, project *github.Project, client *github.Client)) { localRepo, err := github.LocalRepo() utils.Check(err) project, err := localRepo.CurrentProject() utils.Check(err) client := github.NewClient(project.Host) fn(localRepo, project, client) os.Exit(0) }
func report(reportedError error, stack string) { title, body, err := reportTitleAndBody(reportedError, stack) utils.Check(err) project := NewProject(hubProjectOwner, hubProjectName, GitHubHost) gh := NewClient(project.Host) issue, err := gh.CreateIssue(project, title, body, []string{"Crash Report"}) utils.Check(err) ui.Println(issue.HTMLURL) }
/* $ hub issue */ func issue(cmd *Command, args *Args) { runInLocalRepo(func(localRepo *github.GitHubRepo, project *github.Project, gh *github.Client) { if args.Noop { ui.Printf("Would request list of issues for %s\n", project) } else { issues, err := gh.Issues(project) 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 } if flagIssueAssignee == "" || strings.EqualFold(issue.Assignee.Login, flagIssueAssignee) { // "nobody" should have more than 1 million github issues ui.Printf("% 7d] %s ( %s )\n", issue.Number, issue.Title, url) } } } }) }
func runHelp(helpCmd *Command, args *Args) { if args.IsParamsEmpty() { printUsage() os.Exit(0) } if args.HasFlags("-a", "--all") { args.After("echo", "\nhub custom commands\n") args.After("echo", " ", strings.Join(customCommands(), " ")) return } command := args.FirstParam() if command == "hub" { err := displayManPage("hub.1", args) if err != nil { utils.Check(err) } } if c := lookupCmd(command); c != nil { if !args.HasFlags("--plain-text") { manPage := fmt.Sprintf("hub-%s.1", c.Name()) err := displayManPage(manPage, args) if err == nil { return } } ui.Println(c.HelpText()) os.Exit(0) } }
func transformApplyArgs(args *Args) { gistRegexp := regexp.MustCompile("^https?://gist\\.github\\.com/([\\w.-]+/)?([a-f0-9]+)") pullRegexp := regexp.MustCompile("^(pull|commit)/([0-9a-f]+)") for _, arg := range args.Params { var ( patch io.ReadCloser apiError error ) projectURL, err := github.ParseURL(arg) if err == nil { gh := github.NewClient(projectURL.Project.Host) match := pullRegexp.FindStringSubmatch(projectURL.ProjectPath()) if match != nil { if match[1] == "pull" { patch, apiError = gh.PullRequestPatch(projectURL.Project, match[2]) } else { patch, apiError = gh.CommitPatch(projectURL.Project, match[2]) } } } else { match := gistRegexp.FindStringSubmatch(arg) if match != nil { // TODO: support Enterprise gist gh := github.NewClient(github.GitHubHost) patch, apiError = gh.GistPatch(match[2]) } } utils.Check(apiError) if patch == nil { continue } idx := args.IndexOfParam(arg) patchFile, err := ioutil.TempFile("", "hub") utils.Check(err) _, err = io.Copy(patchFile, patch) utils.Check(err) patchFile.Close() patch.Close() args.Params[idx] = patchFile.Name() } }
func discover(cmd *Command, args *Args) { if args.ParamsSize() < 1 { utils.Check(fmt.Errorf("Error: you must specify a username.")) } client := github.NewClient(github.GitHubHost) repos, err := client.Repositories(args.GetParam(0)) if err != nil { utils.Check(err) } for _, repo := range repos { fmt.Println(repo.Name) } os.Exit(0) }
/* $ 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, err := github.LocalRepo() utils.Check(err) project, err := localRepo.MainProject() if err != nil { utils.Check(fmt.Errorf("Error: repository under 'origin' remote is not a GitHub project")) } config := github.CurrentConfig() host, err := config.PromptForHost(project.Host) if err != nil { utils.Check(github.FormatError("forking repository", err)) } forkProject := github.NewProject(host.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 || !project.SameAs(parentURL.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 { originRemote, _ := localRepo.OriginRemote() originURL := originRemote.URL.String() url := forkProject.GitURL("", "", true) args.Replace("git", "remote", "add", "-f", forkProject.Owner, originURL) args.After("git", "remote", "set-url", forkProject.Owner, url) args.After("echo", fmt.Sprintf("new remote: %s", forkProject.Owner)) } }
func parseCherryPickProjectAndSha(ref string) (project *github.Project, sha string, isPrivate bool) { shaRe := "[a-f0-9]{7,40}" var mainProject *github.Project localRepo, mainProjectErr := github.LocalRepo() if mainProjectErr == nil { mainProject, mainProjectErr = localRepo.MainProject() } url, err := github.ParseURL(ref) if err == nil { projectPath := url.ProjectPath() commitRegex := regexp.MustCompile(fmt.Sprintf("^commit/(%s)", shaRe)) if matches := commitRegex.FindStringSubmatch(projectPath); len(matches) > 0 { sha = matches[1] project = url.Project return } pullRegex := regexp.MustCompile(fmt.Sprintf(`^pull/(\d+)/commits/(%s)`, shaRe)) if matches := pullRegex.FindStringSubmatch(projectPath); len(matches) > 0 { pullId := matches[1] sha = matches[2] utils.Check(mainProjectErr) api := github.NewClient(mainProject.Host) pullRequest, err := api.PullRequest(url.Project, pullId) utils.Check(err) headRepo := pullRequest.Head.Repo project = github.NewProject(headRepo.Owner.Login, headRepo.Name, mainProject.Host) isPrivate = headRepo.Private return } } ownerWithShaRegexp := regexp.MustCompile(fmt.Sprintf("^(%s)@(%s)$", OwnerRe, shaRe)) if matches := ownerWithShaRegexp.FindStringSubmatch(ref); len(matches) > 0 { utils.Check(mainProjectErr) project = mainProject project.Owner = matches[1] sha = matches[2] } return }
func (c *Config) scanLine() string { var line string scanner := bufio.NewScanner(os.Stdin) if scanner.Scan() { line = scanner.Text() } utils.Check(scanner.Err()) return line }
func report(reportedError error, stack string) { title, body, err := reportTitleAndBody(reportedError, stack) utils.Check(err) project := NewProject(hubProjectOwner, hubProjectName, GitHubHost) gh := NewClient(project.Host) params := map[string]interface{}{ "title": title, "body": body, "labels": []string{"Crash Report"}, } issue, err := gh.CreateIssue(project, params) utils.Check(err) ui.Println(issue.HtmlUrl) }
// Remove the scheme from host when the host 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 } }
/* $ 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, err := github.LocalRepo() utils.Check(err) var ( branch *github.Branch project *github.Project r string ) branch, project, err = localRepo.RemoteBranchAndProject("", false) utils.Check(err) if args.IsParamsEmpty() { if branch != nil && !branch.IsMaster() { r = branch.ShortName() } else { err = fmt.Errorf("Usage: hub compare [USER] [<START>...]<END>") utils.Check(err) } } 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), "", "") } } subpage := utils.ConcatPaths("compare", rangeQueryEscape(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 listIssues(cmd *Command, args *Args) { localRepo, err := github.LocalRepo() utils.Check(err) project, err := localRepo.MainProject() utils.Check(err) gh := github.NewClient(project.Host) if args.Noop { ui.Printf("Would request list of issues for %s\n", project) } else { filters := map[string]interface{}{} if cmd.FlagPassed("state") { filters["state"] = flagIssueState } if cmd.FlagPassed("assignee") { filters["assignee"] = flagIssueAssignee } issues, err := gh.FetchIssues(project, filters) utils.Check(err) maxNumWidth := 0 for _, issue := range issues { if numWidth := len(strconv.Itoa(issue.Number)); numWidth > maxNumWidth { maxNumWidth = numWidth } } colorize := ui.IsTerminal(os.Stdout) for _, issue := range issues { if issue.PullRequest != nil { continue } ui.Printf(formatIssue(issue, flagIssueFormat, colorize)) } } os.Exit(0) }
func runVersion(cmd *Command, args *Args) { gitVersion, err := git.Version() utils.Check(err) ghVersion := fmt.Sprintf("hub version %s", Version) ui.Println(gitVersion) ui.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 { ui.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) ui.Println(issue.HTMLURL) } }) }
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 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() { dirName, err := git.WorkdirName() utils.Check(err) newRepoName = github.SanitizeProjectName(dirName) } else { reg := regexp.MustCompile("^[^-]") if !reg.MatchString(args.FirstParam()) { err = fmt.Errorf("invalid argument: %s", args.FirstParam()) utils.Check(err) } newRepoName = args.FirstParam() } config := github.CurrentConfig() host, err := config.DefaultHost() if err != nil { utils.Check(github.FormatError("creating repository", err)) } owner := host.User if strings.Contains(newRepoName, "/") { split := strings.SplitN(newRepoName, "/", 2) owner = split[0] newRepoName = split[1] } project := github.NewProject(owner, newRepoName, host.Host) gh := github.NewClient(project.Host) if gh.IsRepositoryExist(project) { ui.Errorln("Existing repository detected. Updating git remote") } else { if !args.Noop { repo, err := gh.CreateRepository(project, flagCreateDescription, flagCreateHomepage, flagCreatePrivate) utils.Check(err) project = github.NewProject(repo.FullName, "", project.Host) } } localRepo, err := github.LocalRepo() utils.Check(err) remote, _ := localRepo.OriginRemote() if remote == nil || remote.Name != "origin" { url := project.GitURL("", "", true) args.Before("git", "remote", "add", "-f", "origin", url) } webUrl := project.WebURL("", "", "") args.NoForward() printBrowseOrCopy(args, webUrl, flagCreateBrowse, flagCreateCopy) }
func showRelease(cmd *Command, args *Args) { tagName := cmd.Arg(0) if tagName == "" { utils.Check(fmt.Errorf("Missing argument TAG")) } localRepo, err := github.LocalRepo() utils.Check(err) project, err := localRepo.MainProject() utils.Check(err) gh := github.NewClient(project.Host) if args.Noop { ui.Printf("Would display information for `%s' release\n", tagName) } else { release, err := gh.FetchRelease(project, tagName) utils.Check(err) body := strings.TrimSpace(release.Body) ui.Println(release.Name) if body != "" { ui.Printf("\n%s\n", body) } if flagReleaseShowDownloads { ui.Printf("\n## Downloads\n\n") for _, asset := range release.Assets { ui.Println(asset.DownloadUrl) } if release.ZipballUrl != "" { ui.Println(release.ZipballUrl) ui.Println(release.TarballUrl) } } } args.NoForward() }
func gitRemoteForProject(project *github.Project) (foundRemote *github.Remote) { remotes, err := github.Remotes() utils.Check(err) for _, remote := range remotes { remoteProject, pErr := remote.Project() if pErr == nil && remoteProject.SameAs(project) { foundRemote = &remote return } } return nil }
func listReleases(cmd *Command, args *Args) { localRepo, err := github.LocalRepo() utils.Check(err) project, err := localRepo.MainProject() utils.Check(err) gh := github.NewClient(project.Host) if args.Noop { ui.Printf("Would request list of releases for %s\n", project) } else { releases, err := gh.FetchReleases(project) utils.Check(err) for _, release := range releases { if !release.Draft || flagReleaseIncludeDrafts { ui.Println(release.TagName) } } } args.NoForward() }