clone

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 1, 2026 License: MIT Imports: 21 Imported by: 0

README

clone

Go library for programs that keep local checkouts of HTTPS Git repositories. It shells out to the git binary, which must be on PATH. The package supports Go 1.25 or later. For in-process object parsing or history walking, use a library such as go-git.

Install

go get github.com/git-pkgs/clone

Clone or update a checkout

Ensure creates a shallow clone on its first call, then fetches and resets the existing checkout on later calls. The ref can be a branch, tag, commit ID, or an empty string for the remote's default branch.

ctx := context.Background()
url := "https://github.com/git-pkgs/clone"
dst := "/var/cache/my-tool/clone"

if err := clone.Ensure(ctx, clone.Retry{}, url, dst, "main", false); err != nil {
    log.Fatal(err)
}

fmt.Println(clone.Head(ctx, dst))

Pass true as the final argument for a full clone, including when an existing shallow checkout needs to be unshallowed. Clone and fetch errors are returned as *clone.UnreachableError, except when the context was canceled or reached its deadline. errors.As retrieves the URL and underlying Git error. ValidateURL accepts https:// URLs, while ValidateRef rejects leading hyphens, .., and characters outside letters, digits, ., _, /, and -.

Persistent cache

Cache stores one checkout per URL under Root. Prepare holds a per-URL lock while updating the shallow checkout, then replaces dst with a copy and returns its commit. The destination must be outside Root.

cache := clone.Cache{
    Root: "/var/cache/my-tool/repositories",
}

commit, err := cache.Prepare(ctx,
    "https://github.com/git-pkgs/clone",
    "",
    "/tmp/job/src",
)
if err != nil {
    log.Fatal(err)
}

fmt.Println(commit, cache.DiskBytes("https://github.com/git-pkgs/clone"))

When a historical commit is missing from the shallow cache, EnsureCommit unshallows the checkout:

if err := cache.EnsureCommit(ctx, url, commit); err != nil {
    log.Fatal(err)
}

Read a file from a commit

InspectBlob runs git show <commit>:<path> and reads at most maxBytes+1. The extra byte distinguishes content exactly at the limit from truncated content. Complete reads use magic.Detect; truncated reads use magic.DetectPrefix so the result can report that later bytes may change the classification. The returned content is retained for text, binary, and unknown results.

Both blob functions validate commits and paths before invoking Git. ValidCommit and SanitizePath are also available when callers need to validate input earlier:

path, ok := clone.SanitizePath("cmd/tool/main.go")
if !ok || !clone.ValidCommit(commit) {
    log.Fatal("invalid commit or path")
}

result, err := clone.InspectBlob(
    ctx,
    filepath.Join(cache.Dir(url), "src"),
    commit,
    path,
    2<<20,
)
if err != nil {
    log.Fatal(err)
}
if result.Detection.Kind == magic.KindText &&
    result.Detection.Encoding == "utf-8" {
    fmt.Printf("%s", result.Content)
}
fmt.Println("truncated:", result.Truncated)

Blob remains available for callers that only need its original NUL-based binary flag. It returns nil content when a NUL occurs within the returned range; a NUL beyond maxBytes is not observed.

Remote queries

RemoteBranches returns sorted branch names from git ls-remote --heads. It disables terminal prompts and the ambient credential helper so a supplied URL cannot trigger credential lookup. RemoteHead returns the SHA advertised for HEAD and keeps ambient non-interactive credentials available.

branches, err := clone.RemoteBranches(ctx, clone.Retry{}, url)
if err != nil {
    log.Fatal(err)
}

head, err := clone.RemoteHead(ctx, clone.Retry{}, url)
if err != nil {
    log.Fatal(err)
}

Retry policy

The zero value of Retry allows three attempts with exponential backoff and positive jitter. Do retries only recognized network and remote-service failures. Permanent markers win when output contains both kinds, and an unknown message stops after the first attempt.

retry := clone.Retry{
    Notify: func(notice clone.Notice) {
        log.Printf(
            "retrying %s after attempt %d of %d in %s",
            notice.Label,
            notice.Attempt,
            notice.Attempts,
            notice.Delay,
        )
    },
}

out, err := retry.Do(ctx, clone.Command{
    Label: "ls-remote",
    Env:   []string{"GIT_TERMINAL_PROMPT=0"},
    Args:  []string{"ls-remote", "--", url, "HEAD"},
})

TransientFailure exposes the same fail-closed classifier for callers with their own command loop. RunnerWithWaitDelay creates a Runner with a custom bound for transport child processes that retain Git's output pipe.

License

MIT

Documentation

Overview

Package clone keeps local checkouts of HTTPS Git repositories. It shells out to the git binary, which must be on PATH. It provides shallow clone-or-fetch, bounded retries for network failures, a persistent cache, and capped reads and content classification for files from commits.

Applications that need to parse Git objects or walk history in process can use a library such as github.com/go-git/go-git.

Index

Constants

View Source
const (
	DefaultAttempts  = 3
	DefaultBaseDelay = 500 * time.Millisecond
	DefaultMaxDelay  = 4 * time.Second
)
View Source
const DefaultWaitDelay = 10 * time.Second

Variables

This section is empty.

Functions

func Blob

func Blob(ctx context.Context, dir, commit, blobPath string, maxBytes int64) (content []byte, binary, truncated bool, err error)

Blob reads path from commit in dir. It caps content at maxBytes and reports whether the blob is binary or was truncated. commit and path are validated with ValidCommit and SanitizePath before reaching Git.

func CopyTree

func CopyTree(src, dst string) error

CopyTree recursively copies src to dst, preserving permissions but not ownership or timestamps. Symlinks are recreated with their original target.

func DestReset

func DestReset(dst string) func() error

DestReset returns the cleanup to run after a failed clone attempt, or nil when there is nothing safe to clean. A clone that dies partway can leave the destination behind, and `git clone` refuses a non-empty target, so the cleanup is needed both before retries and before a terminal error return.

Removal is offered only when dst is absent or empty at this point. Callers reach the clone path exactly when dst holds no .git, so an absent or empty dst can only ever gain content this call put there. A non-empty one belongs to the caller, and Git would reject it as a permanent error that is never retried anyway.

func Ensure

func Ensure(ctx context.Context, retry Retry, url, dst, ref string, full bool) error

Ensure clones url into dst on its first call, then fetches and resets the checkout on later calls. A shallow clone is used unless full is true. An existing shallow clone is unshallowed when full changes to true. ref may be a branch, tag, commit ID, or empty for the remote's default branch.

func Head(ctx context.Context, dir string) string

Head returns the object ID at HEAD in dir, or an empty string when dir is not a Git repository.

func RedactURL added in v0.1.1

func RedactURL(raw string) string

RedactURL replaces any userinfo in raw with a fixed placeholder so error messages and logs cannot leak an embedded token. A URL that fails to parse is returned unchanged: url.Parse does not accept control bytes, so an unparseable string here is one ValidateURL would already have rejected for a reason unrelated to its credential.

func RemoteBranches

func RemoteBranches(ctx context.Context, retry Retry, url string) ([]string, error)

RemoteBranches returns the sorted branch names advertised by url. It disables terminal prompts and the ambient credential helper.

func RemoteHead

func RemoteHead(ctx context.Context, retry Retry, url string) (string, error)

RemoteHead returns the object ID advertised as HEAD by url.

func Run

func Run(ctx context.Context, dir string, env []string, args ...string) (string, error)

Run executes Git with the production WaitDelay.

func SanitizePath

func SanitizePath(value string) (string, bool)

SanitizePath returns a slash-form path safe to use in a Git object expression. It rejects empty and absolute paths, NUL bytes, and traversal.

func TransientFailure

func TransientFailure(out string) bool

TransientFailure reports whether Git's combined output describes a failure worth another attempt.

The classification fails closed. A permanent marker wins over a transient one, and output matching nothing at all is treated as permanent, so an unfamiliar message keeps today's single-attempt behavior rather than turning into repeated remote traffic.

func ValidCommit

func ValidCommit(sha string) bool

ValidCommit reports whether sha is a lowercase hexadecimal object ID or abbreviated object ID between 4 and 64 characters long.

func ValidateRef

func ValidateRef(ref string) error

ValidateRef restricts refs to a conservative branch and tag name character set before they are passed to Git.

func ValidateURL

func ValidateURL(raw string) error

ValidateURL rejects Git URLs that do not use HTTPS, do not parse, or contain control bytes. Embedded userinfo is accepted (some callers use https://<token>@host/... for private repos) but should be redacted before logging; UnreachableError.Error and this package's own error strings do so via RedactURL.

Types

type BlobResult added in v0.2.0

type BlobResult struct {
	Content   []byte
	Detection magic.Result
	Truncated bool
}

BlobResult contains a bounded blob read and its content classification.

func InspectBlob added in v0.2.0

func InspectBlob(ctx context.Context, dir, commit, blobPath string, maxBytes int64) (BlobResult, error)

InspectBlob reads path from commit in dir and classifies the returned bytes. It uses prefix detection when maxBytes truncates the blob. commit and path are validated with ValidCommit and SanitizePath before reaching Git.

type Cache

type Cache struct {
	Root  string // Parent directory for per-URL checkouts.
	Retry Retry  // Retry policy for clone and fetch operations.
	// contains filtered or unexported fields
}

Cache keeps one persistent checkout per repository URL. A Cache must not be copied after its first use.

func (*Cache) Dir

func (c *Cache) Dir(url string) string

Dir returns the persistent directory for url under c.Root.

func (*Cache) DiskBytes

func (c *Cache) DiskBytes(url string) int64

DiskBytes returns the number of bytes used by regular files in url's cache directory. It returns zero when the directory is absent.

func (*Cache) EnsureCommit

func (c *Cache) EnsureCommit(ctx context.Context, url, commit string) error

EnsureCommit unshallows the cached checkout when commit is not already reachable. It does nothing when the checkout is absent or already complete.

func (*Cache) Prepare

func (c *Cache) Prepare(ctx context.Context, url, ref, dst string) (string, error)

Prepare updates the cache for url, replaces dst with a copy of the checkout, and returns its HEAD commit. dst must not overlap Root.

type Command

type Command struct {
	Label string
	Dir   string
	Env   []string
	Args  []string
	// Reset runs after a failed attempt, before either another attempt or a
	// terminal error return. It must only clean command-owned state.
	Reset func() error
	// Confirm may recognize that an operation succeeded despite an ambiguous
	// transient error. A confirmation error does not replace the original Git
	// error; the normal retry budget continues unless the context ended.
	Confirm func(context.Context) (bool, error)
}

Command is one remote Git invocation plus operation-specific hooks.

type Notice

type Notice struct {
	Label    string
	Attempt  int
	Attempts int
	Delay    time.Duration
}

Notice describes a transient failure before the next attempt.

type Retry

type Retry struct {
	Attempts  int
	BaseDelay time.Duration
	MaxDelay  time.Duration
	Run       Runner
	Sleep     func(context.Context, time.Duration) error
	Notify    func(Notice)
}

Retry bounds how a remote Git invocation is retried. Its zero value uses the default policy. Fields are exposed so callers can use a tighter budget or deterministic runners and sleepers in tests.

func (Retry) Do

func (r Retry) Do(ctx context.Context, cmd Command) (string, error)

Do runs cmd, retrying only transient failures while the budget, context, cleanup hook, and optional success confirmation allow another attempt.

func (Retry) Resolved

func (r Retry) Resolved() Retry

Resolved fills zero-valued options with the defaults.

type Runner

type Runner func(ctx context.Context, dir string, env []string, args ...string) (string, error)

Runner runs one Git invocation and returns its combined output.

func RunnerWithWaitDelay

func RunnerWithWaitDelay(waitDelay time.Duration) Runner

RunnerWithWaitDelay returns a Runner with a bounded wait for transport children that retain Git's output pipe after Git itself exits.

type UnreachableError

type UnreachableError struct {
	URL string
	Err error
}

UnreachableError reports a clone or fetch failure for URL.

func (*UnreachableError) Error

func (e *UnreachableError) Error() string

func (*UnreachableError) Unwrap

func (e *UnreachableError) Unwrap() error

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL