| package parent |
| |
| /* |
| Parent implementations for Git repos using Gitiles (implies Gerrit). |
| */ |
| |
| import ( |
| "context" |
| "fmt" |
| "net/http" |
| "sync" |
| |
| "go.skia.org/infra/autoroll/go/codereview" |
| "go.skia.org/infra/autoroll/go/config_vars" |
| "go.skia.org/infra/autoroll/go/repo_manager/common/gerrit_common" |
| "go.skia.org/infra/autoroll/go/repo_manager/common/gitiles_common" |
| "go.skia.org/infra/autoroll/go/repo_manager/common/version_file_common" |
| "go.skia.org/infra/autoroll/go/revision" |
| "go.skia.org/infra/go/gerrit" |
| "go.skia.org/infra/go/skerr" |
| ) |
| |
| // GitilesConfig provides configuration for a Parent which uses Gitiles. |
| type GitilesConfig struct { |
| BaseConfig |
| gitiles_common.GitilesConfig |
| version_file_common.DependencyConfig |
| // Gerrit provides configuration for the Gerrit instance used for |
| // uploading rolls. |
| Gerrit *codereview.GerritConfig `json:"gerrit"` |
| } |
| |
| // See documentation for util.Validator interface. |
| func (c GitilesConfig) Validate() error { |
| if err := c.BaseConfig.Validate(); err != nil { |
| return skerr.Wrap(err) |
| } |
| if c.Gerrit == nil { |
| return skerr.Fmt("Gerrit is required") |
| } |
| if err := c.Gerrit.Validate(); err != nil { |
| return skerr.Wrap(err) |
| } |
| if err := c.GitilesConfig.Validate(); err != nil { |
| return skerr.Wrap(err) |
| } |
| if err := c.DependencyConfig.Validate(); err != nil { |
| return skerr.Wrap(err) |
| } |
| if len(c.GitilesConfig.Dependencies) != 0 { |
| return skerr.Fmt("Dependencies are inherited from the DependencyConfig and should not be set on the GitilesConfig.") |
| } |
| return nil |
| } |
| |
| // gitilesGetChangesForRollFunc computes the changes to be made in the next |
| // roll. These are returned in a map[string]string whose keys are file paths |
| // within the repo and values are the new whole contents of the files. Also |
| // returns any dependencies which are being transitively rolled. |
| type gitilesGetChangesForRollFunc func(context.Context, *gitiles_common.GitilesRepo, string, *revision.Revision, *revision.Revision, []*revision.Revision) (map[string]string, []*version_file_common.TransitiveDepUpdate, error) |
| |
| // gitilesParent is a base for implementations of Parent which use Gitiles. |
| type gitilesParent struct { |
| *baseParent |
| *gitiles_common.GitilesRepo |
| childID string |
| gerrit gerrit.GerritInterface |
| gerritConfig *codereview.GerritConfig |
| serverURL string |
| |
| getChangesForRoll gitilesGetChangesForRollFunc |
| |
| // TODO(borenet): We could make this "stateless" by having Parent.Update |
| // also return the tip revision of the Parent and passing it through to |
| // Parent.CreateNewRoll. |
| baseCommit string |
| baseCommitMtx sync.Mutex // protects baseCommit. |
| } |
| |
| // newGitiles returns a base for implementations of Parent which use Gitiles. |
| func newGitiles(ctx context.Context, c GitilesConfig, reg *config_vars.Registry, client *http.Client, serverURL string, getChangesForRoll gitilesGetChangesForRollFunc) (*gitilesParent, error) { |
| if err := c.Validate(); err != nil { |
| return nil, skerr.Wrap(err) |
| } |
| deps := make([]*version_file_common.VersionFileConfig, 0, len(c.DependencyConfig.TransitiveDeps)+1) |
| deps = append(deps, &c.DependencyConfig.VersionFileConfig) |
| deps = append(deps, c.DependencyConfig.TransitiveDeps...) |
| c.GitilesConfig.Dependencies = deps |
| gr, err := gitiles_common.NewGitilesRepo(ctx, c.GitilesConfig, reg, client) |
| if err != nil { |
| return nil, skerr.Wrap(err) |
| } |
| gc, err := c.Gerrit.GetConfig() |
| if err != nil { |
| return nil, skerr.Wrapf(err, "Failed to get Gerrit config") |
| } |
| g, err := gerrit.NewGerritWithConfig(gc, c.Gerrit.URL, client) |
| if err != nil { |
| return nil, skerr.Wrapf(err, "Failed to create Gerrit client") |
| } |
| base, err := newBaseParent(ctx, c.BaseConfig, serverURL) |
| if err != nil { |
| return nil, skerr.Wrap(err) |
| } |
| return &gitilesParent{ |
| baseParent: base, |
| childID: c.DependencyConfig.ID, |
| GitilesRepo: gr, |
| gerrit: g, |
| gerritConfig: c.Gerrit, |
| getChangesForRoll: getChangesForRoll, |
| }, nil |
| } |
| |
| // See documentation for Parent interface. |
| func (p *gitilesParent) Update(ctx context.Context) (string, error) { |
| // Find the head of the branch we're tracking. |
| baseCommit, err := p.GetTipRevision(ctx) |
| if err != nil { |
| return "", err |
| } |
| lastRollRev, ok := baseCommit.Dependencies[p.childID] |
| if !ok { |
| return "", skerr.Fmt("Unable to find dependency %q in %#v", p.childID, lastRollRev) |
| } |
| |
| // Save the data. |
| p.baseCommitMtx.Lock() |
| defer p.baseCommitMtx.Unlock() |
| p.baseCommit = baseCommit.Id |
| return lastRollRev, nil |
| } |
| |
| // See documentation for Parent interface. |
| func (p *gitilesParent) CreateNewRoll(ctx context.Context, from, to *revision.Revision, rolling []*revision.Revision, emails []string, cqExtraTrybots string, dryRun bool) (int64, error) { |
| p.baseCommitMtx.Lock() |
| defer p.baseCommitMtx.Unlock() |
| |
| nextRollChanges, transitiveDeps, err := p.getChangesForRoll(ctx, p.GitilesRepo, p.baseCommit, from, to, rolling) |
| if err != nil { |
| return 0, skerr.Wrapf(err, "getChangesForRoll func failed") |
| } |
| |
| commitMsg, err := p.buildCommitMsg(from, to, rolling, emails, cqExtraTrybots, transitiveDeps) |
| if err != nil { |
| return 0, skerr.Wrap(err) |
| } |
| |
| return CreateNewGerritRoll(ctx, p.gerrit, p.gerritConfig.Project, p.Branch(), commitMsg, p.baseCommit, nextRollChanges, emails, dryRun) |
| } |
| |
| // CreateNewGerritRoll uploads a Gerrit CL with the given changes and returns |
| // the issue number or any error which occurred. |
| func CreateNewGerritRoll(ctx context.Context, g gerrit.GerritInterface, project, branch, commitMsg, baseCommit string, changes map[string]string, emails []string, dryRun bool) (int64, error) { |
| // Create the change. |
| ci, err := gerrit.CreateAndEditChange(ctx, g, project, branch, commitMsg, baseCommit, func(ctx context.Context, g gerrit.GerritInterface, ci *gerrit.ChangeInfo) error { |
| for file, contents := range changes { |
| 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 := 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 |
| } |
| if err := gerrit_common.SetChangeLabels(ctx, g, ci, emails, dryRun); err != nil { |
| return 0, skerr.Wrap(err) |
| } |
| return ci.Issue, nil |
| } |
| |
| var _ Parent = &gitilesParent{} |