blob: 623d830175e35e5a6b10787ddc8d4f5733486c99 [file] [log] [blame]
package git_common
import (
"context"
"os"
"path/filepath"
"go.skia.org/infra/autoroll/go/config_vars"
"go.skia.org/infra/autoroll/go/repo_manager/common/version_file_common"
"go.skia.org/infra/autoroll/go/revision"
"go.skia.org/infra/go/git"
"go.skia.org/infra/go/skerr"
)
// GitCheckoutConfig provides configuration for a Checkout.
type GitCheckoutConfig struct {
// Branch is the name of the git branch to be tracked by the Checkout.
Branch *config_vars.Template `json:"branch"`
// RepoURL is the git repository URL.
RepoURL string `json:"repoURL"`
// RevLinkTmpl is an optional template used for generating links to
// Revisions. If not specified, Revisions generated by the Checkout will not
// have an associated URL.
RevLinkTmpl string `json:"revLinkTmpl"`
// Dependencies is an optional specification of dependencies to track.
// Revisions generated by the Checkout will contain the pinned versions of
// these dependencies.
Dependencies []*version_file_common.VersionFileConfig `json:"dependencies"`
}
// See documentation for util.Validator interface.
func (c GitCheckoutConfig) Validate() error {
if c.Branch == nil {
return skerr.Fmt("Branch is required")
}
if err := c.Branch.Validate(); err != nil {
return skerr.Wrap(err)
}
if c.RepoURL == "" {
return skerr.Fmt("RepoURL is required")
}
return nil
}
// Checkout provides common functionality for git checkouts.
type Checkout struct {
*git.Checkout
Branch *config_vars.Template
Dependencies []*version_file_common.VersionFileConfig
RepoURL string
RevLinkTmpl string
}
// NewCheckout returns a Checkout instance.
func NewCheckout(ctx context.Context, c GitCheckoutConfig, reg *config_vars.Registry, workdir, userName, userEmail string, co *git.Checkout) (*Checkout, error) {
// Clean up any lockfiles, in case the process was interrupted.
if err := git.DeleteLockFiles(ctx, workdir); err != nil {
return nil, skerr.Wrap(err)
}
// Register the configured branch template.
if err := reg.Register(c.Branch); err != nil {
return nil, skerr.Wrap(err)
}
// Create the local checkout.
if co == nil {
var err error
co, err = git.NewCheckout(ctx, c.RepoURL, workdir)
if err != nil {
return nil, skerr.Wrap(err)
}
}
// Set the git user name and email.
if _, err := co.Git(ctx, "config", "--local", "user.name", userName); err != nil {
return nil, skerr.Wrap(err)
}
if _, err := co.Git(ctx, "config", "--local", "user.email", userEmail); err != nil {
return nil, skerr.Wrap(err)
}
return &Checkout{
Checkout: co,
Branch: c.Branch,
Dependencies: c.Dependencies,
RepoURL: c.RepoURL,
RevLinkTmpl: c.RevLinkTmpl,
}, nil
}
// See documentation for child.Child interface.
func (c *Checkout) GetRevision(ctx context.Context, id string) (*revision.Revision, error) {
details, err := c.Details(ctx, id)
if err != nil {
return nil, skerr.Wrap(err)
}
rev := revision.FromLongCommit(c.RevLinkTmpl, details)
if len(c.Dependencies) > 0 {
deps, err := version_file_common.GetPinnedRevs(ctx, c.Dependencies, func(ctx context.Context, path string) (string, error) {
return c.GetFile(ctx, path, rev.Id)
})
if err != nil {
return nil, skerr.Wrap(err)
}
rev.Dependencies = deps
}
return rev, nil
}
// See documentation for child.Child interface.
func (c *Checkout) Download(ctx context.Context, rev *revision.Revision, dest string) error {
return Clone(ctx, c.RepoURL, dest, rev)
}
// Update resolves the configured branch template, updates the Checkout to the
// newest Revision on the resulting branch and returns both the revision and
// resolved branch name.
func (c *Checkout) Update(ctx context.Context) (*revision.Revision, string, error) {
branch := c.Branch.String()
if err := c.UpdateBranch(ctx, branch); err != nil {
return nil, "", skerr.Wrap(err)
}
tipRev, err := c.GetRevision(ctx, "HEAD")
if err != nil {
return nil, "", skerr.Wrap(err)
}
return tipRev, branch, nil
}
// LogFirstParent returns a slice of revision.Revision instances in the given range.
func (c *Checkout) LogFirstParent(ctx context.Context, from, to *revision.Revision) ([]*revision.Revision, error) {
hashes, err := c.RevList(ctx, "--first-parent", git.LogFromTo(from.Id, to.Id))
if err != nil {
return nil, skerr.Wrap(err)
}
revs := make([]*revision.Revision, 0, len(hashes))
for _, hash := range hashes {
rev, err := c.GetRevision(ctx, hash)
if err != nil {
return nil, skerr.Wrap(err)
}
revs = append(revs, rev)
}
return revs, nil
}
// Clone clones the given repo into the given destination and syncs it to the
// given Revision.
func Clone(ctx context.Context, repoUrl, dest string, rev *revision.Revision) error {
// If the checkout does not already exist in dest, create it.
gitDir := filepath.Join(dest, ".git")
if _, err := os.Stat(gitDir); os.IsNotExist(err) {
if err := git.Clone(ctx, repoUrl, dest, false); err != nil {
return skerr.Wrap(err)
}
}
// Fetch and reset to the given revision.
co := &git.Checkout{GitDir: git.GitDir(dest)}
if err := co.Fetch(ctx); err != nil {
return skerr.Wrap(err)
}
if _, err := co.Git(ctx, "reset", "--hard", rev.Id); err != nil {
return skerr.Wrap(err)
}
return nil
}