2013-06-21 22:40:42 +04:00
|
|
|
package commands
|
|
|
|
|
2013-06-22 00:17:52 +04:00
|
|
|
import (
|
2013-06-22 04:01:00 +04:00
|
|
|
"fmt"
|
2014-04-01 00:01:05 +04:00
|
|
|
"regexp"
|
|
|
|
|
2020-04-17 02:02:37 +03:00
|
|
|
"github.com/github/hub/v2/git"
|
|
|
|
"github.com/github/hub/v2/github"
|
|
|
|
"github.com/github/hub/v2/utils"
|
2013-06-22 00:17:52 +04:00
|
|
|
)
|
|
|
|
|
2013-06-21 22:40:42 +04:00
|
|
|
var cmdCheckout = &Command{
|
|
|
|
Run: checkout,
|
|
|
|
GitExtension: true,
|
2016-01-24 11:56:18 +03:00
|
|
|
Usage: "checkout <PULLREQ-URL> [<BRANCH>]",
|
|
|
|
Long: `Check out the head of a pull request as a local branch.
|
|
|
|
|
|
|
|
## Examples:
|
|
|
|
$ hub checkout https://github.com/jingweno/gh/pull/73
|
2016-09-12 11:39:50 +03:00
|
|
|
> git fetch origin pull/73/head:jingweno-feature
|
|
|
|
> git checkout jingweno-feature
|
2016-01-24 18:50:01 +03:00
|
|
|
|
|
|
|
## See also:
|
|
|
|
|
|
|
|
hub-merge(1), hub-am(1), hub(1), git-checkout(1)
|
2013-07-01 22:29:08 +04:00
|
|
|
`,
|
2013-06-21 22:40:42 +04:00
|
|
|
}
|
|
|
|
|
2013-12-30 02:18:14 +04:00
|
|
|
func init() {
|
|
|
|
CmdRunner.Use(cmdCheckout)
|
|
|
|
}
|
|
|
|
|
2013-06-26 19:48:34 +04:00
|
|
|
func checkout(command *Command, args *Args) {
|
2013-12-11 10:05:26 +04:00
|
|
|
words := args.Words()
|
2014-04-01 00:01:05 +04:00
|
|
|
|
2013-12-11 10:05:26 +04:00
|
|
|
if len(words) == 0 {
|
2017-04-08 23:14:22 +03:00
|
|
|
return
|
2013-12-11 10:05:26 +04:00
|
|
|
}
|
2013-06-22 04:01:00 +04:00
|
|
|
|
2013-12-11 10:05:26 +04:00
|
|
|
checkoutURL := words[0]
|
|
|
|
var newBranchName string
|
|
|
|
if len(words) > 1 {
|
|
|
|
newBranchName = words[1]
|
2013-07-29 21:50:28 +04:00
|
|
|
}
|
2014-04-01 00:01:05 +04:00
|
|
|
|
|
|
|
url, err := github.ParseURL(checkoutURL)
|
|
|
|
if err != nil {
|
|
|
|
// not a valid GitHub URL
|
2017-04-08 23:14:22 +03:00
|
|
|
return
|
2014-04-01 00:01:05 +04:00
|
|
|
}
|
2013-06-22 05:02:29 +04:00
|
|
|
|
2013-12-11 10:05:26 +04:00
|
|
|
pullURLRegex := regexp.MustCompile("^pull/(\\d+)")
|
|
|
|
projectPath := url.ProjectPath()
|
|
|
|
if !pullURLRegex.MatchString(projectPath) {
|
2014-04-01 00:01:05 +04:00
|
|
|
// not a valid PR URL
|
2017-04-08 23:14:22 +03:00
|
|
|
return
|
2013-12-11 10:05:26 +04:00
|
|
|
}
|
2013-07-02 22:28:50 +04:00
|
|
|
|
2015-02-18 09:27:15 +03:00
|
|
|
err = sanitizeCheckoutFlags(args)
|
2017-04-08 23:14:22 +03:00
|
|
|
utils.Check(err)
|
2015-02-18 09:27:15 +03:00
|
|
|
|
2013-12-11 10:05:26 +04:00
|
|
|
id := pullURLRegex.FindStringSubmatch(projectPath)[1]
|
2013-12-17 19:45:48 +04:00
|
|
|
gh := github.NewClient(url.Project.Host)
|
|
|
|
pullRequest, err := gh.PullRequest(url.Project, id)
|
2017-04-08 23:14:22 +03:00
|
|
|
utils.Check(err)
|
|
|
|
|
|
|
|
newArgs, err := transformCheckoutArgs(args, pullRequest, newBranchName)
|
|
|
|
utils.Check(err)
|
2013-06-22 04:01:00 +04:00
|
|
|
|
2013-12-11 10:05:26 +04:00
|
|
|
if idx := args.IndexOfParam(newBranchName); idx >= 0 {
|
|
|
|
args.RemoveParam(idx)
|
|
|
|
}
|
2017-04-08 23:14:22 +03:00
|
|
|
replaceCheckoutParam(args, checkoutURL, newArgs...)
|
|
|
|
}
|
2013-06-22 04:01:00 +04:00
|
|
|
|
2017-04-08 23:14:22 +03:00
|
|
|
func transformCheckoutArgs(args *Args, pullRequest *github.PullRequest, newBranchName string) (newArgs []string, err error) {
|
2016-08-20 23:49:56 +03:00
|
|
|
repo, err := github.LocalRepo()
|
|
|
|
if err != nil {
|
2017-04-08 23:14:22 +03:00
|
|
|
return
|
2013-07-29 21:50:28 +04:00
|
|
|
}
|
|
|
|
|
2016-09-12 11:40:51 +03:00
|
|
|
baseRemote, err := repo.RemoteForRepo(pullRequest.Base.Repo)
|
2016-08-20 23:49:56 +03:00
|
|
|
if err != nil {
|
2017-04-08 23:14:22 +03:00
|
|
|
return
|
2013-06-22 04:01:00 +04:00
|
|
|
}
|
2013-07-29 21:50:28 +04:00
|
|
|
|
2016-09-12 11:40:51 +03:00
|
|
|
var headRemote *github.Remote
|
|
|
|
if pullRequest.IsSameRepo() {
|
|
|
|
headRemote = baseRemote
|
2016-10-03 21:37:45 +03:00
|
|
|
} else if pullRequest.Head.Repo != nil {
|
2016-09-12 11:40:51 +03:00
|
|
|
headRemote, _ = repo.RemoteForRepo(pullRequest.Head.Repo)
|
|
|
|
}
|
|
|
|
|
|
|
|
if headRemote != nil {
|
2016-08-21 01:00:13 +03:00
|
|
|
if newBranchName == "" {
|
|
|
|
newBranchName = pullRequest.Head.Ref
|
|
|
|
}
|
2016-09-12 11:40:51 +03:00
|
|
|
remoteBranch := fmt.Sprintf("%s/%s", headRemote.Name, pullRequest.Head.Ref)
|
|
|
|
refSpec := fmt.Sprintf("+refs/heads/%s:refs/remotes/%s", pullRequest.Head.Ref, remoteBranch)
|
|
|
|
if git.HasFile("refs", "heads", newBranchName) {
|
|
|
|
newArgs = append(newArgs, newBranchName)
|
|
|
|
args.After("git", "merge", "--ff-only", fmt.Sprintf("refs/remotes/%s", remoteBranch))
|
|
|
|
} else {
|
2019-02-22 19:18:25 +03:00
|
|
|
newArgs = append(newArgs, "-b", newBranchName, "--no-track", remoteBranch)
|
|
|
|
args.After("git", "config", fmt.Sprintf("branch.%s.remote", newBranchName), headRemote.Name)
|
|
|
|
args.After("git", "config", fmt.Sprintf("branch.%s.merge", newBranchName), "refs/heads/"+pullRequest.Head.Ref)
|
2016-09-12 11:40:51 +03:00
|
|
|
}
|
|
|
|
args.Before("git", "fetch", headRemote.Name, refSpec)
|
2016-08-21 01:00:13 +03:00
|
|
|
} else {
|
|
|
|
if newBranchName == "" {
|
2018-06-06 11:24:06 +03:00
|
|
|
newBranchName = pullRequest.Head.Ref
|
|
|
|
if pullRequest.Head.Repo != nil && newBranchName == pullRequest.Head.Repo.DefaultBranch {
|
|
|
|
newBranchName = fmt.Sprintf("%s-%s", pullRequest.Head.Repo.Owner.Login, newBranchName)
|
2016-10-03 19:39:46 +03:00
|
|
|
}
|
2016-08-21 01:00:13 +03:00
|
|
|
}
|
|
|
|
newArgs = append(newArgs, newBranchName)
|
2017-02-03 10:53:44 +03:00
|
|
|
|
2019-10-22 18:48:58 +03:00
|
|
|
b, errB := repo.CurrentBranch()
|
|
|
|
isCurrentBranch := errB == nil && b.ShortName() == newBranchName
|
|
|
|
|
2017-04-08 23:14:22 +03:00
|
|
|
ref := fmt.Sprintf("refs/pull/%d/head", pullRequest.Number)
|
2019-10-22 18:48:58 +03:00
|
|
|
if isCurrentBranch {
|
|
|
|
args.Before("git", "fetch", baseRemote.Name, ref)
|
|
|
|
args.After("git", "merge", "--ff-only", "FETCH_HEAD")
|
|
|
|
} else {
|
|
|
|
args.Before("git", "fetch", baseRemote.Name, fmt.Sprintf("%s:%s", ref, newBranchName))
|
|
|
|
}
|
2017-02-03 10:53:44 +03:00
|
|
|
|
|
|
|
remote := baseRemote.Name
|
|
|
|
mergeRef := ref
|
|
|
|
if pullRequest.MaintainerCanModify && pullRequest.Head.Repo != nil {
|
2017-04-08 23:14:22 +03:00
|
|
|
var project *github.Project
|
|
|
|
project, err = github.NewProjectFromRepo(pullRequest.Head.Repo)
|
2017-02-04 02:54:44 +03:00
|
|
|
if err != nil {
|
2017-04-08 23:14:22 +03:00
|
|
|
return
|
2017-02-04 02:54:44 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
remote = project.GitURL("", "", true)
|
2017-02-03 10:53:44 +03:00
|
|
|
mergeRef = fmt.Sprintf("refs/heads/%s", pullRequest.Head.Ref)
|
|
|
|
}
|
2019-10-22 18:56:07 +03:00
|
|
|
|
|
|
|
if mc, err := git.Config(fmt.Sprintf("branch.%s.merge", newBranchName)); err != nil || mc == "" {
|
|
|
|
args.After("git", "config", fmt.Sprintf("branch.%s.remote", newBranchName), remote)
|
|
|
|
args.After("git", "config", fmt.Sprintf("branch.%s.merge", newBranchName), mergeRef)
|
|
|
|
}
|
2016-08-18 22:12:34 +03:00
|
|
|
}
|
2017-04-08 23:14:22 +03:00
|
|
|
return
|
2013-07-02 22:28:50 +04:00
|
|
|
}
|
2015-02-17 21:04:45 +03:00
|
|
|
|
2015-02-18 09:27:15 +03:00
|
|
|
func sanitizeCheckoutFlags(args *Args) error {
|
|
|
|
if i := args.IndexOfParam("-b"); i != -1 {
|
|
|
|
return fmt.Errorf("Unsupported flag -b when checking out pull request")
|
|
|
|
}
|
|
|
|
|
|
|
|
if i := args.IndexOfParam("--orphan"); i != -1 {
|
|
|
|
return fmt.Errorf("Unsupported flag --orphan when checking out pull request")
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-08-18 22:12:34 +03:00
|
|
|
func replaceCheckoutParam(args *Args, checkoutURL string, replacement ...string) {
|
2015-02-17 21:04:45 +03:00
|
|
|
idx := args.IndexOfParam(checkoutURL)
|
|
|
|
args.RemoveParam(idx)
|
2016-08-18 22:12:34 +03:00
|
|
|
args.InsertParam(idx, replacement...)
|
2015-02-17 21:04:45 +03:00
|
|
|
}
|