Documentation
¶
Overview ¶
Package gitcli implements the Git ports over the git binary. Every invocation runs in an isolated environment that is built here and never inherits the caller's.
Index ¶
- Variables
- func HooksDir(dataDir string) string
- type Config
- type Git
- func (g *Git) BranchHead(ctx context.Context, repo domain.RepoName, branch string) (domain.ObjectID, error)
- func (g *Git) CheckVersion(ctx context.Context) (Version, error)
- func (g *Git) Close(ctx context.Context) error
- func (g *Git) CommitTree(ctx context.Context, repo domain.RepoName, id domain.ObjectID) (domain.ObjectID, error)
- func (g *Git) DefaultBranch(ctx context.Context, name domain.RepoName) (string, bool, error)
- func (g *Git) DiffCommits(ctx context.Context, repo domain.RepoName, base, head domain.ObjectID, ...) (domain.DiffStats, domain.Diff, error)
- func (g *Git) Discard(_ context.Context, id ports.StagedID) error
- func (g *Git) GetCommit(ctx context.Context, repo domain.RepoName, rev domain.Revision, ...) (domain.CommitDetail, error)
- func (g *Git) GetTree(ctx context.Context, repo domain.RepoName, rev domain.Revision, ...) (domain.Tree, error)
- func (g *Git) HTTPBackend(ctx context.Context, push bool) (ports.HTTPBackend, error)
- func (g *Git) HasBranch(ctx context.Context, name domain.RepoName, branch string) (bool, error)
- func (g *Git) HasCommit(ctx context.Context, repo domain.RepoName, id domain.ObjectID) (bool, error)
- func (g *Git) HeadBranch(_ context.Context, name domain.RepoName) (string, error)
- func (g *Git) IsAncestor(ctx context.Context, repo domain.RepoName, ...) (bool, error)
- func (g *Git) List(_ context.Context) (ports.RepoDirs, error)
- func (g *Git) ListCommits(ctx context.Context, repo domain.RepoName, q ports.CommitQuery) (ports.CommitPage, error)
- func (g *Git) ListRange(ctx context.Context, repo domain.RepoName, exclude, head domain.ObjectID, ...) ([]domain.Commit, bool, error)
- func (g *Git) ListRefs(ctx context.Context, repo domain.RepoName) ([]domain.Ref, error)
- func (g *Git) Maintain(ctx context.Context, name domain.RepoName) error
- func (g *Git) MergeBase(ctx context.Context, repo domain.RepoName, a, b domain.ObjectID) (domain.ObjectID, error)
- func (g *Git) OpenBlob(ctx context.Context, repo domain.RepoName, rev domain.Revision, ...) (domain.BlobInfo, io.ReadCloser, error)
- func (g *Git) Publish(ctx context.Context, id ports.StagedID, name domain.RepoName) error
- func (g *Git) PurgeStaging(ctx context.Context) error
- func (g *Git) ResolveRefPath(ctx context.Context, repo domain.RepoName, path string) (domain.RefPath, error)
- func (g *Git) ResolveRevision(ctx context.Context, repo domain.RepoName, rev domain.Revision) (domain.ResolvedRevision, error)
- func (g *Git) SetDefaultBranch(ctx context.Context, name domain.RepoName, branch string) error
- func (g *Git) Stage(ctx context.Context) (ports.StagedID, error)
- func (g *Git) Trash(ctx context.Context, name domain.RepoName) error
- func (g *Git) UpdateBranch(ctx context.Context, repo domain.RepoName, branch string, ...) error
- func (g *Git) WriteCommit(ctx context.Context, repo domain.RepoName, object []byte) (domain.ObjectID, error)
- type Version
Constants ¶
This section is empty.
Variables ¶
var ErrClosed = errors.New("gitcli: reader is closed")
ErrClosed is returned by reads after Close.
var MinVersion = Version{Major: 2, Minor: 45}
MinVersion is the oldest supported git.
Functions ¶
Types ¶
type Config ¶
type Config struct {
// Bin is the git executable: a name looked up in the process PATH, or a path.
// It is required; the default belongs to the configuration.
Bin string
// DataDir is the data root (absolute). repos/, repos/.staging/ and
// trash/ below it must exist; run/git-home/ is the HOME of git.
DataDir string
// Path is the PATH given to git, which it uses to find its helpers.
Path string
// MaxReceiveBytes is the largest pack a push may send
// (receive.maxInputSize); zero leaves Git unlimited.
MaxReceiveBytes int64
// CatFileIdle is how long a repository's long-lived `git cat-file`
// process may stay unused before it is stopped. Zero means 60 seconds.
CatFileIdle time.Duration
// MaxCatFiles bounds the long-lived cat-file processes. Zero means 16.
MaxCatFiles int
// MaxExec bounds the one-shot git processes that run at once (every
// command but the long-lived cat-file processes and the blob streams);
// callers beyond it wait for a slot until their context ends. Zero means
// max(4, 2*GOMAXPROCS).
MaxExec int
// MaxBlobStreams bounds the open blob streams, each of which holds a git
// process until its reader is closed. Streams beyond it are refused with
// domain.ErrResourceExhausted rather than queued: a slow reader could hold
// a slot for long. Zero means 32.
MaxBlobStreams int
}
Config configures a Git adapter. The adapter never reads the process environment: the caller passes what it needs.
type Git ¶
type Git struct {
// contains filtered or unexported fields
}
Git is the git CLI adapter for ports.GitRepos.
func (*Git) BranchHead ¶ added in v0.2.0
func (g *Git) BranchHead(ctx context.Context, repo domain.RepoName, branch string) (domain.ObjectID, error)
BranchHead resolves refs/heads/<branch>^{commit} with `cat-file`.
func (*Git) CheckVersion ¶
CheckVersion runs `git version` and fails if git is missing, unreadable or older than MinVersion. It returns the version found.
func (*Git) Close ¶
Close stops the long-lived git processes of the read side and waits for them, until ctx ends. Reads after Close fail with ErrClosed. Close is idempotent; the adapter's write side keeps working.
func (*Git) CommitTree ¶ added in v0.2.0
func (g *Git) CommitTree(ctx context.Context, repo domain.RepoName, id domain.ObjectID) (domain.ObjectID, error)
CommitTree runs `git rev-parse <commit>^{tree}`.
func (*Git) DefaultBranch ¶
DefaultBranch reads HEAD and checks the branch with `git for-each-ref`.
func (*Git) DiffCommits ¶ added in v0.2.0
func (g *Git) DiffCommits(ctx context.Context, repo domain.RepoName, base, head domain.ObjectID, limits domain.DiffLimits) (domain.DiffStats, domain.Diff, error)
DiffCommits reads the diff between two commits like GetCommit does.
func (*Git) GetCommit ¶
func (g *Git) GetCommit(ctx context.Context, repo domain.RepoName, rev domain.Revision, limits domain.DiffLimits) (domain.CommitDetail, error)
GetCommit returns a commit with its diff against its first parent (the empty tree for a root commit), read in one pass of
git diff-tree -r -M -z --raw --numstat -p <parent> <commit>
which prints the changed files, then their line counts, then the patch. Statistics come from the line counts, binary files from their "-" counts. The patch is parsed as it streams: nothing beyond the limits is kept, and git is stopped once nothing more is needed.
func (*Git) GetTree ¶
func (g *Git) GetTree(ctx context.Context, repo domain.RepoName, rev domain.Revision, path domain.TreePath, includeLast bool) (domain.Tree, error)
GetTree lists a directory with `git ls-tree -z -l`.
func (*Git) HTTPBackend ¶
HTTPBackend resolves git-http-backend through `git --exec-path` of the configured git (once), and builds its environment: the isolated one, GIT_PROJECT_ROOT=<data>/repos, GIT_HTTP_EXPORT_ALL and the configuration passed by GIT_CONFIG_COUNT/KEY/VALUE, so that nothing is written to the repositories:
core.hooksPath=<data>/run/hooks receive.fsckObjects=true uploadpack.allowFilter=true http.receivepack=true (push only) receive.maxInputSize=<max> (push only, when configured)
A push also gets REMOTE_USER=owner.
func (*Git) HasBranch ¶
HasBranch checks refs/heads/<branch> with `git for-each-ref` after `git check-ref-format`, which rejects the glob characters for-each-ref would otherwise interpret.
func (*Git) HasCommit ¶ added in v0.2.0
func (g *Git) HasCommit(ctx context.Context, repo domain.RepoName, id domain.ObjectID) (bool, error)
HasCommit asks `cat-file` for the type of the object.
func (*Git) HeadBranch ¶
HeadBranch reads the HEAD file of the bare repository (files ref backend). It is empty when HEAD is detached.
func (*Git) IsAncestor ¶ added in v0.2.0
func (g *Git) IsAncestor(ctx context.Context, repo domain.RepoName, ancestor, descendant domain.ObjectID) (bool, error)
IsAncestor runs `git merge-base --is-ancestor`, which exits 1 for "no".
func (*Git) List ¶
List returns the repository directories. Entries that are not "<valid name>.git" directories are reported as invalid; .staging is not an entry.
func (*Git) ListCommits ¶
func (g *Git) ListCommits(ctx context.Context, repo domain.RepoName, q ports.CommitQuery) (ports.CommitPage, error)
ListCommits pages through `git log`, which stops after Skip+Limit+1 commits.
func (*Git) ListRange ¶ added in v0.2.0
func (g *Git) ListRange(ctx context.Context, repo domain.RepoName, exclude, head domain.ObjectID, limit int) ([]domain.Commit, bool, error)
ListRange runs `git log <head> ^<exclude>`.
func (*Git) ListRefs ¶
ListRefs lists HEAD, the branches and the tags with `git for-each-ref`. An annotated tag is followed to its commit; a tag of a tag is followed with one `cat-file` request each.
func (*Git) MergeBase ¶ added in v0.2.0
func (g *Git) MergeBase(ctx context.Context, repo domain.RepoName, a, b domain.ObjectID) (domain.ObjectID, error)
MergeBase runs `git merge-base`, which exits 1 when there is none.
func (*Git) OpenBlob ¶
func (g *Git) OpenBlob(ctx context.Context, repo domain.RepoName, rev domain.Revision, p domain.TreePath) (domain.BlobInfo, io.ReadCloser, error)
OpenBlob describes the file at path and returns its content as a stream.
A blob up to 256 KiB is read through the repository's long-lived cat-file process. A larger one gets a `git cat-file blob` process that lives until the reader is closed, or ctx ends, or the adapter is closed; the memory used is a few buffers, whatever the size. The reader fails with io.ErrUnexpectedEOF if git stops before the announced size.
func (*Git) Publish ¶
Publish renames the staged repository to repos/<name>.git without replacing anything.
func (*Git) PurgeStaging ¶
PurgeStaging removes everything under repos/.staging/.
func (*Git) ResolveRefPath ¶
func (g *Git) ResolveRefPath(ctx context.Context, repo domain.RepoName, path string) (domain.RefPath, error)
ResolveRefPath tries the leading segments of path as a revision (branch, tag, full ref name, HEAD; a single segment may also be a commit id), longest first, with up to one `cat-file` request per candidate on the repository's process. At most 32 segments are tried.
func (*Git) ResolveRevision ¶
func (g *Git) ResolveRevision(ctx context.Context, repo domain.RepoName, rev domain.Revision) (domain.ResolvedRevision, error)
ResolveRevision resolves a revision with `cat-file`.
func (*Git) SetDefaultBranch ¶
SetDefaultBranch runs `git symbolic-ref HEAD refs/heads/<branch>`.
func (*Git) Stage ¶
Stage runs `git init --bare` in a new directory under repos/.staging/ and sets the repository defaults. The initial branch is main.
func (*Git) UpdateBranch ¶ added in v0.2.0
func (g *Git) UpdateBranch(ctx context.Context, repo domain.RepoName, branch string, old, newID domain.ObjectID) error
UpdateBranch runs `git update-ref refs/heads/<branch> <new> <old>`, which takes the ref lock and refuses when the ref is not at old. A failure is told from a moved branch by reading the branch again.
type Version ¶
type Version struct{ Major, Minor, Patch int }
Version is a git release number.
func ParseVersion ¶
ParseVersion parses the output of `git version`, such as "git version 2.45.0", "git version 2.39.5 (Apple Git-154)" or "git version 2.46.0.rc1".