App-layer operations overview
The higher-level git-hosting operations layer built on createGitFs, and the OpsHooks seam every function shares.
git-fs-s3/ops is the layer a repo browser, a PR flow, or a
CMS admin UI actually needs — branches, commit writing, tree read/write,
commit history, file history, diffs, and merge — built entirely on the root
package's createGitFs. Extracted from a production git-hosting service's
own application layer.
Branches
listBranches, createBranchFrom, deleteBranchByName,
assertBranchExists. See
Branches & commits.
Commits
commitFilesToBare, deleteFileFromBare, writeCommitToBare,
authorNow. See
Branches & commits.
Trees, history & diff
Tree primitives, cached commit-log walks, file history, diffs. See Trees, history & diff.
Merge
Fast-forward and pre-merge analysis. See Merge.
OpsHooks: the shared seam
Every function in this module accepts an optional hooks: OpsHooks as its
last argument:
interface OpsHooks {
resultCache?: ResultCache;
/** Wrap a timed sub-step (network walk, tree listing). Default: run directly. */
step?<T>(label: string, fn: () => Promise<T>): Promise<T>;
/** Diagnostic sink for cache hit/miss and walk summaries. */
onNote?(message: string): void;
/**
* Warm every pack (e.g. GitFs.prefetchPacks) before a cache-miss read, so
* a sequential walk doesn't discover packs one round trip at a time.
*/
prefetch?(): Promise<void>;
}A typical wiring, reused across every call:
const hooks: OpsHooks = {
resultCache: myAppResultCache, // see ResultCache below
step: perfStep,
onNote: (msg) => console.debug(msg),
prefetch: () => (repo.fs as GitFs).prefetchPacks(repo.gitdir),
};ResultCache
Several functions in this module — getCommitLog (and getCommitHistory
on top of it), getTreeFromRef, getLastCommitsForTree, getFileHistory — memoize
expensive walk results (a commit log, a tree listing) so repeated or
overlapping requests don't re-walk from scratch:
interface ResultCache {
get<T extends object>(key: string): T | null | undefined;
set(key: string, value: object): void;
}Keys are namespaced <kind>:<gitdir>:..., so entries self-invalidate on a
new push (a new head sha means a new key) — but you still need to evict
entries under resultKeyPrefixes(gitdir)
after rewriting a repo's storage out of band (a bulk sync, a rename), or a
stale walk outlives the history it describes.
Get the cache key namespace right
These keys are namespaced by gitdir, not by any app-level identifier you
might otherwise use (an owner/repo pair, a database ID). If your own
invalidation logic uses a different prefix scheme than
resultKeyPrefixes(gitdir) produces, invalidation silently does nothing
— the prefixes never match, and stale results linger past the point they
should have been evicted. Always derive invalidation prefixes from
resultKeyPrefixes, never hand-roll them.
Any object satisfying the two-method interface works — an LRU, a Map, a
Redis client wrapper. get/set are called with plain values (arrays,
objects); the store owns how it actually keeps them.
Repo
Every function takes a Repo as its first argument — the same shape
createGitFs produces:
interface Repo {
fs: IsoGitFs;
gitdir: string;
cache?: object; // isomorphic-git's own shared parse cache — recommended per repo
}Create one per request and reuse it across every /ops call in that
request — sharing cache is what lets isomorphic-git reuse parsed pack
indexes instead of re-parsing them on every call.