Documentation
¶
Overview ¶
Package git wraps the git plumbing commands we need to build the optional initial commit on a freshly-init'd bare repo. Plumbing-only, no working tree: hash-object → update-index (with GIT_INDEX_FILE) → write-tree → commit-tree → update-ref. This is what `git` itself does internally; we drive it from outside via short-lived subprocesses so we don't need to vendor a Go-native git implementation.
Every shell-out is constrained to a caller-supplied gitDir we control (`storage.RepoFS.RepoPath` produces it from a strict path whitelist). `git -C` is used instead of changing the process working directory.
Index ¶
- Variables
- func AheadBehind(ctx context.Context, gitDir, base, head string) (ahead, behind int, err error)
- func AheadBehindCacheStats() lru.Stats
- func CountCommits(ctx context.Context, gitDir, ref string) (int, error)
- func FetchIntoNamespace(ctx context.Context, dstRepoDir, srcRepoDir, srcRef, dstRef string) error
- func HasAnyBranch(ctx context.Context, gitDir string) (bool, error)
- func InvalidateAheadBehind(key AheadBehindKey)
- func InvalidateAheadBehindForRepo(repoID int64)
- func IsAncestor(ctx context.Context, gitDir, a, b string) (bool, error)
- func ListAllPaths(ctx context.Context, gitDir, ref string) ([]string, error)
- func ReadBlobBytes(ctx context.Context, gitDir, ref, path string, maxBytes int64) ([]byte, error)
- func ResolveRef(refs []string, segments []string) (ref, path string, ok bool)
- func ResolveRefOID(ctx context.Context, gitDir, ref string) (string, error)
- func SetSymbolicRef(ctx context.Context, gitDir, ref, target string) error
- func StreamBlob(ctx context.Context, gitDir, ref, path string, w io.Writer) error
- func UpdateRefCAS(ctx context.Context, gitDir, ref, newOID, oldOID string) error
- func WeeklyCommitActivity(ctx context.Context, gitDir, ref string, bucketCount int, now time.Time) ([]int, error)
- type AheadBehindKey
- type AheadBehindResult
- type BlameChunk
- type BlameLine
- type BlameOptions
- type BlobInfo
- type Commit
- type CommitDetail
- type FileChange
- type FileEntry
- type HeadCommit
- type InitialCommit
- type LogOptions
- type MergeOptions
- type MergeResult
- type MergeTreeResult
- type PRFileChange
- type RefEntry
- type RefListing
- type TreeBlob
- type TreeEntry
- type TreeEntryKind
Constants ¶
This section is empty.
Variables ¶
var ErrBlameOnBinary = errors.New("git: blame target is binary")
ErrBlameOnBinary is returned when the requested path is a binary blob. Blame on binary is meaningless; refuse early.
var ErrBlameTooLarge = errors.New("git: blame target too large")
ErrBlameTooLarge is returned when the file exceeds the cap. The handler renders the "blame too expensive" placeholder.
var ErrBlobTooLarge = errors.New("git: blob exceeds size cap")
ErrBlobTooLarge is returned when a maxBytes cap is hit on ReadBlobBytes.
var ErrCommitNotFound = errors.New("git: commit not found")
ErrCommitNotFound is returned by GetCommit when the SHA doesn't resolve to a commit on this repo.
var ErrNotABlob = errors.New("git: not a blob")
ErrNotABlob is the inverse — used by ReadBlob.
var ErrNotATree = errors.New("git: not a tree")
ErrNotATree is returned by LsTree when the requested path turns out to be a blob (so the caller should fall through to BlobInfo).
var ErrPathNotFound = errors.New("git: path not found")
ErrPathNotFound is for paths that don't exist on the ref.
var ErrRefNotFound = errors.New("git: ref not found")
ErrRefNotFound is returned when git can't resolve a ref or commit. Distinguished from generic exec failures so handlers can render a 404-leaning response.
var ErrRefRaced = errors.New("repogit: ref moved concurrently")
ErrRefRaced is the typed sentinel UpdateRefCAS returns when the ref moved between our read and our update.
Functions ¶
func AheadBehind ¶
AheadBehind returns the number of commits unique to head (ahead) and unique to base (behind), computed via `git rev-list --left-right --count base...head`. Output shape is "<behind>\t<ahead>" — the left side is base, right is head.
When base or head doesn't exist on the repo we surface the typed ErrRefNotFound so callers can render "—" instead of a number.
func AheadBehindCacheStats ¶
AheadBehindCacheStats exposes hit/miss counters for the /metrics surface and bench reports.
func CountCommits ¶
CountCommits returns the number of commits reachable from ref.
func FetchIntoNamespace ¶
FetchIntoNamespace fetches a single ref from `srcRepoDir` into `dstRepoDir` under the supplied refspec. The dst-side ref name is the second half of the refspec. Used by S27 cross-fork PR support to pull a fork's head branch into the base repo's `refs/shithub-pr/<pr_id>/head` namespace (private — never advertised via `info/refs`).
Idempotent at the git layer; calling repeatedly with the same refspec just updates the dst ref.
func HasAnyBranch ¶
HasAnyBranch reports whether the bare repo at gitDir has at least one ref under refs/heads/. Used by the repo home view to fork between the "quick setup" empty-state and the post-push view.
func InvalidateAheadBehind ¶
func InvalidateAheadBehind(key AheadBehindKey)
InvalidateAheadBehind drops one specific (repo, base, head) entry. Use from push:process when the exact OIDs are known.
func InvalidateAheadBehindForRepo ¶
func InvalidateAheadBehindForRepo(repoID int64)
InvalidateAheadBehindForRepo drops every cached entry whose key matches repoID. Called from push:process so a force-push that rewrites history doesn't surface stale ahead/behind counts.
The current implementation is approximate: the LRU exposes Delete per-key, not by-prefix scan. Push handlers that know the specific OIDs that moved should call InvalidateAheadBehind directly; this helper is kept as a documented future extension point so the API is stable when a richer scan lands.
func IsAncestor ¶
IsAncestor reports whether commit a is an ancestor of commit b. Used by the pre-receive force-push detector: a fast-forward is `IsAncestor(old, new)`.
func ListAllPaths ¶
ListAllPaths runs `git ls-tree -r --name-only` and returns every blob path under the ref. Used by the "Go to file" finder. Filters out submodule-style entries (commit type) which shouldn't surface in the file finder.
func ReadBlobBytes ¶
ReadBlobBytes reads the entire blob at `<ref>:<path>`. Caller-imposed max-size limit is the right guard — git itself doesn't bound the stream. Pass 0 for "no cap"; otherwise an oversize read returns ErrBlobTooLarge.
func ResolveRef ¶
ResolveRef takes the URL segments after /tree/ or /blob/ and finds the longest-prefix match against the supplied refs. Hex-SHA shortcut: if the first segment is exactly 40 hex chars, treat it as a SHA. The returned `path` is the joined remainder after the matched ref (no leading slash).
Example: refs = ["main", "feature/x"], URL = ["feature", "x", "sub", "f.go"] → ref="feature/x", path="sub/f.go".
func ResolveRefOID ¶
ResolveRefOID returns the full SHA for `ref` via `git rev-parse`. Returns ErrRefNotFound when git can't resolve.
func SetSymbolicRef ¶
SetSymbolicRef updates HEAD (or any other symbolic ref) atomically. Used by the default-branch change to point HEAD at the new branch.
func StreamBlob ¶
StreamBlob writes the blob bytes to w. For raw downloads we never buffer; this lets the response stream as `git cat-file -p` produces.
func UpdateRefCAS ¶
UpdateRefCAS performs an atomic compare-and-swap on a ref: only succeeds if the ref currently points at oldOID. Returns ErrRefRaced when the ref moved underneath us (a concurrent push is the canonical case). Used by S27's sync-fork to fast-forward the fork's default branch only when nothing else has touched it.
The git update-ref `<oldvalue>` argument is what enforces the CAS; passing it gives git's exact-match semantics (no off-by-one races even when oldvalue happens to equal newvalue).
func WeeklyCommitActivity ¶
func WeeklyCommitActivity(ctx context.Context, gitDir, ref string, bucketCount int, now time.Time) ([]int, error)
WeeklyCommitActivity counts commits by UTC week, oldest bucket first. It is intentionally small and read-only so list surfaces can render GitHub-style activity sparklines without parsing full commit rows.
Types ¶
type AheadBehindKey ¶
AheadBehindKey is the (repo_id, base_oid, head_oid) tuple. The OIDs make the cache safe across pushes — when either ref moves, the new OID forms a different key. Old keys age out via LRU eviction; explicit invalidation isn't strictly required but helps keep the working set small (push handlers call InvalidateRepo).
type AheadBehindResult ¶
type AheadBehindResult struct {
Ahead, Behind int
}
AheadBehindResult is the cached payload.
func AheadBehindCached ¶
func AheadBehindCached(ctx context.Context, gitDir string, key AheadBehindKey) (AheadBehindResult, error)
AheadBehindCached is the cached + single-flighted variant of AheadBehind. Callers pass the resolved OIDs (not ref names) so the key is stable across ref-name renames and the cache is never poisoned by a stale ref pointer.
On cache miss the underlying `git rev-list` runs once even when many requests arrive concurrently for the same key (single-flight dogpile guard). The result is cached until LRU eviction or explicit InvalidateRepo.
type BlameChunk ¶
type BlameChunk struct {
OID string
ShortOID string
AuthorName string
AuthorEmail string
AuthorWhen time.Time
StartLine int // first FinalLineNo in the chunk
EndLine int
Lines []BlameLine
}
BlameChunk groups consecutive lines that share an originating commit. The handler renders each chunk with one header (commit/author/age) + N body rows.
func Blame ¶
func Blame(ctx context.Context, gitDir string, opts BlameOptions) ([]BlameChunk, error)
Blame runs `git blame --line-porcelain` and returns chunks. The caller groups by chunk for rendering. We size-check the blob via StatPath first so a giant file fails fast without forking blame.
type BlameLine ¶
type BlameLine struct {
OID string
ShortOID string
AuthorName string
AuthorEmail string
AuthorWhen time.Time
OrigLineNo int // line number in the originating commit
FinalLineNo int // line number in the final file (1-indexed)
Content string // the source line, no trailing newline
}
BlameLine is one source line + the commit that last touched it.
type BlameOptions ¶
type BlameOptions struct {
Ref string
Path string
// MaxBytes / MaxLines are the safety caps. 0 → use defaults
// (5 MiB / 50k lines per S18 spec).
MaxBytes int64
MaxLines int
}
BlameOptions configures Blame. Currently just the file ref + path, but the shape leaves room for "older" navigation in a follow-up.
type Commit ¶
type Commit struct {
OID string // full 40-char SHA
ShortOID string // git's --abbrev result, typically 7 chars
AuthorName string
AuthorEmail string
AuthorWhen time.Time
Subject string
Body string // empty for list view; populated by GetCommit
}
Commit is one commit row from the commits list. Only the cheap fields arrive via `git log --format=...`; the per-file stats live on CommitDetail.
func CommitsBetween ¶
CommitsBetween returns the commits unique to head (the right side of the symmetric range). Used by the compare view's commits list.
type CommitDetail ¶
type CommitDetail struct {
Commit
CommitterName string
CommitterEmail string
CommitterWhen time.Time
Parents []string // each is a full OID
TreeOID string
Files []FileChange
}
CommitDetail holds the single-commit-view data: the Commit plus committer fields, parents, tree OID, and the per-file change list.
func CommitsBetweenDetail ¶
func CommitsBetweenDetail(ctx context.Context, gitDir, baseOID, headOID string, max int) ([]CommitDetail, error)
CommitsBetweenDetail returns commits unique to head, with author + committer + body, suitable for refreshing pull_request_commits. The result preserves head-side oldest-first ordering by inverting log's default newest-first via --reverse.
func GetCommit ¶
func GetCommit(ctx context.Context, gitDir, sha string) (CommitDetail, error)
GetCommit returns the full detail for one SHA. Two commands: one for the commit metadata + parents (cheap, single-line), one for the file stats. Combining them in one --pretty walk would save a fork but complicate parsing.
type FileChange ¶
type FileChange struct {
Status string
Path string
OldPath string // populated for R/C
Insert int
Delete int
Binary bool
}
FileChange is one row from `git diff-tree --numstat --no-commit-id -r`. Status is git's letter code: A added, M modified, D deleted, R renamed, C copied, T type-changed.
func DiffStat ¶
func DiffStat(ctx context.Context, gitDir, sha string) ([]FileChange, error)
DiffStat returns the per-file change list for a SHA. We run two commands: --name-status for the letter and rename pairs, --numstat for +/- counts. Two passes is two forks, but the parsing stays simple; combining via --raw is harder to read.
type FileEntry ¶
FileEntry is one file destined for the initial commit. Path is the repository-relative slash-separated path (no leading slash). Body is the raw bytes; we hash whatever we're given without normalization.
type HeadCommit ¶
type HeadCommit struct {
OID string
Subject string
AuthorName string
AuthorEmail string
AuthorWhen time.Time
}
HeadCommit is a read-only view of one commit. Returned by HeadOf for the repo home page; richer commit-info reads belong to S17.
type InitialCommit ¶
type InitialCommit struct {
GitDir string // absolute bare-repo path (must already exist + be inited)
AuthorName string
AuthorEmail string
Message string // defaults to "Initial commit"
Branch string // defaults to "trunk"
When time.Time // defaults to time.Now()
Files []FileEntry
}
InitialCommit describes the single commit produced for a freshly initialized repo whose owner ticked "initialize with README/license/ .gitignore" on the create form. Everything except Files defaults to sensible values; callers must always set GitDir + Author* + Files.
type LogOptions ¶
type LogOptions struct {
Ref string
MaxCount int
Skip int
Path string
Author string
Since time.Time
Until time.Time
Follow bool // --follow; only meaningful when Path is set
}
LogOptions tunes the commits-list query. Zero values are sensible:
- MaxCount 0 → 30
- Skip 0 → 0
- Path "" → log over all paths
- Author "" → no author filter
- Since/Until zero → no date filter
type MergeOptions ¶
type MergeOptions struct {
GitDir string
BaseRef string // e.g. "refs/heads/trunk"
BaseOID string
HeadOID string
Method string // "merge" | "squash" | "rebase"
AuthorName string
AuthorEmail string
CommitterName string
CommitterEmail string
When time.Time
Subject string
Body string
WorktreesDir string // parent dir for the temp worktree (must share volume with GitDir)
}
MergeOptions configures a worktree-based merge.
type MergeResult ¶
type MergeResult struct {
NewBaseOID string // the new tip of base_ref after the merge
MergedOID string // for "merge" method: the merge commit; "squash"/"rebase": same as NewBaseOID
}
MergeResult is what PerformMerge returns on success.
func PerformMerge ¶
func PerformMerge(ctx context.Context, opts MergeOptions) (MergeResult, error)
PerformMerge executes the requested merge strategy in a temp worktree rooted at WorktreesDir. The worktree is removed on every exit path (success or failure). Returns the new base-ref tip.
type MergeTreeResult ¶
MergeTreeResult captures the output of `git merge-tree --write-tree --merge-base=<base> <base> <head>`. ConflictPaths is empty when the merge is clean. Git ≥ 2.38 required.
func ProbeMerge ¶
func ProbeMerge(ctx context.Context, gitDir, baseOID, headOID string) (MergeTreeResult, error)
ProbeMerge runs `git merge-tree --write-tree --no-messages <base> <head>` and lets git auto-compute the merge base. Exit 0 = clean merge (TreeOID set); exit 1 = conflicts (ConflictPaths populated). Anything else is wrapped.
type PRFileChange ¶
type PRFileChange struct {
Path string
OldPath string
Status string // "added" | "modified" | "deleted" | "renamed" | "copied"
Additions int
Deletions int
}
PRFileChange is a per-file row for pull_request_files. Status mirrors the migration enum; OldPath is non-empty for renames + copies.
func FilesChangedBetween ¶
func FilesChangedBetween(ctx context.Context, gitDir, baseOID, headOID string) ([]PRFileChange, error)
FilesChangedBetween returns the change set head-side, computed as `git diff --name-status --numstat <base>...<head>` (three-dot: changes from merge-base to head). Status is git's letter code, renames carry the old path as the second column.
type RefEntry ¶
type RefEntry struct {
Name string // short name (without refs/heads/ or refs/tags/)
OID string // 40-char hex sha
}
RefEntry is one ref from for-each-ref.
type RefListing ¶
type RefListing struct {
Branches []RefEntry // refs/heads/<name>
Tags []RefEntry // refs/tags/<name>
}
ListRefs runs `git for-each-ref` and returns every ref under refs/heads/ and refs/tags/. Used by the ref-resolver to do longest-prefix matching against URLs like /tree/feature/x/sub/dir.
Returns separate lists so callers can fork on UX (branches vs tags).
type TreeBlob ¶
TreeBlob is one blob returned by a recursive tree walk.
type TreeEntry ¶
type TreeEntry struct {
Kind TreeEntryKind
Mode string // 100644, 100755, 040000, 160000, 120000
OID string
Size int64 // -1 when N/A (trees, submodules)
Name string // basename relative to the listed path
}
TreeEntry is one row from `git ls-tree --long --full-tree`.
type TreeEntryKind ¶
type TreeEntryKind string
TreeEntryKind is one of the four shapes git tree entries take.
const ( EntryTree TreeEntryKind = "tree" EntryBlob TreeEntryKind = "blob" EntrySubmod TreeEntryKind = "commit" // a "commit" entry in a tree is a submodule pointer EntrySymlink TreeEntryKind = "symlink" )