github.com/olli-ai/jx/v2@v2.0.400-0.20210921045218-14731b4dd448/pkg/cmd/opts/pullrequest.go (about) 1 package opts 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/jenkins-x/jx-logging/pkg/log" 8 "github.com/olli-ai/jx/v2/pkg/gits" 9 "github.com/olli-ai/jx/v2/pkg/util" 10 "github.com/pkg/errors" 11 "k8s.io/apimachinery/pkg/util/uuid" 12 ) 13 14 // PullRequestDetails details to pass in to create a PullRequest if the repository is modified 15 type PullRequestDetails struct { 16 Dir string 17 RepositoryGitURL string 18 RepositoryBranch string 19 RepositoryMessage string 20 BranchNameText string 21 Title string 22 Message string 23 } 24 25 // CreatePullRequest creates a Pull Request on the given repository 26 func (options *CommonOptions) CreatePullRequest(o *PullRequestDetails, modifyFn func() error) error { 27 if o.RepositoryBranch == "" { 28 o.RepositoryBranch = "master" 29 } 30 dir := o.Dir 31 originalGitURL := o.RepositoryGitURL 32 message := o.RepositoryMessage 33 gitter := options.Git() 34 gitInfo, err := gits.ParseGitURL(originalGitURL) 35 if err != nil { 36 return err 37 } 38 provider, err := options.GitProviderForURL(originalGitURL, message) 39 if err != nil { 40 return err 41 } 42 43 if o.Message == "" { 44 log.Logger().Warn("missing option: 'message' when creating a PR") 45 } 46 username := provider.CurrentUsername() 47 if username == "" { 48 return fmt.Errorf("no git user name found") 49 } 50 51 originalOrg := gitInfo.Organisation 52 originalRepo := gitInfo.Name 53 54 repo, err := provider.GetRepository(username, originalRepo) 55 if err != nil { 56 if originalOrg == username { 57 return err 58 } 59 60 // lets try create a fork - using a blank organisation to force a user specific fork 61 repo, err = provider.ForkRepository(originalOrg, originalRepo, "") 62 if err != nil { 63 return errors.Wrapf(err, "failed to fork GitHub repo %s/%s to user %s", originalOrg, originalRepo, username) 64 } 65 log.Logger().Infof("Forked %s to %s\n", message, util.ColorInfo(repo.HTMLURL)) 66 67 repo, err = provider.GetRepository(username, originalRepo) 68 if err != nil { 69 return errors.Wrapf(err, "failed to load GitHub repo %s/%s", username, originalRepo) 70 } 71 } 72 fork := repo.Fork 73 74 err = gitter.Clone(repo.CloneURL, dir) 75 if err != nil { 76 return errors.Wrapf(err, "cloning the %s %q", message, repo.CloneURL) 77 } 78 log.Logger().Infof("cloned fork of %s %s to %s", message, util.ColorInfo(repo.HTMLURL), util.ColorInfo(dir)) 79 80 err = gitter.SetRemoteURL(dir, "upstream", originalGitURL) 81 if err != nil { 82 return errors.Wrapf(err, "setting remote upstream %q in forked %s", originalGitURL, message) 83 } 84 err = gitter.PullUpstream(dir) 85 if err != nil { 86 return errors.Wrapf(err, "pulling upstream of forked %s", message) 87 } 88 89 if fork { 90 base := "master" 91 err = gitter.ResetToUpstream(dir, base) 92 if err != nil { 93 return errors.Wrapf(err, "resetting forked branch %s to upstream version", base) 94 } 95 } 96 97 branchName := gitter.ConvertToValidBranchName(o.BranchNameText) 98 branchNames, err := gitter.RemoteBranchNames(dir, "remotes/origin/") 99 if err != nil { 100 return errors.Wrapf(err, "failed to load remote branch names") 101 } 102 if util.StringArrayIndex(branchNames, branchName) >= 0 { 103 // lets append a UUID as the branch name already exists 104 branchName += "-" + string(uuid.NewUUID()) 105 } 106 107 err = gitter.CreateBranch(dir, branchName) 108 if err != nil { 109 return err 110 } 111 err = gitter.Checkout(dir, branchName) 112 if err != nil { 113 return err 114 } 115 116 err = modifyFn() 117 if err != nil { 118 return err 119 } 120 121 err = gitter.Add(dir, "*", "*/*") 122 if err != nil { 123 return err 124 } 125 changes, err := gitter.HasChanges(dir) 126 if err != nil { 127 return err 128 } 129 if !changes { 130 log.Logger().Infof("No source changes so not generating a Pull Request") 131 return nil 132 } 133 134 err = gitter.CommitDir(dir, o.Title) 135 if err != nil { 136 return err 137 } 138 139 // lets find a previous PR so we can force push to its branch 140 prs, err := provider.ListOpenPullRequests(gitInfo.Organisation, gitInfo.Name) 141 if err != nil { 142 return errors.Wrapf(err, "failed to list open pull requests on %s", gitInfo.HTMLURL) 143 } 144 for _, pr := range prs { 145 author := pr.Author 146 if pr.Title == o.Title && author != nil && author.Login == username { 147 log.Logger().Infof("found existing PullRequest: %s", util.ColorInfo(pr.URL)) 148 149 head := pr.HeadRef 150 if head == nil { 151 log.Logger().Warnf("No head value!") 152 } else { 153 headText := *head 154 remoteBranch := headText 155 paths := strings.SplitN(headText, ":", 2) 156 if len(paths) > 1 { 157 remoteBranch = paths[1] 158 } 159 log.Logger().Infof("force pushing to remote branch %s", util.ColorInfo(remoteBranch)) 160 err := gitter.ForcePushBranch(dir, branchName, remoteBranch) 161 if err != nil { 162 return errors.Wrapf(err, "failed to force push to remote branch %s", remoteBranch) 163 } 164 165 pr.Body = o.Message 166 167 log.Logger().Infof("force pushed new pull request change to: %s", util.ColorInfo(pr.URL)) 168 169 err = provider.AddPRComment(pr, o.Message) 170 if err != nil { 171 return errors.Wrapf(err, "failed to add message to PR %s", pr.URL) 172 } 173 return nil 174 } 175 } 176 } 177 178 err = gitter.Push(dir, "origin", false, "HEAD") 179 if err != nil { 180 return errors.Wrapf(err, "pushing to %s in dir %q", message, dir) 181 } 182 183 base := o.RepositoryBranch 184 185 head := branchName 186 if fork { 187 head = username + ":" + branchName 188 } 189 gha := &gits.GitPullRequestArguments{ 190 GitRepository: gitInfo, 191 Title: o.Title, 192 Body: o.Message, 193 Base: base, 194 Head: head, 195 } 196 197 pr, err := provider.CreatePullRequest(gha) 198 if err != nil { 199 return errors.Wrapf(err, "failed to create pull quest from base %s with header %s", base, head) 200 } 201 log.Logger().Infof("Created Pull Request: %s\n", util.ColorInfo(pr.URL)) 202 return nil 203 }