| package repo_manager |
| |
| import ( |
| "bytes" |
| "context" |
| "path/filepath" |
| "strings" |
| "text/template" |
| |
| "go.skia.org/infra/autoroll/go/codereview" |
| "go.skia.org/infra/autoroll/go/config" |
| "go.skia.org/infra/autoroll/go/config_vars" |
| "go.skia.org/infra/autoroll/go/repo_manager/child" |
| "go.skia.org/infra/autoroll/go/repo_manager/common/gerrit_common" |
| "go.skia.org/infra/autoroll/go/repo_manager/common/git_common" |
| "go.skia.org/infra/autoroll/go/repo_manager/parent" |
| "go.skia.org/infra/autoroll/go/revision" |
| "go.skia.org/infra/go/exec" |
| "go.skia.org/infra/go/gerrit" |
| "go.skia.org/infra/go/git" |
| "go.skia.org/infra/go/skerr" |
| "go.skia.org/infra/go/sklog" |
| ) |
| |
| // CommandTmplVars is used as input to the text template provided to the |
| // SetPinnedRev command for updating the revision of the Child. |
| type CommandTmplVars struct { |
| // RollingFrom is the revision we're updating from. |
| RollingFrom string |
| |
| // RollingTo is the revision we're updating to. |
| RollingTo string |
| } |
| |
| // CommandRepoManager implements RepoManager by shelling out to various |
| // configured commands to perform all of the work. |
| type CommandRepoManager struct { |
| co *git_common.Checkout |
| shortRevRegex *config_vars.Template |
| getTipRev *config.CommandRepoManagerConfig_CommandConfig |
| getPinnedRev *config.CommandRepoManagerConfig_CommandConfig |
| setPinnedRev *config.CommandRepoManagerConfig_CommandConfig |
| createRoll git_common.CreateRollFunc |
| uploadRoll git_common.UploadRollFunc |
| } |
| |
| // NewCommandRepoManager returns a RepoManager implementation which rolls |
| // trace_processor_shell into Chrome. |
| func NewCommandRepoManager(ctx context.Context, c *config.CommandRepoManagerConfig, reg *config_vars.Registry, workdir, serverURL string, cr codereview.CodeReview) (*CommandRepoManager, error) { |
| if err := c.Validate(); err != nil { |
| return nil, skerr.Wrap(err) |
| } |
| var shortRevRegex *config_vars.Template |
| if c.ShortRevRegex != "" { |
| var err error |
| shortRevRegex, err = config_vars.NewTemplate(c.ShortRevRegex) |
| if err != nil { |
| return nil, skerr.Wrap(err) |
| } |
| if err := reg.Register(shortRevRegex); err != nil { |
| return nil, skerr.Wrap(err) |
| } |
| } |
| var rm *CommandRepoManager |
| createRoll := func(ctx context.Context, co git.Checkout, from, to *revision.Revision, _ []*revision.Revision, commitMsg string) (string, error) { |
| vars := &CommandTmplVars{ |
| RollingFrom: from.Id, |
| RollingTo: to.Id, |
| } |
| if _, err := rm.run(ctx, rm.setPinnedRev, vars); err != nil { |
| return "", skerr.Wrap(err) |
| } |
| if _, err := co.Git(ctx, "commit", "-a", "-m", commitMsg); err != nil { |
| return "", skerr.Wrap(err) |
| } |
| out, err := co.Git(ctx, "rev-parse", "HEAD") |
| if err != nil { |
| return "", skerr.Wrap(err) |
| } |
| return strings.TrimSpace(out), nil |
| } |
| g, ok := cr.Client().(gerrit.GerritInterface) |
| if !ok { |
| return nil, skerr.Fmt("CommandRepoManager must use Gerrit for code review.") |
| } |
| uploadRoll := parent.GitCheckoutUploadGerritRollFunc(g) |
| co, err := git_common.NewCheckout(ctx, c.GitCheckout, reg, workdir, cr.UserName(), cr.UserEmail(), nil) |
| if err != nil { |
| return nil, skerr.Wrap(err) |
| } |
| if err := gerrit_common.SetupGerrit(ctx, co.Checkout, g); err != nil { |
| return nil, skerr.Wrap(err) |
| } |
| rm = &CommandRepoManager{ |
| co: co, |
| shortRevRegex: shortRevRegex, |
| getTipRev: c.GetTipRev, |
| getPinnedRev: c.GetPinnedRev, |
| setPinnedRev: c.SetPinnedRev, |
| createRoll: createRoll, |
| uploadRoll: uploadRoll, |
| } |
| return rm, nil |
| } |
| |
| func makeCommand(cfg *config.CommandRepoManagerConfig_CommandConfig, baseDir string, vars *CommandTmplVars) (*exec.Command, error) { |
| args := make([]string, 0, len(cfg.Command)) |
| for _, arg := range cfg.Command { |
| tmpl, err := template.New(arg).Parse(arg) |
| if err != nil { |
| return nil, skerr.Wrap(err) |
| } |
| var buf bytes.Buffer |
| if err := tmpl.Execute(&buf, vars); err != nil { |
| return nil, skerr.Wrap(err) |
| } |
| args = append(args, buf.String()) |
| } |
| c := &exec.Command{ |
| Name: args[0], |
| Args: args[1:], |
| Dir: baseDir, |
| } |
| if cfg.Dir != "" { |
| c.Dir = filepath.Join(c.Dir, cfg.Dir) |
| } |
| for _, envVar := range cfg.Env { |
| c.Env = append(c.Env, envVar) |
| } |
| c.InheritEnv = true |
| return c, nil |
| } |
| |
| // Run the given command and return the output. |
| func (rm *CommandRepoManager) run(ctx context.Context, cmd *config.CommandRepoManagerConfig_CommandConfig, vars *CommandTmplVars) (string, error) { |
| c, err := makeCommand(cmd, rm.co.Dir(), vars) |
| if err != nil { |
| return "", skerr.Wrap(err) |
| } |
| sklog.Infof("Running: %s %s", c.Name, strings.Join(c.Args, " ")) |
| out, err := exec.RunCommand(ctx, c) |
| if err != nil { |
| return out, err |
| } |
| return strings.TrimSpace(out), nil |
| } |
| |
| // Update implements RepoManager. |
| func (rm *CommandRepoManager) Update(ctx context.Context) (*revision.Revision, *revision.Revision, []*revision.Revision, error) { |
| if _, _, err := rm.co.Update(ctx); err != nil { |
| return nil, nil, nil, skerr.Wrap(err) |
| } |
| tipRevStr, err := rm.run(ctx, rm.getTipRev, nil) |
| if err != nil { |
| return nil, nil, nil, skerr.Wrap(err) |
| } |
| tipRev, err := rm.GetRevision(ctx, tipRevStr) |
| if err != nil { |
| return nil, nil, nil, skerr.Wrap(err) |
| } |
| lastRollRevStr, err := rm.run(ctx, rm.getPinnedRev, nil) |
| if err != nil { |
| return nil, nil, nil, skerr.Wrap(err) |
| } |
| lastRollRev, err := rm.GetRevision(ctx, lastRollRevStr) |
| if err != nil { |
| return nil, nil, nil, skerr.Wrap(err) |
| } |
| notRolledRevs, err := rm.LogRevisions(ctx, lastRollRev, tipRev) |
| if err != nil { |
| return nil, nil, nil, skerr.Wrap(err) |
| } |
| return lastRollRev, tipRev, notRolledRevs, nil |
| } |
| |
| // GetRevision implements RepoManager. |
| func (rm *CommandRepoManager) GetRevision(ctx context.Context, id string) (*revision.Revision, error) { |
| // Just return the most basic Revision with the given ID. Note that we could |
| // add a command which retrieves revision information and passes it back to |
| // us in JSON format or something, but I'm not sure how valuable that would |
| // be. |
| rev := &revision.Revision{Id: id} |
| if rm.shortRevRegex != nil { |
| shortRev, err := child.ShortRev(rm.shortRevRegex.String(), rev.Id) |
| if err != nil { |
| return nil, skerr.Wrap(err) |
| } |
| rev.Display = shortRev |
| } |
| return rev, nil |
| } |
| |
| // LogRevisions implements RepoManager. |
| func (rm *CommandRepoManager) LogRevisions(ctx context.Context, from, to *revision.Revision) ([]*revision.Revision, error) { |
| var revs []*revision.Revision |
| if from.Id != to.Id { |
| revs = append(revs, to) |
| } |
| return revs, nil |
| } |
| |
| // CreateNewRoll implements RepoManager. |
| func (rm *CommandRepoManager) CreateNewRoll(ctx context.Context, rollingFrom *revision.Revision, rollingTo *revision.Revision, revisions []*revision.Revision, reviewers []string, dryRun bool, commitMsg string) (int64, error) { |
| return rm.co.CreateNewRoll(ctx, rollingFrom, rollingTo, revisions, reviewers, dryRun, commitMsg, rm.createRoll, rm.uploadRoll) |
| } |
| |
| // commandRepoManager implements RepoManager. |
| var _ RepoManager = &CommandRepoManager{} |