Skip to content

Trees, history & diff

functionupsertTree
upsertTree(repo: Repo, treeOid: string | undefined, entries: Map<string, string>): Promise<string>
ParameterTypeDescription
treeOidstring | undefinedThe existing root tree, or undefined to start empty.
entriesMap<string, string>relativePath → blobOid.

Returns: Promise<string>

Builds/updates a tree by overlaying blobs onto an existing one, returning the new root tree oid. Handles nested paths — intermediate subtrees are created or updated as needed. Sibling subdirectories are built in parallel.

deleteFromTree(repo: Repo, treeOid: string, filePath: string): Promise<string>

Returns: Promise<string>

Removes one path from a tree, returning the new root tree oid.

findTreeEntry(repo: Repo, rootTreeOid: string, treePath: string): Promise<TreeEntry | null>

Returns: Promise<TreeEntry | null>

Resolves a path inside a tree to its entry ({ path, mode, type, oid }), or null if it doesn’t exist. An empty treePath returns the root tree itself.

listTreeEntries(repo: Repo, treeOid: string, prefix?: string): Promise<TreeEntry[]>

Returns: Promise<TreeEntry[]>

A tree’s direct children, with path prefixed by prefix if given — used to build result entries whose keys are already the full path a caller expects.

getTreeFromRef(repo: Repo, options: { ref, treePath? }, hooks?: OpsHooks): Promise<TreeEntry[]>
ParameterTypeDescription
options.refstringBranch name or commit sha. A ref that doesn't resolve throws GitRefNotFoundError.
options.treePath?stringDefaults to the repo root.

Returns: Promise<TreeEntry[]>

A directory listing at a ref’s tip — resolves the ref to a commit, then treePath inside that commit’s tree. Results are memoized per resolved head sha, same pattern as getCommitLog below.

functiongetCommitLog
getCommitLog(repo: Repo, options: CommitLogOptions, hooks?: OpsHooks): Promise<CommitInfo[]>
ParameterTypeDescription
options.refstringBranch name or commit sha. An unborn branch yields [].
options.depth?numberDefaults to 50.
options.knownHeadSha?stringPass this when the caller already resolved ref — skips a redundant resolveRef.

Returns: Promise<CommitInfo[]>

The commit chain from a ref, newest first. ref not resolving (empty repo, unborn branch) returns []; a commit the walk needs but can’t read after ref already resolved throws instead — see empty repo vs. missing data.

getCommitHistory: pagination on top of the log cache

Section titled “getCommitHistory: pagination on top of the log cache”
getCommitHistory(repo: Repo, options: { ref, limit?, skip? }, hooks?: OpsHooks): Promise<CommitInfo[]>

Returns: Promise<CommitInfo[]>

A page of commit history (limit/skip): a slice of getCommitLog’s memoized walk, so paging never re-walks what an earlier page already walked. [] when the ref doesn’t exist.

getFileHistory(repo: Repo, options: { ref, filePath, limit?, maxDepth? }, hooks?: OpsHooks): Promise<FileHistoryResult>

Returns: Promise<FileHistoryResult>

All commits (newest first) that changed one file’s blob oid, walking the first-parent chain. { entries, truncated } — truncated: true means the walk hit its depth budget (or the requested limit) before exhausting the branch’s full commit chain; there may be older commits touching this file a deeper walk would surface.

getLastCommitsForTree(repo: Repo, options: { ref, treePath?, depth? }, hooks?: OpsHooks): Promise<Record<string, LastCommitInfo>>

Returns: Promise<Record<string, CommitSummary>>

For each direct child of treePath, the most recent commit that changed it — the tree-view “last commit” column GitHub-style UIs show next to every file and folder.

Both walk history in fixed-size windows, prefetching each window’s tree reads in parallel before processing that window sequentially — the walk itself must advance commit-by-commit for correctness (each step needs to know what’s still unresolved from the step before), but the tree-object reads backing it don’t depend on each other, since every commit’s tree oid is already known upfront from the commit log. Preserve this two-phase structure if you ever need to touch this algorithm — collapsing it back to one-commit-at-a-time turns a batched-round-trip walk back into a round-trip-per-commit one, which is the single largest contributor to slow directory listings on large repos.

getCommitDiff(repo: Repo, commitSha: string): Promise<DiffResult>

Returns: Promise<DiffResult>

The diff a single commit introduced, against its first parent. For a root commit (no parent), every file in the tree is reported as added — the whole tree is walked recursively, not just its top level.

getDiffBetweenRefs(repo: Repo, baseRef: string, compareRef: string): Promise<DiffResult>

Returns: Promise<DiffResult>

What compareRef changed since it diverged from baseRef — a pull request’s diff, git’s three-dot base...compare: compared against their merge base, so commits that landed on baseRef after the branch point don’t show up as reverts. Branch names or commit shas are accepted. DiffResult is { files: DiffFile[], totalAdditions, totalDeletions, totalFiles }; each DiffFile carries a git-style unified patch (diff --git a/... b/... header, no leaking diff package internals like an Index:/=== block) when kind is "text". Binary files (kind: "binary") and text over MAX_DIFF_TEXT_BYTES (kind: "too-large") carry no patch and no content — only oldSize/newSize; fetch either side by path at its ref if it needs displaying.

The too-large decision is made before anything is read: each side’s size comes from its pack entry (getObjectSize), so a file over MAX_DIFF_TEXT_BYTES is reported as too-large without ever being inflated. Blob reads and size probes share one semaphore (MAX_CONCURRENT_READS), so a wide commit can’t fan out into one object-store request per entry. When a side’s size isn’t available — a loose object, or a store without getRange — the blob is read and the same limit is applied afterwards.