git

package
v0.65.0 Latest Latest
Warning

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

Go to latest
Published: Aug 4, 2026 License: Apache-2.0 Imports: 9 Imported by: 0

Documentation

Overview

Package git provides repository checkouts for scanners that operate on source trees.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func IsLocalPath added in v0.55.0

func IsLocalPath(url string) bool

IsLocalPath reports whether url names a directory on this machine rather than a remote.

Deliberately a filesystem question rather than a URL-parsing one: "." and "../service" and an absolute path are all local, and anything with a scheme or an scp-style host is not.

func UncommittedFiles added in v0.55.0

func UncommittedFiles(ctx context.Context, url string) int

UncommittedFiles counts a local repository's uncommitted changes, or 0 when there are none, the path is not local, or git cannot say.

Exists because a local path is cloned like any other source: the scan sees the *committed* state, not the files on disk. That is right — a scan has to describe a revision someone else can reproduce — and it is invisible, so a change that introduces a finding passes until it is committed. Best-effort: a scan must not fail because git could not answer a courtesy question.

func WithPool added in v0.65.0

func WithPool(ctx context.Context, p *Pool) context.Context

WithPool returns a context carrying p, so the scanners a run drives share its checkouts.

Context rather than a parameter because the alternative is a new argument on the Scanner interface — public API, and one every third-party scanner would have to accept for a housekeeping detail it has no opinion about. A scanner that is handed no pool clones for itself, which is what makes a scanner runnable on its own and in a test.

Types

type Pool added in v0.65.0

type Pool struct {
	// contains filtered or unexported fields
}

Pool materialises each distinct checkout once per run and shares it between the scanners that asked for it.

Every repository scanner checks out for itself, so a five-control scan of one repository used to clone it five times. For a local path that costs little; for a remote it is five network fetches, and the cost grows with the repository.

The stronger reason is agreement. Independent clones of a moving branch can resolve to different commits, so two controls could report against different code while the report named one revision. One checkout per run removes the possibility rather than describing it.

The shared tree is made **read-only**. Every scanner Draugr ships only reads what it scans, but "only reads" is a property of each tool, and sharing a directory would quietly make one scanner's write another's input. A tool that writes now fails where it writes, which is a bug report rather than a corrupted neighbouring scan.

func NewPool added in v0.65.0

func NewPool() *Pool

NewPool returns an empty pool. Close it when the run ends.

func PoolFrom added in v0.65.0

func PoolFrom(ctx context.Context) *Pool

PoolFrom returns the run's pool, or nil.

func (*Pool) Checkout added in v0.65.0

func (p *Pool) Checkout(ctx context.Context, key string, materialise func(context.Context) (Tree, func(), error)) (Tree, func(), error)

Checkout returns the shared tree for key, materialising it on first request.

Callers that arrive while the first is still cloning wait for it rather than starting their own. A failed checkout is remembered too — five scanners should report one unreachable repository once, not attempt it five times over.

The returned cleanup is a no-op: the pool owns the directory until Close.

func (*Pool) Close added in v0.65.0

func (p *Pool) Close()

Close removes every checkout the pool materialised.

type Scope added in v0.52.0

type Scope struct {
	// Paths restricts the checkout to these directories. Empty means the whole repository.
	//
	// Directory prefixes, not general globs: `services/web` and `services/web/**` both mean the
	// same subtree. That is what sparse checkout can express, and expressing it any other way
	// would mean downloading the repository to throw most of it away.
	Paths []string

	// Ignore removes matching paths after checkout, applied last so it can carve out of Paths.
	// Gitignore-style: a trailing `/` matches a directory and everything under it, `*` matches
	// within a path segment, `**` matches across them.
	Ignore []string
}

Scope restricts which of a repository's files a checkout materialises.

Shaping the tree rather than passing flags to each tool is deliberate. Every repository scanner is handed the checkout directory and points its tool at it — Trivy, Semgrep, Gitleaks and gosec all take a root and walk it. Translating a descriptor's scope into each tool's own include and exclude syntax would be a mapping per tool, wrong in a different way for each, and absent for the next scanner someone adds. A tree that already contains what was asked for needs no translation and cannot be forgotten.

func (Scope) Empty added in v0.52.0

func (s Scope) Empty() bool

Empty reports whether the scope restricts anything.

func (Scope) Key added in v0.52.0

func (s Scope) Key() string

Key renders the scope for a cache or dedup identity. Two components scoped to different subtrees of one repository are different scans, and a key that cannot tell them apart lets the second silently receive the first's findings.

type Tree added in v0.64.0

type Tree struct {
	// Dir is the checkout on disk.
	Dir string
	// Revision is the SHA that was materialised, as `git rev-parse HEAD` reports it. Empty only
	// when git could not be asked, which is not worth failing a scan over.
	Revision string
	// Dirty counts uncommitted files in the source. For a clone they are what the tree is
	// missing; for a working-tree copy they are what it uniquely contains. WorkingTree says
	// which, so nothing has to infer it from a count.
	Dirty int
	// WorkingTree reports that this copy came from a checkout on disk, uncommitted work included,
	// rather than from a commit — so it is not reproducible.
	WorkingTree bool
}

Tree is a materialised copy of a repository, and the revision it actually holds.

The resolved revision matters because a descriptor usually does not name one: `revision` is empty far more often than not, meaning "the default branch", which is a moving answer. A report that cannot say which commit it describes cannot be reproduced or compared, and that is the entire justification for scanning a committed revision in the first place.

func Checkout

func Checkout(ctx context.Context, url, revision string, scope Scope) (tree Tree, cleanup func(), err error)

Checkout clones url into a fresh temporary directory, materialising only what scope allows. With an empty revision it does a shallow clone of the default branch; otherwise it clones and checks out revision. The returned cleanup removes the directory (call it even on error paths that returned a dir).

func CheckoutWorkingTree added in v0.64.0

func CheckoutWorkingTree(ctx context.Context, path string, scope Scope) (Tree, func(), error)

CheckoutWorkingTree copies a local checkout — including uncommitted work — into a temporary directory and scopes it exactly as Checkout does.

A copy rather than the path itself, which is the whole point. Scanning in place would mean a tool writing its caches into somebody's repository, and `paths`/`ignore` are applied by deleting what is not wanted — against a real checkout that is not scoping, it is data loss.

The file list comes from `git ls-files -co --exclude-standard`: tracked files plus untracked ones that are not ignored. That is git's own answer to "what is in this working tree", so a build artifact directory or a local .env is left behind for the same reason a commit would leave it behind, rather than by a rule Draugr invented.

Jump to

Keyboard shortcuts

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