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 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 }
/* $ 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), "", "") } } } if project == nil { project, err = localRepo.CurrentProject() utils.Check(err) } 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 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 parsePullRequestProject(context *github.Project, s string) (p *github.Project, ref string) { p = context ref = s if strings.Contains(s, ":") { split := strings.SplitN(s, ":", 2) ref = split[1] var name string if !strings.Contains(split[0], "/") { name = context.Name } p = github.NewProject(split[0], name, context.Host) } return }
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 }
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 }
/* $ 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 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 } } }
/* $ gh browse > open https://github.com/CURRENT_REPO $ gh browse -- issues > open https://github.com/CURRENT_REPO/issues $ gh browse jingweno/gh > open https://github.com/jingweno/gh $ gh browse gh > open https://github.com/YOUR_LOGIN/gh $ gh browse gh wiki > open https://github.com/YOUR_LOGIN/gh/wiki */ func browse(command *Command, args *Args) { var ( dest string subpage string path string project *github.Project branch *github.Branch err error ) flagBrowseURLOnly := parseFlagBrowseURLOnly(args) if !args.IsParamsEmpty() { dest = args.RemoveParam(0) } if dest == "--" { dest = "" } if !args.IsParamsEmpty() { subpage = args.RemoveParam(0) } 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() } branch, project, _ = localRepo.RemoteBranchAndProject("", currentBranch.IsMaster()) if branch == nil { branch = localRepo.MasterBranch() } } if project == nil { err := fmt.Errorf(command.FormattedUsage()) 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 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)) project, err = localRepo.CurrentProject() if args.IsParamsEmpty() { utils.Check(err) } else { projectName := "" if err == nil { projectName = project.Name } project = github.NewProject(args.RemoveParam(args.ParamsSize()-1), projectName, "") if project.Name == "" { utils.Check(fmt.Errorf("error: missing project name (owner: %q)\n", project.Owner)) } } } } if project == nil { project, err = localRepo.CurrentProject() utils.Check(err) } subpage := utils.ConcatPaths("compare", rangeQueryEscape(r)) url := project.WebURL("", "", subpage) args.NoForward() printBrowseOrCopy(args, url, !flagCompareURLOnly, false) }