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 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)) } originRemote, err := localRepo.OriginRemote() if err != nil { utils.Check(fmt.Errorf("Error creating fork: %s", err)) } forkProject := github.NewProject(host.User, project.Name, project.Host) newRemoteName := forkProject.Owner 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 { newRepo, err := client.ForkRepository(project) utils.Check(err) forkProject.Owner = newRepo.Owner.Login forkProject.Name = newRepo.Name } } args.NoForward() if !flagForkNoRemote { originURL := originRemote.URL.String() url := forkProject.GitURL("", "", true) args.Before("git", "remote", "add", "-f", newRemoteName, originURL) args.Before("git", "remote", "set-url", newRemoteName, url) args.AfterFn(func() error { ui.Printf("new remote: %s\n", newRemoteName) return nil }) } }
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 transformCloneArgs(args *Args) { isSSH := parseClonePrivateFlag(args) hasValueRegxp := regexp.MustCompile("^(--(upload-pack|template|depth|origin|branch|reference|name)|-[ubo])$") nameWithOwnerRegexp := regexp.MustCompile(NameWithOwnerRe) for i := 0; i < args.ParamsSize(); i++ { a := args.Params[i] if strings.HasPrefix(a, "-") { if hasValueRegxp.MatchString(a) { i++ } } else { if nameWithOwnerRegexp.MatchString(a) && !isDir(a) { name, owner := parseCloneNameAndOwner(a) var host *github.Host if owner == "" { config := github.CurrentConfig() h, err := config.DefaultHost() if err != nil { utils.Check(github.FormatError("cloning repository", err)) } host = h owner = host.User } var hostStr string if host != nil { hostStr = host.Host } project := github.NewProject(owner, name, hostStr) if !isSSH && args.Command != "submodule" && !github.IsHttpsProtocol() { client := github.NewClient(project.Host) repo, err := client.Repository(project) isSSH = (err == nil) && (repo.Private || repo.Permissions.Push) } url := project.GitURL(name, owner, isSSH) args.ReplaceParam(i, url) } break } } }
func transformInitArgs(args *Args) error { if !parseInitFlag(args) { return nil } var err error dirToInit := "." hasValueRegxp := regexp.MustCompile("^--(template|separate-git-dir|shared)$") // Find the first argument that isn't related to any of the init flags. // We assume this is the optional `directory` argument to git init. for i := 0; i < args.ParamsSize(); i++ { arg := args.Params[i] if hasValueRegxp.MatchString(arg) { i++ } else if !strings.HasPrefix(arg, "-") { dirToInit = arg break } } dirToInit, err = filepath.Abs(dirToInit) if err != nil { return err } config := github.CurrentConfig() host, err := config.DefaultHost() if err != nil { utils.Check(github.FormatError("initializing repository", err)) } // Assume that the name of the working directory is going to be the name of // the project on GitHub. projectName := strings.Replace(filepath.Base(dirToInit), " ", "-", -1) project := github.NewProject(host.User, projectName, "") url := project.GitURL("", "", true) addRemote := []string{ "git", "--git-dir", filepath.Join(dirToInit, ".git"), "remote", "add", "origin", url, } args.After(addRemote...) return nil }
/* # 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, err := github.LocalRepo() utils.Check(err) currentBranch, err := localRepo.CurrentBranch() utils.Check(err) baseProject, err := localRepo.MainProject() utils.Check(err) host, err := github.CurrentConfig().PromptForHost(baseProject.Host) if err != nil { utils.Check(github.FormatError("creating pull request", err)) } trackedBranch, headProject, err := localRepo.RemoteBranchAndProject(host.User, false) 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 == "" && trackedBranch != nil { if !trackedBranch.IsRemote() { // the current branch tracking another branch // pretend there's no upstream at all trackedBranch = nil } else { if baseProject.SameAs(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 head == "" { 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) } } var editor *github.Editor if title == "" && flagPullRequestIssue == "" { baseTracking := base headTracking := head remote := gitRemoteForProject(baseProject) if remote != nil { baseTracking = fmt.Sprintf("%s/%s", remote.Name, base) } if remote == nil || !baseProject.SameAs(headProject) { remote = gitRemoteForProject(headProject) } if remote != nil { headTracking = fmt.Sprintf("%s/%s", remote.Name, head) } message, err := pullRequestChangesMessage(baseTracking, headTracking, fullBase, fullHead) utils.Check(err) editor, err = github.NewEditor("PULLREQ", "pull request", message) utils.Check(err) title, body, err = editor.EditTitleAndBody() 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 { var ( pr *octokit.PullRequest err error ) client := github.NewClientWithHost(host) if title != "" { pr, err = client.CreatePullRequest(baseProject, base, fullHead, title, body) } else if flagPullRequestIssue != "" { pr, err = client.CreatePullRequestForIssue(baseProject, base, fullHead, flagPullRequestIssue) } if err == nil && editor != nil { defer editor.DeleteFile() } utils.Check(err) pullRequestURL = pr.HTMLURL if flagPullRequestAssignee != "" || flagPullRequestMilestone > 0 || flagPullRequestLabels != "" { params := octokit.IssueParams{ Assignee: flagPullRequestAssignee, Milestone: flagPullRequestMilestone, Labels: strings.Split(flagPullRequestLabels, ","), } err = client.UpdateIssue(baseProject, pr.Number, params) utils.Check(err) } } if flagPullRequestBrowse { launcher, err := utils.BrowserLauncher() utils.Check(err) args.Replace(launcher[0], "", launcher[1:]...) args.AppendParams(pullRequestURL) } else { args.Replace("echo", "", pullRequestURL) } if flagPullRequestIssue != "" { args.After("echo", "Warning: Issue to pull request conversion is deprecated and might not work in the future.") } }
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() } 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) var action string if gh.IsRepositoryExist(project) { ui.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) } } localRepo, err := github.LocalRepo() utils.Check(err) remote, _ := localRepo.OriginRemote() if remote == nil || remote.Name != "origin" { 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 browse(command *Command, args *Args) { var ( dest string subpage string path string project *github.Project branch *github.Branch err error ) if !args.IsParamsEmpty() { dest = args.RemoveParam(0) } if !args.IsParamsEmpty() { subpage = args.RemoveParam(0) } if args.Terminator { subpage = dest dest = "" } localRepo, _ := github.LocalRepo() if dest != "" { project = github.NewProject("", dest, "") branch = localRepo.MasterBranch() } else if subpage != "" && subpage != "commits" && subpage != "tree" && subpage != "blob" && subpage != "settings" { project, err = localRepo.MainProject() branch = localRepo.MasterBranch() utils.Check(err) } else { currentBranch, err := localRepo.CurrentBranch() if err != nil { currentBranch = localRepo.MasterBranch() } var owner string mainProject, err := localRepo.MainProject() if err == nil { host, err := github.CurrentConfig().PromptForHost(mainProject.Host) if err != nil { utils.Check(github.FormatError("in browse", err)) } else { owner = host.User } } branch, project, _ = localRepo.RemoteBranchAndProject(owner, currentBranch.IsMaster()) if branch == nil { branch = localRepo.MasterBranch() } } if project == nil { err := fmt.Errorf(command.Synopsis()) utils.Check(err) } if subpage == "commits" { path = fmt.Sprintf("commits/%s", branchInURL(branch)) } else if subpage == "tree" || subpage == "" { if !branch.IsMaster() { path = fmt.Sprintf("tree/%s", branchInURL(branch)) } } else { path = subpage } pageUrl := project.WebURL("", "", path) launcher, err := utils.BrowserLauncher() utils.Check(err) if flagBrowseURLOnly { args.Replace("echo", pageUrl) } else { args.Replace(launcher[0], "", launcher[1:]...) args.AppendParams(pageUrl) } }
func transformCloneArgs(args *Args) { isSSH := parseClonePrivateFlag(args) hasValueRegxp := regexp.MustCompile("^(--(upload-pack|template|depth|origin|branch|reference|name)|-[ubo])$") nameWithOwnerRegexp := regexp.MustCompile(NameWithOwnerRe) for i := 0; i < args.ParamsSize(); i++ { a := args.Params[i] if strings.HasPrefix(a, "-") { if hasValueRegxp.MatchString(a) { i++ } } else { if nameWithOwnerRegexp.MatchString(a) && !isCloneable(a) { name, owner := parseCloneNameAndOwner(a) var host *github.Host if owner == "" { config := github.CurrentConfig() h, err := config.DefaultHost() if err != nil { utils.Check(github.FormatError("cloning repository", err)) } host = h owner = host.User } var hostStr string if host != nil { hostStr = host.Host } expectWiki := strings.HasSuffix(name, ".wiki") if expectWiki { name = strings.TrimSuffix(name, ".wiki") } project := github.NewProject(owner, name, hostStr) gh := github.NewClient(project.Host) repo, err := gh.Repository(project) if err != nil { if strings.Contains(err.Error(), "HTTP 404") { err = fmt.Errorf("Error: repository %s/%s doesn't exist", project.Owner, project.Name) } utils.Check(err) } owner = repo.Owner.Login name = repo.Name if expectWiki { if !repo.HasWiki { utils.Check(fmt.Errorf("Error: %s/%s doesn't have a wiki", owner, name)) } else { name = name + ".wiki" } } if !isSSH && args.Command != "submodule" && !github.IsHttpsProtocol() { isSSH = repo.Private || repo.Permissions.Push } url := project.GitURL(name, owner, isSSH) args.ReplaceParam(i, url) } break } } }
func pullRequest(cmd *Command, args *Args) { localRepo, err := github.LocalRepo() utils.Check(err) currentBranch, err := localRepo.CurrentBranch() utils.Check(err) baseProject, err := localRepo.MainProject() utils.Check(err) host, err := github.CurrentConfig().PromptForHost(baseProject.Host) if err != nil { utils.Check(github.FormatError("creating pull request", err)) } client := github.NewClientWithHost(host) trackedBranch, headProject, err := localRepo.RemoteBranchAndProject(host.User, false) 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 == "" && trackedBranch != nil { if !trackedBranch.IsRemote() { // the current branch tracking another branch // pretend there's no upstream at all trackedBranch = nil } else { if baseProject.SameAs(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 head == "" { if trackedBranch == nil { head = currentBranch.ShortName() } else { head = trackedBranch.ShortName() } } if headRepo, err := client.Repository(headProject); err == nil { headProject.Owner = headRepo.Owner.Login headProject.Name = headRepo.Name } 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) } } var editor *github.Editor var title, body string baseTracking := base headTracking := head remote := gitRemoteForProject(baseProject) if remote != nil { baseTracking = fmt.Sprintf("%s/%s", remote.Name, base) } if remote == nil || !baseProject.SameAs(headProject) { remote = gitRemoteForProject(headProject) } if remote != nil { headTracking = fmt.Sprintf("%s/%s", remote.Name, head) } if flagPullRequestPush && remote == nil { utils.Check(fmt.Errorf("Can't find remote for %s", head)) } if cmd.FlagPassed("message") { title, body = readMsg(flagPullRequestMessage) } else if cmd.FlagPassed("file") { title, body, editor, err = readMsgFromFile(flagPullRequestFile, flagPullRequestEdit, "PULLREQ", "pull request") utils.Check(err) } else if flagPullRequestIssue == "" { headForMessage := headTracking if flagPullRequestPush { headForMessage = head } message, err := createPullRequestMessage(baseTracking, headForMessage, fullBase, fullHead) utils.Check(err) editor, err = github.NewEditor("PULLREQ", "pull request", message) utils.Check(err) title, body, err = editor.EditTitleAndBody() utils.Check(err) } if title == "" && flagPullRequestIssue == "" { utils.Check(fmt.Errorf("Aborting due to empty pull request title")) } if flagPullRequestPush { if args.Noop { args.Before(fmt.Sprintf("Would push to %s/%s", remote.Name, head), "") } else { err = git.Spawn("push", "--set-upstream", remote.Name, fmt.Sprintf("HEAD:%s", head)) utils.Check(err) } } 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 { params := map[string]interface{}{ "base": base, "head": fullHead, } if title != "" { params["title"] = title if body != "" { params["body"] = body } } else { issueNum, _ := strconv.Atoi(flagPullRequestIssue) params["issue"] = issueNum } startedAt := time.Now() numRetries := 0 retryDelay := 2 retryAllowance := 0 if flagPullRequestPush { if allowanceFromEnv := os.Getenv("HUB_RETRY_TIMEOUT"); allowanceFromEnv != "" { retryAllowance, err = strconv.Atoi(allowanceFromEnv) utils.Check(err) } else { retryAllowance = 9 } } var pr *github.PullRequest for { pr, err = client.CreatePullRequest(baseProject, params) if err != nil && strings.Contains(err.Error(), `Invalid value for "head"`) { if retryAllowance > 0 { retryAllowance -= retryDelay time.Sleep(time.Duration(retryDelay) * time.Second) retryDelay += 1 numRetries += 1 } else { if numRetries > 0 { duration := time.Now().Sub(startedAt) err = fmt.Errorf("%s\nGiven up after retrying for %.1f seconds.", err, duration.Seconds()) } break } } else { break } } if err == nil && editor != nil { defer editor.DeleteFile() } utils.Check(err) pullRequestURL = pr.HtmlUrl params = map[string]interface{}{} if len(flagPullRequestLabels) > 0 { params["labels"] = flagPullRequestLabels } if len(flagPullRequestAssignees) > 0 { params["assignees"] = flagPullRequestAssignees } if flagPullRequestMilestone > 0 { params["milestone"] = flagPullRequestMilestone } if len(params) > 0 { err = client.UpdateIssue(baseProject, pr.Number, params) utils.Check(err) } } if flagPullRequestIssue != "" { ui.Errorln("Warning: Issue to pull request conversion is deprecated and might not work in the future.") } args.NoForward() printBrowseOrCopy(args, pullRequestURL, flagPullRequestBrowse, flagPullRequestCopy) }