Trees, history & diff
Tree primitives
Section titled “Tree primitives”upsertTreeupsertTree(repo: Repo, treeOid: string | undefined, entries: Map<string, string>): Promise<string>| Parameter | Type | Description |
|---|---|---|
treeOid | string | undefined | The existing root tree, or undefined to start empty. |
entries | Map<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.
deleteFromTreedeleteFromTree(repo: Repo, treeOid: string, filePath: string): Promise<string>Returns: Promise<string>
Removes one path from a tree, returning the new root tree oid.
findTreeEntryfindTreeEntry(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.
listTreeEntrieslistTreeEntries(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.
getTreeFromRefgetTreeFromRef(repo: Repo, options: { ref, treePath? }, hooks?: OpsHooks): Promise<TreeEntry[]>| Parameter | Type | Description |
|---|---|---|
options.ref | string | Branch name or commit sha. A ref that doesn't resolve throws GitRefNotFoundError. |
options.treePath? | string | Defaults 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.
History: getCommitLog
Section titled “History: getCommitLog”getCommitLoggetCommitLog(repo: Repo, options: CommitLogOptions, hooks?: OpsHooks): Promise<CommitInfo[]>| Parameter | Type | Description |
|---|---|---|
options.ref | string | Branch name or commit sha. An unborn branch yields []. |
options.depth? | number | Defaults to 50. |
options.knownHeadSha? | string | Pass 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”getCommitHistorygetCommitHistory(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.
File history & last-commits-per-directory
Section titled “File history & last-commits-per-directory”getFileHistorygetFileHistory(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.
getLastCommitsForTreegetLastCommitsForTree(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.
getCommitDiffgetCommitDiff(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.
getDiffBetweenRefsgetDiffBetweenRefs(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.