| package parent |
| |
| import ( |
| "context" |
| "fmt" |
| "strings" |
| |
| "go.skia.org/infra/autoroll/go/config_vars" |
| "go.skia.org/infra/go/git" |
| "go.skia.org/infra/go/github" |
| "go.skia.org/infra/go/skerr" |
| ) |
| |
| const ( |
| githubForkRemoteName = "fork" |
| ) |
| |
| // GitCheckoutGithubConfig provides configuration for Parents which use a local |
| // git checkout and upload changes to GitHub. |
| type GitCheckoutGithubConfig struct { |
| GitCheckoutConfig |
| ForkBranchName string `json:"forkBranchName"` |
| ForkRepoURL string `json:"forkRepoURL"` |
| } |
| |
| // See documentation for util.Validator interface. |
| func (c GitCheckoutGithubConfig) Validate() error { |
| if err := c.GitCheckoutConfig.Validate(); err != nil { |
| return skerr.Wrap(err) |
| } |
| if c.ForkRepoURL == "" { |
| return skerr.Fmt("ForkRepoURL is required") |
| } |
| return nil |
| } |
| |
| // GitCheckoutUploadGithubRollFunc returns |
| func GitCheckoutUploadGithubRollFunc(githubClient *github.GitHub, userName, forkBranchName string) GitCheckoutUploadRollFunc { |
| return func(ctx context.Context, co *git.Checkout, upstreamBranch, hash string, emails []string, dryRun bool, commitMsg string) (int64, error) { |
| // Make sure the forked repo is at the same hash as the target repo |
| // before creating the pull request. |
| if _, err := co.Git(ctx, "push", "-f", githubForkRemoteName, fmt.Sprintf("origin/%s", upstreamBranch)); err != nil { |
| return 0, skerr.Wrap(err) |
| } |
| |
| // Push the changes to the forked repository. |
| if _, err := co.Git(ctx, "push", "-f", githubForkRemoteName, fmt.Sprintf("%s:%s", rollBranch, forkBranchName)); err != nil { |
| return 0, skerr.Wrap(err) |
| } |
| |
| // Build the commit message. |
| commitMsg = strings.ReplaceAll(commitMsg, "git@github.com:", "https://github.com/") |
| commitMsgLines := strings.Split(commitMsg, "\n") |
| // Grab the first line of the commit msg to use as the title of the pull |
| // request. |
| title := commitMsgLines[0] |
| // Use the remaining part of the commit message as the pull request |
| // description. |
| descComment := commitMsgLines[1:] |
| if len(commitMsgLines) > 50 { |
| // Truncate too large description comment because Github API cannot |
| // handle large comments. |
| descComment = append(commitMsgLines[:50], "...") |
| } |
| // Create a pull request. |
| headBranch := fmt.Sprintf("%s:%s", userName, forkBranchName) |
| pr, err := githubClient.CreatePullRequest(title, upstreamBranch, headBranch, strings.Join(descComment, "\n")) |
| if err != nil { |
| return 0, skerr.Wrap(err) |
| } |
| |
| // Add appropriate label to the pull request. |
| if !dryRun { |
| if err := githubClient.AddLabel(pr.GetNumber(), github.WAITING_FOR_GREEN_TREE_LABEL); err != nil { |
| return 0, skerr.Wrap(err) |
| } |
| } |
| |
| return int64(pr.GetNumber()), nil |
| } |
| } |
| |
| // NewGitCheckoutGithub returns an implementation of Parent which uses a local |
| // git checkout and uploads pull requests to Github. |
| func NewGitCheckoutGithub(ctx context.Context, c GitCheckoutGithubConfig, reg *config_vars.Registry, githubClient *github.GitHub, serverURL, workdir, userName, userEmail string, co *git.Checkout, createRoll GitCheckoutCreateRollFunc) (*GitCheckoutParent, error) { |
| if err := c.Validate(); err != nil { |
| return nil, skerr.Wrap(err) |
| } |
| |
| // See documentation for GitCheckoutUploadRollFunc. |
| uploadRoll := GitCheckoutUploadGithubRollFunc(githubClient, userName, c.ForkBranchName) |
| |
| // Create the GitCheckout Parent. |
| p, err := NewGitCheckout(ctx, c.GitCheckoutConfig, reg, serverURL, workdir, userName, userEmail, co, createRoll, uploadRoll) |
| if err != nil { |
| return nil, skerr.Wrap(err) |
| } |
| if err := SetupGithub(ctx, p, c.ForkRepoURL); err != nil { |
| return nil, skerr.Wrap(err) |
| } |
| return p, nil |
| } |
| |
| // SetupGithub performs additional setup for a GitCheckoutParent which uses |
| // Github. This is required when not using NewGitCheckoutGithub to create the |
| // Parent. |
| // TODO(borenet): This is needed for RepoManagers which use NewDEPSLocal, since |
| // they need to pass in a GitCheckoutUploadRollFunc but can't do other |
| // initialization. Find a way to make this unnecessary. |
| func SetupGithub(ctx context.Context, p *GitCheckoutParent, forkRepoURL string) error { |
| // Check to see whether we have a remote for the fork. |
| remoteOutput, err := p.Git(ctx, "remote", "show") |
| if err != nil { |
| return skerr.Wrap(err) |
| } |
| remoteFound := false |
| remoteLines := strings.Split(remoteOutput, "\n") |
| for _, remoteLine := range remoteLines { |
| if remoteLine == githubForkRemoteName { |
| remoteFound = true |
| break |
| } |
| } |
| if !remoteFound { |
| if _, err := p.Git(ctx, "remote", "add", githubForkRemoteName, forkRepoURL); err != nil { |
| return skerr.Wrap(err) |
| } |
| } |
| if _, err := p.Git(ctx, "fetch", githubForkRemoteName); err != nil { |
| return skerr.Wrap(err) |
| } |
| return nil |
| } |