| package repo_manager |
| |
| import ( |
| "context" |
| "errors" |
| "fmt" |
| "net/http" |
| |
| "go.skia.org/infra/autoroll/go/codereview" |
| "go.skia.org/infra/autoroll/go/revision" |
| "go.skia.org/infra/go/gerrit" |
| "go.skia.org/infra/go/gitiles" |
| ) |
| |
| /* |
| Repo manager which does not use a local checkout. |
| |
| Use this repo manager as a helper but not directly. |
| */ |
| |
| // NoCheckoutRepoManagerConfig provides configuration for the noCheckoutRepoManager. |
| type NoCheckoutRepoManagerConfig struct { |
| CommonRepoManagerConfig |
| |
| // URL of the parent repo. |
| ParentRepo string `json:"parentRepo"` |
| } |
| |
| // See documentation for RepoManagerConfig interface. |
| func (c *NoCheckoutRepoManagerConfig) NoCheckout() bool { |
| return true |
| } |
| |
| func (c *NoCheckoutRepoManagerConfig) Validate() error { |
| if err := c.CommonRepoManagerConfig.Validate(); err != nil { |
| return err |
| } |
| if c.ParentRepo == "" { |
| return errors.New("ParentRepo is required.") |
| } |
| if len(c.PreUploadSteps) > 0 { |
| return errors.New("Checkout-less rollers don't support pre-upload steps") |
| } |
| return nil |
| } |
| |
| // noCheckoutRepoManager is a RepoManager which rolls without the use of a |
| // local checkout. |
| type noCheckoutRepoManager struct { |
| *commonRepoManager |
| baseCommit string |
| createRoll noCheckoutCreateRollHelperFunc |
| gerritConfig *codereview.GerritConfig |
| parentRepo *gitiles.Repo |
| updateHelper noCheckoutUpdateHelperFunc |
| } |
| |
| // noCheckoutUpdateHelperFunc is a function called by noCheckoutRepoManager.Update() |
| // which returns the last roll revision, next roll revision, and a list of |
| // not-yet-rolled revisions. The parameters are the parent repo and its base |
| // commit. |
| type noCheckoutUpdateHelperFunc func(context.Context, *gitiles.Repo, string) (*revision.Revision, *revision.Revision, []*revision.Revision, error) |
| |
| // noCheckoutCreateRollHelperFunc is a function called by |
| // noCheckoutRepoManager.CreateNewRoll() which returns a commit message for |
| // a given roll, plus a map of file names to new contents, given the previous |
| // roll revision, next roll revision, URL of the server, extra trybots for the |
| // CQ, and TBR emails. |
| type noCheckoutCreateRollHelperFunc func(context.Context, *revision.Revision, *revision.Revision, []*revision.Revision, string, string, []string) (string, map[string]string, error) |
| |
| // Return a noCheckoutRepoManager instance. |
| func newNoCheckoutRepoManager(ctx context.Context, c NoCheckoutRepoManagerConfig, workdir string, g gerrit.GerritInterface, serverURL string, client *http.Client, cr codereview.CodeReview, createRoll noCheckoutCreateRollHelperFunc, updateHelper noCheckoutUpdateHelperFunc, local bool) (*noCheckoutRepoManager, error) { |
| if err := c.Validate(); err != nil { |
| return nil, err |
| } |
| crm, err := newCommonRepoManager(ctx, c.CommonRepoManagerConfig, workdir, serverURL, g, client, cr, local) |
| if err != nil { |
| return nil, err |
| } |
| rv := &noCheckoutRepoManager{ |
| commonRepoManager: crm, |
| createRoll: createRoll, |
| gerritConfig: cr.Config().(*codereview.GerritConfig), |
| parentRepo: gitiles.NewRepo(c.ParentRepo, client), |
| updateHelper: updateHelper, |
| } |
| return rv, nil |
| } |
| |
| // See documentation for RepoManager interface. |
| func (rm *noCheckoutRepoManager) CreateNewRoll(ctx context.Context, from, to *revision.Revision, rolling []*revision.Revision, emails []string, cqExtraTrybots string, dryRun bool) (int64, error) { |
| // Build the roll. |
| commitMsg, nextRollChanges, err := rm.createRoll(ctx, from, to, rolling, rm.serverURL, cqExtraTrybots, emails) |
| if err != nil { |
| return 0, err |
| } |
| |
| rm.infoMtx.Lock() |
| defer rm.infoMtx.Unlock() |
| |
| // Create the change. |
| ci, err := gerrit.CreateAndEditChange(ctx, rm.g, rm.gerritConfig.Project, rm.parentBranch, commitMsg, rm.baseCommit, func(ctx context.Context, g gerrit.GerritInterface, ci *gerrit.ChangeInfo) error { |
| for file, contents := range nextRollChanges { |
| if contents == "" { |
| if err := g.DeleteFile(ctx, ci, file); err != nil { |
| return fmt.Errorf("Failed to delete %s file: %s", file, err) |
| } |
| } else { |
| if err := g.EditFile(ctx, ci, file, contents); err != nil { |
| return fmt.Errorf("Failed to edit %s file: %s", file, err) |
| } |
| } |
| } |
| return nil |
| }) |
| if err != nil { |
| if ci != nil { |
| if err2 := rm.g.Abandon(ctx, ci, "Failed to create roll CL"); err2 != nil { |
| return 0, fmt.Errorf("Failed to create roll with: %s\nAnd failed to abandon the change with: %s", err, err2) |
| } |
| } |
| return 0, err |
| } |
| |
| // Mark the change as ready for review, if necessary. |
| if err := rm.unsetWIP(ctx, ci, 0); err != nil { |
| return 0, err |
| } |
| |
| // Set the CQ bit as appropriate. |
| labels := rm.g.Config().SetCqLabels |
| if dryRun { |
| labels = rm.g.Config().SetDryRunLabels |
| } |
| labels = gerrit.MergeLabels(labels, rm.g.Config().SelfApproveLabels) |
| if err = rm.g.SetReview(ctx, ci, "", labels, emails); err != nil { |
| // TODO(borenet): Should we try to abandon the CL? |
| return 0, fmt.Errorf("Failed to set review: %s", err) |
| } |
| |
| // Manually submit if necessary. |
| if !rm.g.Config().HasCq { |
| if err := rm.g.Submit(ctx, ci); err != nil { |
| // TODO(borenet): Should we try to abandon the CL? |
| return 0, fmt.Errorf("Failed to submit: %s", err) |
| } |
| } |
| |
| return ci.Issue, nil |
| } |
| |
| // See documentation for RepoManager interface. |
| func (rm *noCheckoutRepoManager) Update(ctx context.Context) (*revision.Revision, *revision.Revision, []*revision.Revision, error) { |
| rm.repoMtx.Lock() |
| defer rm.repoMtx.Unlock() |
| // Find HEAD of the desired parent branch. We make sure to provide the |
| // base commit of our change, to avoid clobbering other changes to the |
| // DEPS file. |
| baseCommit, err := rm.parentRepo.Details(ctx, rm.parentBranch) |
| if err != nil { |
| return nil, nil, nil, err |
| } |
| |
| // Get the next roll rev, and the list of versions in between the last |
| // and next rolls. |
| lastRollRev, tipRev, notRolledRevs, err := rm.updateHelper(ctx, rm.parentRepo, baseCommit.Hash) |
| if err != nil { |
| return nil, nil, nil, err |
| } |
| |
| rm.infoMtx.Lock() |
| defer rm.infoMtx.Unlock() |
| rm.baseCommit = baseCommit.Hash |
| return lastRollRev, tipRev, notRolledRevs, nil |
| } |
| |
| // See documentation for RepoManager interface. |
| func (r *noCheckoutRepoManager) GetRevision(ctx context.Context, id string) (*revision.Revision, error) { |
| return nil, errors.New("NOT IMPLEMENTED") |
| } |