blob: 8a5f2059ba9b5538748942443fb22ad6828b8a50 [file] [log] [blame] [edit]
package git
/*
Thin wrapper around a local Git repo.
*/
import (
"context"
"os"
"time"
"go.skia.org/infra/go/exec"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/go/sklog"
"go.skia.org/infra/go/vcsinfo"
)
// Repo is used for managing a local git repo, without a working copy.
type Repo interface {
GitDir
// Update syncs the Repo from its remote.
Update(ctx context.Context) error
// Checkout returns a Checkout of the Repo in the given working directory.
Checkout(ctx context.Context, workdir string) (CheckoutDir, error)
// TempCheckout returns a TempCheckout of the repo.
TempCheckout(ctx context.Context) (*TempCheckout, error)
}
// RepoDir implements Repo.
type RepoDir string
// NewRepo returns a Repo instance based in the given working directory. Uses
// any existing repo in the given directory, or clones one if necessary. Only
// creates bare clones; Repo does not maintain a checkout.
func NewRepo(ctx context.Context, repoUrl, workdir string) (RepoDir, error) {
g, err := newGitDir(ctx, repoUrl, workdir, true)
if err != nil {
return "", skerr.Wrap(err)
}
return RepoDir(g), nil
}
// Update syncs the Repo from its remote.
func (r RepoDir) Update(ctx context.Context) error {
gitExec, err := Executable(ctx)
if err != nil {
return skerr.Wrap(err)
}
cmd := &exec.Command{
Name: gitExec,
Args: []string{"--git-dir=.", "fetch", "--force", "--all", "--prune"},
Dir: r.Dir(),
Timeout: 2 * time.Minute,
}
out, err := exec.RunCommand(ctx, cmd)
if err != nil {
return skerr.Wrapf(err, "failed to update repo")
}
sklog.Debugf("DEBUG: output of 'git fetch':\n%s", out)
return nil
}
// Checkout returns a Checkout of the Repo in the given working directory.
func (r RepoDir) Checkout(ctx context.Context, workdir string) (CheckoutDir, error) {
return NewCheckout(ctx, r.Dir(), workdir)
}
// TempCheckout returns a TempCheckout of the repo.
func (r RepoDir) TempCheckout(ctx context.Context) (*TempCheckout, error) {
return NewTempCheckout(ctx, r.Dir())
}
// Dir returns the working directory of the Repo.
func (r RepoDir) Dir() string {
return string(r)
}
// Git runs the given git command in the Repo.
func (r RepoDir) Git(ctx context.Context, cmd ...string) (string, error) {
git, err := Executable(ctx)
if err != nil {
return "", skerr.Wrap(err)
}
baseCmd := []string{git, "--git-dir=."}
return exec.RunCwd(ctx, r.Dir(), append(baseCmd, cmd...)...)
}
// Details returns a vcsinfo.LongCommit instance representing the given commit.
func (r RepoDir) Details(ctx context.Context, name string) (*vcsinfo.LongCommit, error) {
return gitRunner_Details(ctx, r, name)
}
// RevParse runs "git rev-parse <name>" and returns the result.
func (r RepoDir) RevParse(ctx context.Context, args ...string) (string, error) {
return gitRunner_RevParse(ctx, r, args...)
}
// RevList runs "git rev-list <name>" and returns a slice of commit hashes.
func (r RepoDir) RevList(ctx context.Context, args ...string) ([]string, error) {
return gitRunner_RevList(ctx, r, args...)
}
// GetBranchHead returns the commit hash at the HEAD of the given branch.
func (r RepoDir) GetBranchHead(ctx context.Context, branchName string) (string, error) {
return gitRunner_GetBranchHead(ctx, r, branchName)
}
// Branches runs "git branch" and returns a slice of Branch instances.
func (r RepoDir) Branches(ctx context.Context) ([]*Branch, error) {
return gitRunner_Branches(ctx, r)
}
// GetFile returns the contents of the given file at the given commit.
func (r RepoDir) GetFile(ctx context.Context, fileName, commit string) (string, error) {
return gitRunner_GetFile(ctx, r, fileName, commit)
}
// IsSubmodule returns true if the given path is submodule, ie contains gitlink.
func (r RepoDir) IsSubmodule(ctx context.Context, path, commit string) (bool, error) {
return gitRunner_IsSubmodule(ctx, r, path, commit)
}
// ReadSubmodule returns commit hash of the given path, if the path is git
// submodule. ErrorNotFound is returned if path is not found in the git
// worktree. ErrorNotSubmodule is returned if path exists, but it's not a
// submodule.
func (r RepoDir) ReadSubmodule(ctx context.Context, path, commit string) (string, error) {
return gitRunner_ReadSubmodule(ctx, r, path, commit)
}
// UpdateSubmodule updates git submodule of the given path to the given commit.
// If submodule doesn't exist, it returns ErrorNotFound since it doesn't have
// all necessary information to create a valid submodule (requires an entry in
// .gitmodules).
func (r RepoDir) UpdateSubmodule(ctx context.Context, path, commit string) error {
return gitRunner_UpdateSubmodule(ctx, r, path, commit)
}
// NumCommits returns the number of commits in the repo.
func (r RepoDir) NumCommits(ctx context.Context) (int64, error) {
return gitRunner_NumCommits(ctx, r)
}
// IsAncestor returns true iff A is an ancestor of B.
func (r RepoDir) IsAncestor(ctx context.Context, a, b string) (bool, error) {
return gitRunner_IsAncestor(ctx, r, a, b)
}
// Version returns the Git version.
func (r RepoDir) Version(ctx context.Context) (int, int, error) {
return gitRunner_Version(ctx)
}
// FullHash gives the full commit hash for the given ref.
func (r RepoDir) FullHash(ctx context.Context, ref string) (string, error) {
return gitRunner_FullHash(ctx, r, ref)
}
// CatFile runs "git cat-file -p <ref>:<path>".
func (r RepoDir) CatFile(ctx context.Context, ref, path string) ([]byte, error) {
return gitRunner_CatFile(ctx, r, ref, path)
}
// ReadDir is analogous to os.File.Readdir for a particular ref.
func (r RepoDir) ReadDir(ctx context.Context, ref, path string) ([]os.FileInfo, error) {
return gitRunner_ReadDir(ctx, r, ref, path)
}
// GetRemotes returns a mapping of remote repo name to URL.
func (r RepoDir) GetRemotes(ctx context.Context) (map[string]string, error) {
return gitRunner_GetRemotes(ctx, r)
}
// VFS returns a vfs.FS using Git for the given revision.
func (r RepoDir) VFS(ctx context.Context, ref string) (*FS, error) {
return VFS(ctx, r, ref)
}
// Assert that RepoDir implements Repo.
var _ Repo = RepoDir("")