git-fs-s3 0.12.1 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/dist/chunk-RJYNJOYF.js +598 -0
- package/dist/chunk-RJYNJOYF.js.map +1 -0
- package/dist/http.js +1 -1
- package/dist/ops.js +41 -311
- package/dist/ops.js.map +1 -1
- package/dist/types/ops/branch.d.ts +9 -0
- package/dist/types/ops/history.d.ts +2 -0
- package/dist/types/ops/index.d.ts +2 -2
- package/package.json +3 -3
- package/dist/chunk-QH3HS7VI.js +0 -256
- package/dist/chunk-QH3HS7VI.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ops/tree.ts","../src/ops/types.ts","../src/ops/history.ts","../src/ops/branch.ts","../src/pack-write.ts","../src/semaphore.ts"],"sourcesContent":["import git from \"isomorphic-git\";\nimport { GitInvalidRequestError, GitPathNotFoundError } from \"../git-errors.js\";\nimport type { Repo } from \"./types.js\";\n\nexport interface TreeEntry {\n\tpath: string;\n\tmode: string;\n\ttype: \"blob\" | \"tree\" | \"commit\";\n\toid: string;\n\tsize?: number;\n}\n\nconst joinPath = (prefix: string, name: string) =>\n\tprefix ? `${prefix.replace(/\\/+$/, \"\")}/${name}` : name;\n\n/** File modes a blob entry can carry; anything else isn't a regular file. */\nconst BLOB_MODES = new Set([\"100644\", \"100755\", \"120000\"]);\n\n/**\n * Build/update a git tree by overlaying new blobs onto an existing tree,\n * returning the new root tree oid. `entries` maps relative paths to blob oids.\n *\n * An updated file keeps its existing mode (executable bit, symlink); a path\n * that names an existing directory, or runs through an existing file, is\n * rejected rather than silently replacing what's there.\n */\nexport async function upsertTree(\n\trepo: Repo,\n\ttreeOid: string | undefined,\n\tentries: Map<string, string>,\n): Promise<string> {\n\tconst existing = treeOid\n\t\t? (await git.readTree({ ...repo, oid: treeOid })).tree\n\t\t: [];\n\tconst byName = new Map(existing.map((e) => [e.path, e]));\n\tconst direct = new Map<string, string>();\n\tconst nested = new Map<string, Map<string, string>>();\n\tfor (const [filePath, blobOid] of entries) {\n\t\tconst slash = filePath.indexOf(\"/\");\n\t\tif (slash === -1) {\n\t\t\tdirect.set(filePath, blobOid);\n\t\t} else {\n\t\t\tconst dir = filePath.slice(0, slash);\n\t\t\tconst rest = filePath.slice(slash + 1);\n\t\t\tif (!nested.has(dir)) nested.set(dir, new Map());\n\t\t\tnested.get(dir)?.set(rest, blobOid);\n\t\t}\n\t}\n\tfor (const [name, blobOid] of direct) {\n\t\tconst current = byName.get(name);\n\t\tif (current && current.type !== \"blob\") {\n\t\t\tthrow new GitInvalidRequestError(`\"${name}\" is a directory, not a file`);\n\t\t}\n\t\tbyName.set(name, {\n\t\t\tmode: current && BLOB_MODES.has(current.mode) ? current.mode : \"100644\",\n\t\t\tpath: name,\n\t\t\toid: blobOid,\n\t\t\ttype: \"blob\",\n\t\t});\n\t}\n\t// Sibling subdirectories are independent subtree writes — no reason to\n\t// serialize them for multi-file commits touching several directories.\n\tconst nestedResults = await Promise.all(\n\t\tArray.from(nested, async ([dir, subEntries]) => {\n\t\t\tconst entry = byName.get(dir);\n\t\t\tif (entry && entry.type !== \"tree\") {\n\t\t\t\tthrow new GitInvalidRequestError(`\"${dir}\" is a file, not a directory`);\n\t\t\t}\n\t\t\tconst subtreeOid = entry?.oid;\n\t\t\tconst newOid = await upsertTree(repo, subtreeOid, subEntries);\n\t\t\treturn [dir, newOid] as const;\n\t\t}),\n\t);\n\tfor (const [dir, newOid] of nestedResults) {\n\t\tbyName.set(dir, { mode: \"040000\", path: dir, oid: newOid, type: \"tree\" });\n\t}\n\treturn git.writeTree({ ...repo, tree: Array.from(byName.values()) });\n}\n\n/**\n * Remove a file path from a tree, returning the new root tree oid. Throws\n * GitPathNotFoundError when there's no such file; directories left empty\n * are dropped, as git never stores an empty subtree.\n */\nexport async function deleteFromTree(\n\trepo: Repo,\n\ttreeOid: string,\n\tfilePath: string,\n): Promise<string> {\n\tconst result = await deleteFromSubtree(repo, treeOid, filePath, filePath);\n\treturn result ?? git.writeTree({ ...repo, tree: [] });\n}\n\n/** The new subtree oid, or null when removing the file left it empty. */\nasync function deleteFromSubtree(\n\trepo: Repo,\n\ttreeOid: string,\n\tfilePath: string,\n\tfullPath: string,\n): Promise<string | null> {\n\tconst existing = (await git.readTree({ ...repo, oid: treeOid })).tree;\n\tconst byName = new Map(existing.map((e) => [e.path, e]));\n\tconst slash = filePath.indexOf(\"/\");\n\tconst name = slash === -1 ? filePath : filePath.slice(0, slash);\n\tconst entry = byName.get(name);\n\tif (slash === -1) {\n\t\tif (entry?.type !== \"blob\") {\n\t\t\tthrow new GitPathNotFoundError(`No such file: ${fullPath}`);\n\t\t}\n\t\tbyName.delete(name);\n\t} else {\n\t\tif (entry?.type !== \"tree\") {\n\t\t\tthrow new GitPathNotFoundError(`No such file: ${fullPath}`);\n\t\t}\n\t\tconst newOid = await deleteFromSubtree(\n\t\t\trepo,\n\t\t\tentry.oid,\n\t\t\tfilePath.slice(slash + 1),\n\t\t\tfullPath,\n\t\t);\n\t\tif (newOid === null) byName.delete(name);\n\t\telse byName.set(name, { ...entry, oid: newOid });\n\t}\n\tif (byName.size === 0) return null;\n\treturn git.writeTree({ ...repo, tree: Array.from(byName.values()) });\n}\n\n/** Resolve a path inside a tree to its entry, or null when absent. */\nexport async function findTreeEntry(\n\trepo: Repo,\n\trootTreeOid: string,\n\ttreePath: string,\n): Promise<TreeEntry | null> {\n\tif (!treePath) {\n\t\treturn { path: \"\", mode: \"040000\", type: \"tree\", oid: rootTreeOid };\n\t}\n\n\tconst parts = treePath.split(\"/\").filter(Boolean);\n\tlet currentTreeOid = rootTreeOid;\n\tlet currentPath = \"\";\n\n\tfor (const [index, part] of parts.entries()) {\n\t\tconst tree = await git.readTree({ ...repo, oid: currentTreeOid });\n\t\tconst entry = tree.tree.find((candidate) => candidate.path === part);\n\n\t\tif (!entry) return null;\n\n\t\tcurrentPath = currentPath ? joinPath(currentPath, entry.path) : entry.path;\n\n\t\tif (index === parts.length - 1) {\n\t\t\treturn {\n\t\t\t\tpath: currentPath,\n\t\t\t\tmode: entry.mode,\n\t\t\t\ttype: entry.type,\n\t\t\t\toid: entry.oid,\n\t\t\t};\n\t\t}\n\n\t\tif (entry.type !== \"tree\") return null;\n\n\t\tcurrentTreeOid = entry.oid;\n\t}\n\n\treturn null;\n}\n\n/** List a tree's direct entries, with paths prefixed by `prefix`. */\nexport async function listTreeEntries(\n\trepo: Repo,\n\ttreeOid: string,\n\tprefix = \"\",\n): Promise<TreeEntry[]> {\n\tconst tree = await git.readTree({ ...repo, oid: treeOid });\n\n\treturn tree.tree.map((entry) => ({\n\t\tpath: prefix ? joinPath(prefix, entry.path) : entry.path,\n\t\tmode: entry.mode,\n\t\ttype: entry.type,\n\t\toid: entry.oid,\n\t}));\n}\n","import type git from \"isomorphic-git\";\n\n/** The fs shape isomorphic-git accepts, derived from its own signatures. */\nexport type IsoGitFs = Parameters<typeof git.readTree>[0][\"fs\"];\n\n/**\n * A repository handle: everything isomorphic-git needs to address one bare\n * repo. Create it once per request (sharing `cache` across calls is what lets\n * isomorphic-git reuse parsed pack indexes) and pass it to every op.\n */\nexport interface Repo {\n\tfs: IsoGitFs;\n\tgitdir: string;\n\t/** isomorphic-git's shared parse cache — strongly recommended per repo. */\n\tcache?: object;\n}\n\n/**\n * Key/value store for memoizing expensive walk results (commit logs, tree\n * listings, per-path history). Keys are namespaced `kind:gitdir:headSha:…`,\n * so entries self-invalidate on push (new head, new key); evict\n * {@link resultKeyPrefixes} to drop a repo's superseded entries early.\n * Values are always objects (arrays or records).\n */\nexport interface ResultCache {\n\tget<T extends object>(key: string): T | null | undefined;\n\tset(key: string, value: object): void;\n}\n\n/** Optional instrumentation hooks accepted by every op. */\nexport interface OpsHooks {\n\tresultCache?: ResultCache;\n\t/** Wrap a timed sub-step (network walk, tree listing). Default: run directly. */\n\tstep?: <T>(label: string, fn: () => Promise<T>) => Promise<T>;\n\t/** Diagnostic sink for cache hit/miss and walk summaries. */\n\tonNote?: (message: string) => void;\n\t/**\n\t * Warm every pack (e.g. `GitFs.prefetchPacks`) before a cache-miss read,\n\t * so a sequential walk doesn't discover packs one round trip at a time.\n\t */\n\tprefetch?: () => Promise<void>;\n}\n\nexport const runStep = <T>(\n\thooks: OpsHooks | undefined,\n\tlabel: string,\n\tfn: () => Promise<T>,\n): Promise<T> => (hooks?.step ? hooks.step(label, fn) : fn());\n\n/**\n * The result-cache key prefixes holding entries for `gitdir` — evict these\n * from your {@link ResultCache} when the repo's storage was rewritten outside\n * a normal push (rename, repack).\n */\nexport function resultKeyPrefixes(gitdir: string): string[] {\n\treturn [\n\t\t`commitlog:${gitdir}:`,\n\t\t`tree:${gitdir}:`,\n\t\t`last-commits:${gitdir}:`,\n\t\t`file-history:${gitdir}:`,\n\t];\n}\n","import git from \"isomorphic-git\";\nimport {\n\tGitObjectNotFoundError,\n\tGitPathNotFoundError,\n\tGitRefNotFoundError,\n} from \"../git-errors.js\";\nimport { qualifyBranchRef } from \"../refs.js\";\nimport { findTreeEntry, listTreeEntries, type TreeEntry } from \"./tree.js\";\nimport { type OpsHooks, type Repo, runStep } from \"./types.js\";\n\n/** How far back history walks look by default (commit count). */\nexport const HISTORY_WALK_DEPTH = 400;\n\nexport interface GitIdentity {\n\tname: string;\n\temail: string;\n\ttimestamp: number;\n\ttimezoneOffset: number;\n}\n\nexport interface CommitInfo {\n\toid: string;\n\tcommit: {\n\t\tmessage: string;\n\t\ttree: string;\n\t\tparent: string[];\n\t\tauthor: GitIdentity;\n\t\tcommitter: GitIdentity;\n\t};\n\tpayload: string;\n}\n\n/** The display summary of a commit (history lists, \"last commit\" columns). */\nexport interface CommitSummary {\n\tsha: string;\n\tmessage: string;\n\tauthorName: string;\n\tauthorEmail: string;\n\t/** ISO 8601 author date. */\n\tcreatedAt: string;\n}\n\nexport function summarizeCommit(commit: CommitInfo): CommitSummary {\n\treturn {\n\t\tsha: commit.oid,\n\t\tmessage: commit.commit.message.trim(),\n\t\tauthorName: commit.commit.author.name,\n\t\tauthorEmail: commit.commit.author.email,\n\t\tcreatedAt: new Date(commit.commit.author.timestamp * 1000).toISOString(),\n\t};\n}\n\nconst isNotFound = (err: unknown) =>\n\t(err as { code?: string } | null)?.code === \"NotFoundError\";\n\n/**\n * A ref that doesn't resolve is an expected condition (empty repo, unborn\n * branch) each caller handles. An object missing *underneath* a resolved ref\n * means storage is inconsistent — raised as GitObjectNotFoundError so it is\n * never mistaken for \"empty\".\n */\nfunction wrapMissingObject<T>(\n\tpromise: Promise<T>,\n\tcontext: string,\n): Promise<T> {\n\treturn promise.catch((err: unknown) => {\n\t\tif (isNotFound(err)) {\n\t\t\tthrow new GitObjectNotFoundError(\n\t\t\t\t`Git data for ${context} is missing from storage`,\n\t\t\t);\n\t\t}\n\t\tthrow err;\n\t});\n}\n\n/** The commit a branch name, full ref or sha points at; GitRefNotFoundError if none. */\n/**\n * A short name tries `refs/heads/<name>` first and then `refs/tags/<name>`,\n * so a branch wins over a same-named tag. Annotated tags are peeled to the\n * commit they point at, so every read accepts a tag wherever it accepts a\n * branch.\n */\nasync function resolveRefOid(repo: Repo, ref: string): Promise<string> {\n\tconst qualified = qualifyBranchRef(ref);\n\tconst candidates =\n\t\tqualified === `refs/heads/${ref}`\n\t\t\t? [qualified, `refs/tags/${ref}`]\n\t\t\t: [qualified];\n\tfor (const candidate of candidates) {\n\t\ttry {\n\t\t\treturn await peelToCommit(\n\t\t\t\trepo,\n\t\t\t\tawait git.resolveRef({ ...repo, ref: candidate }),\n\t\t\t);\n\t\t} catch (err) {\n\t\t\tif (!isNotFound(err)) throw err;\n\t\t}\n\t}\n\tthrow new GitRefNotFoundError(`Ref ${ref} does not exist`);\n}\n\n/** Follow annotated tags (possibly nested) down to the object they name. */\nexport async function peelToCommit(repo: Repo, oid: string): Promise<string> {\n\tlet current = oid;\n\tfor (let depth = 0; depth < 10; depth++) {\n\t\tconst { type, object } = await git.readObject({\n\t\t\t...repo,\n\t\t\toid: current,\n\t\t\tformat: \"parsed\",\n\t\t});\n\t\tif (type !== \"tag\") return current;\n\t\tcurrent = (object as { object: string }).object;\n\t}\n\tthrow new GitRefNotFoundError(`Tag chain at ${oid} is too deep`);\n}\n\n/** Resolve a branch name / full ref / sha to its commit. */\nexport async function resolveCommit(repo: Repo, ref: string) {\n\tconst oid = await resolveRefOid(repo, ref);\n\tconst { commit } = await wrapMissingObject(\n\t\tgit.readCommit({ ...repo, oid }),\n\t\t`${repo.gitdir} commit ${oid}`,\n\t);\n\treturn { oid, commit };\n}\n\n/** Read a blob's bytes by oid. */\nexport async function getBlob(repo: Repo, sha: string): Promise<Uint8Array> {\n\tconst { blob } = await wrapMissingObject(\n\t\tgit.readBlob({ ...repo, oid: sha }),\n\t\t`${repo.gitdir} blob ${sha}`,\n\t);\n\treturn blob;\n}\n\n/** Read a file's bytes at a ref. Throws GitPathNotFoundError when absent. */\nexport async function getFileContent(\n\trepo: Repo,\n\tfilePath: string,\n\tref: string,\n\thooks?: OpsHooks,\n): Promise<Uint8Array> {\n\t// A commit, a tree per path segment, then the blob: warm every pack first\n\t// rather than discovering them one round trip at a time.\n\tif (hooks?.prefetch) await runStep(hooks, \"prefetch\", hooks.prefetch);\n\tconst { commit } = await resolveCommit(repo, ref);\n\tconst context = `${repo.gitdir}@${ref}:${filePath}`;\n\tconst entry = await wrapMissingObject(\n\t\tfindTreeEntry(repo, commit.tree, filePath),\n\t\tcontext,\n\t);\n\tif (entry?.type !== \"blob\") {\n\t\tthrow new GitPathNotFoundError(`File not found: ${filePath}`);\n\t}\n\tconst { blob } = await wrapMissingObject(\n\t\tgit.readBlob({ ...repo, oid: entry.oid }),\n\t\tcontext,\n\t);\n\treturn blob;\n}\n\n/** Read one commit by sha. */\nexport async function getCommit(repo: Repo, sha: string): Promise<CommitInfo> {\n\tconst result = await wrapMissingObject(\n\t\tgit.readCommit({ ...repo, oid: sha }),\n\t\t`${repo.gitdir} commit ${sha}`,\n\t);\n\treturn { oid: result.oid, commit: result.commit, payload: result.payload };\n}\n\nexport interface CommitLogOptions {\n\tref: string;\n\tdepth?: number;\n\t/** The ref's commit, when the caller already resolved it (saves a lookup). */\n\tknownHeadSha?: string;\n}\n\n/**\n * The commit chain from a ref, newest first; [] when the ref doesn't exist.\n * Walking is sequential and round-trip-bound on object storage, so the\n * deepest walk seen per head is memoized in `hooks.resultCache` and sliced\n * for shallower requests.\n */\nexport async function getCommitLog(\n\trepo: Repo,\n\toptions: CommitLogOptions,\n\thooks?: OpsHooks,\n): Promise<CommitInfo[]> {\n\tconst { ref } = options;\n\tconst depth = options.depth ?? 50;\n\tlet headSha = options.knownHeadSha;\n\tif (headSha === undefined) {\n\t\ttry {\n\t\t\theadSha = await resolveRefOid(repo, ref);\n\t\t} catch (err) {\n\t\t\tif (err instanceof GitRefNotFoundError) return [];\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\tconst cacheKey = `commitlog:${repo.gitdir}:${headSha}`;\n\tconst cached = hooks?.resultCache?.get<CommitInfo[]>(cacheKey);\n\tconst fullyWalked = cached?.at(-1)?.commit.parent.length === 0;\n\tif (cached && (cached.length >= depth || fullyWalked)) {\n\t\thooks?.onNote?.(`getCommitLog: cache hit ${cacheKey} (depth=${depth})`);\n\t\treturn cached.slice(0, depth);\n\t}\n\thooks?.onNote?.(`getCommitLog: cache miss ${cacheKey} (depth=${depth})`);\n\tif (hooks?.prefetch) await runStep(hooks, \"prefetch\", hooks.prefetch);\n\n\tconst log = await wrapMissingObject(\n\t\trunStep(hooks, `git.log ${ref} depth=${depth}`, () =>\n\t\t\tgit.log({ ...repo, ref: headSha, depth }),\n\t\t),\n\t\t`${repo.gitdir}@${ref} history`,\n\t);\n\tconst result = log.map(({ oid, commit, payload }) => ({\n\t\toid,\n\t\tcommit,\n\t\tpayload: payload ?? \"\",\n\t}));\n\tif (!cached || result.length > cached.length) {\n\t\thooks?.resultCache?.set(cacheKey, result);\n\t}\n\treturn result;\n}\n\n/**\n * List a directory at the tip of a ref. Throws GitRefNotFoundError when the\n * ref doesn't resolve and GitPathNotFoundError when `treePath` doesn't exist.\n * Memoized per head sha.\n */\nexport async function getTreeFromRef(\n\trepo: Repo,\n\toptions: { ref: string; treePath?: string },\n\thooks?: OpsHooks,\n): Promise<TreeEntry[]> {\n\tconst { ref } = options;\n\tconst treePath = options.treePath ?? \"\";\n\tconst { commit, oid: headSha } = await resolveCommit(repo, ref);\n\n\tconst cacheKey = `tree:${repo.gitdir}:${headSha}:${treePath}`;\n\tconst cached = hooks?.resultCache?.get<TreeEntry[]>(cacheKey);\n\tif (cached) {\n\t\thooks?.onNote?.(`getTreeFromRef: cache hit ${cacheKey}`);\n\t\treturn cached;\n\t}\n\thooks?.onNote?.(`getTreeFromRef: cache miss ${cacheKey}`);\n\tif (hooks?.prefetch) await runStep(hooks, \"prefetch\", hooks.prefetch);\n\n\tconst context = `${repo.gitdir}@${ref}:${treePath || \"/\"}`;\n\tconst dir = treePath\n\t\t? await wrapMissingObject(\n\t\t\t\trunStep(hooks, `findTreeEntry ${treePath}`, () =>\n\t\t\t\t\tfindTreeEntry(repo, commit.tree, treePath),\n\t\t\t\t),\n\t\t\t\tcontext,\n\t\t\t)\n\t\t: { path: \"\", type: \"tree\" as const, oid: commit.tree };\n\tif (!dir) {\n\t\tthrow new GitPathNotFoundError(\n\t\t\t`Path \"${treePath}\" does not exist at ${ref}`,\n\t\t);\n\t}\n\tconst result =\n\t\tdir.type === \"tree\"\n\t\t\t? await wrapMissingObject(\n\t\t\t\t\trunStep(hooks, `listTreeEntries ${treePath || \"/\"}`, () =>\n\t\t\t\t\t\tlistTreeEntries(repo, dir.oid, dir.path),\n\t\t\t\t\t),\n\t\t\t\t\tcontext,\n\t\t\t\t)\n\t\t\t: [];\n\thooks?.resultCache?.set(cacheKey, result);\n\treturn result;\n}\n\n/**\n * A page of commit history from a ref tip; [] when the ref doesn't exist.\n * Pages are slices of {@link getCommitLog}'s memoized walk.\n */\nexport async function getCommitHistory(\n\trepo: Repo,\n\toptions: { ref: string; limit?: number; skip?: number },\n\thooks?: OpsHooks,\n): Promise<CommitInfo[]> {\n\tconst limit = options.limit ?? 50;\n\tconst skip = options.skip ?? 0;\n\tconst log = await getCommitLog(\n\t\trepo,\n\t\t{ ref: options.ref, depth: limit + skip },\n\t\thooks,\n\t);\n\treturn log.slice(skip, skip + limit);\n}\n","import git from \"isomorphic-git\";\nimport { GitConflictError, GitInvalidRequestError } from \"../git-errors.js\";\nimport { isSafeBranchName } from \"../refs.js\";\nimport { peelToCommit } from \"./history.js\";\nimport type { Repo } from \"./types.js\";\n\nexport interface Branch {\n\tname: string;\n\tcommit: string;\n\tisDefault: boolean;\n}\n\nexport interface Tag {\n\tname: string;\n\t/** The commit the tag names (annotated tags peeled). */\n\tcommit: string;\n\t/** The tag object's own oid, for annotated tags; absent for lightweight ones. */\n\ttagObject?: string;\n}\n\n/**\n * Defense in depth for every branch-name argument below: `git.deleteBranch`\n * and raw resolveRef/writeRef reads don't validate ref names internally the\n * way `git.branch` does (see refs.ts) — guard at the point the primitives are\n * actually called, not just at an API boundary far above.\n */\nexport function assertSafeBranchName(name: string): void {\n\tif (!isSafeBranchName(name)) {\n\t\tthrow new GitInvalidRequestError(`Invalid branch name: ${name}`);\n\t}\n}\n\nconst FULL_SHA_RE = /^[0-9a-f]{40}$/i;\n\ntype RecursiveFileLister = {\n\tlistFilesRecursively(dirpath: string): Promise<string[]>;\n};\n\nfunction hasRecursiveFileLister(\n\tfs: Repo[\"fs\"],\n): fs is Repo[\"fs\"] & RecursiveFileLister {\n\treturn (\n\t\ttypeof fs === \"object\" &&\n\t\tfs !== null &&\n\t\t\"listFilesRecursively\" in fs &&\n\t\ttypeof (fs as Partial<RecursiveFileLister>).listFilesRecursively ===\n\t\t\t\"function\"\n\t);\n}\n\ntype PromisesFs = {\n\treadFile(path: string, encoding: \"utf8\"): Promise<string | Uint8Array>;\n};\n\n// Repo.fs is typed as isomorphic-git's own fs union (promise- or\n// callback-based) since that's what git.readTree's signature allows, but\n// every fs this package actually constructs (git-fs.ts) is promise-based —\n// this just keeps the callback-style branch honest instead of crashing on\n// it. Shared by every loose-ref fast path below instead of each repeating\n// the same cast + presence check.\nfunction getPromisesFs(fs: Repo[\"fs\"]): PromisesFs | null {\n\tconst promises = (fs as { promises?: PromisesFs }).promises;\n\treturn promises ?? null;\n}\n\nfunction decodeFileContent(content: string | Uint8Array): string {\n\treturn typeof content === \"string\"\n\t\t? content\n\t\t: new TextDecoder().decode(content);\n}\n\n/**\n * Lists loose branch names without isomorphic-git's recursive readdir/stat\n * traversal. Object stores already return only leaf keys in a recursive LIST,\n * so this is one request regardless of branch-name nesting. Ordinary\n * filesystems retain isomorphic-git's implementation and packed refs remain\n * supported there.\n */\nasync function listLooseBranches(repo: Repo): Promise<string[] | null> {\n\treturn listLooseRefNames(repo, \"refs/heads\");\n}\n\n/**\n * Loose ref names under `dir` (e.g. `refs/tags`) in one recursive listing,\n * or `null` when the fs can't list recursively (use isomorphic-git's walk).\n */\nexport async function listLooseRefNames(\n\trepo: Repo,\n\tdir: \"refs/heads\" | \"refs/tags\",\n): Promise<string[] | null> {\n\tif (!hasRecursiveFileLister(repo.fs)) return null;\n\tconst names = await repo.fs.listFilesRecursively(`${repo.gitdir}/${dir}`);\n\treturn names.filter(isSafeBranchName);\n}\n\n/** Read packed branch refs so the object-store fast path preserves Git semantics. */\nasync function readPackedBranches(repo: Repo): Promise<Map<string, string>> {\n\tif (!hasRecursiveFileLister(repo.fs)) return new Map();\n\tconst promisesFs = getPromisesFs(repo.fs);\n\tif (!promisesFs) return new Map();\n\ttry {\n\t\tconst content = await promisesFs.readFile(\n\t\t\t`${repo.gitdir}/packed-refs`,\n\t\t\t\"utf8\",\n\t\t);\n\t\tconst refs = new Map<string, string>();\n\t\tfor (const line of decodeFileContent(content).split(\"\\n\")) {\n\t\t\tconst match = /^([0-9a-f]{40}) refs\\/heads\\/(.+)$/i.exec(line);\n\t\t\tif (match?.[1] && match[2] && isSafeBranchName(match[2])) {\n\t\t\t\trefs.set(match[2], match[1]);\n\t\t\t}\n\t\t}\n\t\treturn refs;\n\t} catch {\n\t\treturn new Map();\n\t}\n}\n\n/** Read the symbolic HEAD directly when the object-store fs is available. */\nasync function getCurrentLooseBranch(repo: Repo): Promise<string | null> {\n\tif (!hasRecursiveFileLister(repo.fs)) return null;\n\tconst promisesFs = getPromisesFs(repo.fs);\n\tif (!promisesFs) return null;\n\ttry {\n\t\tconst content = await promisesFs.readFile(`${repo.gitdir}/HEAD`, \"utf8\");\n\t\tconst head = decodeFileContent(content).trim();\n\t\tconst match = /^ref: refs\\/heads\\/(.+)$/.exec(head);\n\t\treturn match?.[1] && isSafeBranchName(match[1]) ? match[1] : null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Resolve a fully-qualified ref's oid with one read instead of isomorphic-\n * git's own `resolveRef`, which does a stat *then* a read for a loose ref\n * (two round trips against object storage) — wasteful specifically here,\n * where the caller already knows the ref exists (it came from a directory\n * listing moments earlier). Reads the loose file directly and falls back to\n * `git.resolveRef` only when that doesn't pan out (packed-refs, or anything\n * else the plain-loose-file assumption doesn't cover), so correctness for\n * those cases is unchanged — this only removes the redundant round trip on\n * the common path.\n */\nexport async function resolveLooseRefFast(\n\trepo: Repo,\n\tref: string,\n): Promise<string> {\n\tconst promisesFs = getPromisesFs(repo.fs);\n\tif (promisesFs) {\n\t\ttry {\n\t\t\tconst content = await promisesFs.readFile(\n\t\t\t\t`${repo.gitdir}/${ref}`,\n\t\t\t\t\"utf8\",\n\t\t\t);\n\t\t\tconst oid = decodeFileContent(content).trim();\n\t\t\tif (FULL_SHA_RE.test(oid)) return oid;\n\t\t} catch {\n\t\t\t// Not a loose file (packed-refs, or genuinely absent) — fall through.\n\t\t}\n\t}\n\treturn git.resolveRef({ ...repo, ref });\n}\n\n/** All branches with their tip commits; [] for an empty repository. */\nexport async function listBranches(repo: Repo): Promise<Branch[]> {\n\ttry {\n\t\tconst [looseBranches, looseCurrentBranch, packedBranches] =\n\t\t\tawait Promise.all([\n\t\t\t\tlistLooseBranches(repo),\n\t\t\t\tgetCurrentLooseBranch(repo),\n\t\t\t\treadPackedBranches(repo),\n\t\t\t]);\n\t\tconst branches =\n\t\t\tlooseBranches === null\n\t\t\t\t? await git.listBranches(repo)\n\t\t\t\t: [...new Set([...looseBranches, ...packedBranches.keys()])].sort();\n\t\tconst currentBranch =\n\t\t\tlooseBranches === null\n\t\t\t\t? await git\n\t\t\t\t\t\t.currentBranch({ ...repo, fullname: false })\n\t\t\t\t\t\t.catch(() => null)\n\t\t\t\t: looseCurrentBranch;\n\n\t\treturn Promise.all(\n\t\t\tbranches.map(async (branch) => {\n\t\t\t\tconst packedCommit = packedBranches.get(branch);\n\t\t\t\treturn {\n\t\t\t\t\tname: branch,\n\t\t\t\t\tcommit:\n\t\t\t\t\t\tpackedCommit && !looseBranches?.includes(branch)\n\t\t\t\t\t\t\t? packedCommit\n\t\t\t\t\t\t\t: await resolveLooseRefFast(repo, `refs/heads/${branch}`),\n\t\t\t\t\tisDefault: branch === currentBranch,\n\t\t\t\t};\n\t\t\t}),\n\t\t);\n\t} catch (err: unknown) {\n\t\tif ((err as { code?: string }).code === \"NotFoundError\") return [];\n\t\tthrow err;\n\t}\n}\n\n/** Create `name` pointing at the tip of `startPoint` (no checkout). */\nexport async function createBranchFrom(\n\trepo: Repo,\n\tname: string,\n\tstartPoint: string,\n): Promise<void> {\n\tassertSafeBranchName(name);\n\tassertSafeBranchName(startPoint);\n\tconst object = await resolveLooseRefFast(repo, `refs/heads/${startPoint}`);\n\ttry {\n\t\tawait git.branch({ ...repo, ref: name, checkout: false, object });\n\t} catch (err) {\n\t\tif ((err as { code?: string }).code === \"AlreadyExistsError\") {\n\t\t\tthrow new GitConflictError(`Branch ${name} already exists`);\n\t\t}\n\t\tthrow err;\n\t}\n}\n\n/** Delete a branch ref (validated — deleteBranch has no internal ref check). */\nexport async function deleteBranchByName(\n\trepo: Repo,\n\tname: string,\n): Promise<void> {\n\tassertSafeBranchName(name);\n\t// Same rule as receive-pack: deleting the branch HEAD names leaves every\n\t// clone without a default branch.\n\tconst current =\n\t\t(await getCurrentLooseBranch(repo)) ??\n\t\t(await git.currentBranch({ ...repo, fullname: false }).catch(() => null));\n\tif (current === name) {\n\t\tthrow new GitInvalidRequestError(\n\t\t\t`Cannot delete ${name}: it is the default branch`,\n\t\t);\n\t}\n\tawait git.deleteBranch({ ...repo, ref: name });\n}\n\n/** Throws (NotFoundError) unless the branch resolves. */\nexport async function assertBranchExists(\n\trepo: Repo,\n\tname: string,\n): Promise<void> {\n\tassertSafeBranchName(name);\n\tawait resolveLooseRefFast(repo, `refs/heads/${name}`);\n}\n\n/** All tags with the commits they name, sorted by name; [] when there are none. */\nexport async function listTags(repo: Repo): Promise<Tag[]> {\n\tconst names = (await listLooseRefNames(repo, \"refs/tags\")) ?? [];\n\tconst all = new Set([\n\t\t...names,\n\t\t...(await git.listTags(repo).catch(() => [] as string[])),\n\t]);\n\tconst tags = await Promise.all(\n\t\t[...all].filter(isSafeBranchName).map(async (name): Promise<Tag | null> => {\n\t\t\ttry {\n\t\t\t\tconst oid = await resolveLooseRefFast(repo, `refs/tags/${name}`);\n\t\t\t\tconst commit = await peelToCommit(repo, oid);\n\t\t\t\treturn commit === oid\n\t\t\t\t\t? { name, commit }\n\t\t\t\t\t: { name, commit, tagObject: oid };\n\t\t\t} catch {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t}),\n\t);\n\treturn tags\n\t\t.filter((tag): tag is Tag => tag !== null)\n\t\t.sort((a, b) => a.name.localeCompare(b.name, undefined, { numeric: true }));\n}\n","/**\n * Writing packfiles: serialize whole (never deltified) objects into a pack,\n * and store a pack plus its index in a repository. Shared by repack (the\n * loose-object fallback) and packed writes (ops/packed-writes.ts).\n *\n * Edge-compatible: Web Crypto SHA-1, CompressionStream, Uint8Array only.\n */\nimport git from \"isomorphic-git\";\nimport {\n\tconcat,\n\tdeflate,\n\tencodeUtf8,\n\tfromHex,\n\tsha1,\n\ttoHex,\n} from \"./edge-utils.js\";\nimport { rawFs } from \"./http/types.js\";\nimport type { Repo } from \"./ops/types.js\";\nimport { packFileName } from \"./pack-files.js\";\n\nexport type PackObjectType = \"commit\" | \"tree\" | \"blob\" | \"tag\";\n\n// Git pack object type bits (bits 6-4 of the header's first byte).\nconst PACK_OBJECT_TYPE_BITS: Record<PackObjectType, number> = {\n\tcommit: 0b0010000,\n\ttree: 0b0100000,\n\tblob: 0b0110000,\n\ttag: 0b1000000,\n};\n\n// First byte: continuation bit | 3-bit type | low 4 bits of length; the rest\n// of the length follows 7 bits at a time, little-endian.\nfunction encodePackObjectHeader(\n\ttype: PackObjectType,\n\tlength: number,\n): Uint8Array {\n\tconst bytes: number[] = [];\n\tlet more = length > 0b1111;\n\tbytes.push(\n\t\t(more ? 0b10000000 : 0) | PACK_OBJECT_TYPE_BITS[type] | (length & 0b1111),\n\t);\n\tlet rest = Math.floor(length / 16);\n\twhile (more) {\n\t\tmore = rest > 0b01111111;\n\t\tbytes.push((more ? 0b10000000 : 0) | (rest & 0b01111111));\n\t\trest = Math.floor(rest / 128);\n\t}\n\treturn new Uint8Array(bytes);\n}\n\n/** Git's object id: sha1(\"<type> <byte length>\\0<content>\"). */\nexport function hashGitObject(\n\ttype: string,\n\tcontent: Uint8Array,\n): Promise<string> {\n\treturn sha1(concat(encodeUtf8(`${type} ${content.length}\\0`), content));\n}\n\nexport interface PackObject {\n\ttype: PackObjectType;\n\tcontent: Uint8Array<ArrayBuffer>;\n}\n\n/** A version-2 pack holding `objects` as full entries, in order. */\nexport async function encodePack(\n\tobjects: readonly PackObject[],\n): Promise<Uint8Array<ArrayBuffer>> {\n\tconst header = new Uint8Array(12);\n\tconst view = new DataView(header.buffer);\n\theader.set(encodeUtf8(\"PACK\"), 0);\n\tview.setUint32(4, 2, false);\n\tview.setUint32(8, objects.length, false);\n\n\tconst chunks: Uint8Array[] = [header];\n\tconst BATCH_SIZE = 100;\n\tfor (let i = 0; i < objects.length; i += BATCH_SIZE) {\n\t\tconst encoded = await Promise.all(\n\t\t\tobjects\n\t\t\t\t.slice(i, i + BATCH_SIZE)\n\t\t\t\t.map(async (object) =>\n\t\t\t\t\tconcat(\n\t\t\t\t\t\tencodePackObjectHeader(object.type, object.content.length),\n\t\t\t\t\t\tawait deflate(object.content),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t);\n\t\tchunks.push(...encoded);\n\t}\n\t// The trailer is the SHA-1 of every preceding byte.\n\tconst body = concat(...chunks);\n\treturn concat(body, fromHex(await sha1(body)));\n}\n\n/**\n * A pack's conventional base name, `pack-<checksum>`: the trailer SHA-1 is a\n * function of the content, so two writers can never pick the same name for\n * different packs, and rewriting the same pack is idempotent.\n */\nexport function packBaseName(pack: Uint8Array): string {\n\treturn `pack-${toHex(pack.subarray(pack.length - 20))}`;\n}\n\n/**\n * Store `pack` in `repo` and index it (isomorphic-git's `indexPack`, which\n * re-hashes every object). The `.idx` is written last: a pack only becomes\n * visible to readers once its index exists. Returns the base name and the\n * oids the index lists.\n */\nexport async function storePack(\n\trepo: Repo,\n\tpack: Uint8Array,\n): Promise<{ base: string; oids: string[] }> {\n\tconst fsp = rawFs(repo);\n\tconst packDir = `${repo.gitdir}/objects/pack`;\n\tconst base = packBaseName(pack);\n\tawait fsp.mkdir(packDir, { recursive: true });\n\tawait fsp.writeFile(`${packDir}/${packFileName(base, \"pack\")}`, pack);\n\tconst { oids } = await git.indexPack({\n\t\t...repo,\n\t\tdir: packDir,\n\t\tfilepath: packFileName(base, \"pack\"),\n\t});\n\treturn { base, oids };\n}\n","/**\n * A FIFO semaphore for bounding in-flight I/O.\n *\n * An unbounded `Promise.all` over a list whose size the caller (or an\n * attacker) controls — a git tree with tens of thousands of blob entries, say,\n * each costing one object-store read — issues that many concurrent requests\n * from a single push or clone. Sockets and memory then scale with the input,\n * and S3-compatible providers rate-limit per account, so one hostile push\n * degrades every other tenant. Bounding the *in-flight* count caps resource\n * use at `limit` no matter how wide the input is, while still letting reads\n * overlap up to that limit instead of running strictly sequentially.\n */\nexport interface Semaphore {\n\trun<T>(fn: () => Promise<T>): Promise<T>;\n}\n\n/**\n * Create a FIFO semaphore admitting at most `limit` concurrent `run`\n * executions. Waiters are queued as promises and released in call order — no\n * polling or timers — so a caller blocked on a full semaphore is woken the\n * instant a permit frees, not on the next tick of a poll loop.\n */\nexport function createSemaphore(limit: number): Semaphore {\n\tif (!Number.isInteger(limit) || limit < 1) {\n\t\tthrow new Error(\n\t\t\t`createSemaphore: limit must be an integer >= 1, got ${limit}`,\n\t\t);\n\t}\n\n\tlet active = 0;\n\tconst waiting: Array<() => void> = [];\n\n\t/**\n\t * Hand the permit to the next waiter instead of dropping it, so `active`\n\t * only decreases when nothing is queued and never exceeds `limit`.\n\t */\n\tfunction release(): void {\n\t\tconst next = waiting.shift();\n\t\tif (next) next();\n\t\telse active--;\n\t}\n\n\tfunction acquire(): Promise<void> {\n\t\tif (active < limit) {\n\t\t\tactive++;\n\t\t\treturn Promise.resolve();\n\t\t}\n\t\treturn new Promise<void>((resolve) => {\n\t\t\twaiting.push(resolve);\n\t\t});\n\t}\n\n\treturn {\n\t\trun<T>(fn: () => Promise<T>): Promise<T> {\n\t\t\treturn acquire().then(async () => {\n\t\t\t\ttry {\n\t\t\t\t\treturn await fn();\n\t\t\t\t} finally {\n\t\t\t\t\trelease();\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\t};\n}\n"],"mappings":"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","names":["git","git","git","git","git","git"]}
|
package/dist/http.js
CHANGED
package/dist/ops.js
CHANGED
|
@@ -3,21 +3,36 @@ import {
|
|
|
3
3
|
parseLooseObjectPath
|
|
4
4
|
} from "./chunk-G636FB7W.js";
|
|
5
5
|
import {
|
|
6
|
+
HISTORY_WALK_DEPTH,
|
|
6
7
|
assertBranchExists,
|
|
7
8
|
assertSafeBranchName,
|
|
8
9
|
createBranchFrom,
|
|
9
10
|
createSemaphore,
|
|
10
11
|
deleteBranchByName,
|
|
12
|
+
deleteFromTree,
|
|
11
13
|
encodePack,
|
|
14
|
+
findTreeEntry,
|
|
15
|
+
getBlob,
|
|
16
|
+
getCommit,
|
|
17
|
+
getCommitHistory,
|
|
18
|
+
getCommitLog,
|
|
19
|
+
getFileContent,
|
|
20
|
+
getTreeFromRef,
|
|
12
21
|
listBranches,
|
|
22
|
+
listTags,
|
|
23
|
+
listTreeEntries,
|
|
24
|
+
peelToCommit,
|
|
25
|
+
resolveCommit,
|
|
13
26
|
resolveLooseRefFast,
|
|
14
|
-
|
|
15
|
-
|
|
27
|
+
resultKeyPrefixes,
|
|
28
|
+
runStep,
|
|
29
|
+
storePack,
|
|
30
|
+
summarizeCommit,
|
|
31
|
+
upsertTree
|
|
32
|
+
} from "./chunk-RJYNJOYF.js";
|
|
16
33
|
import {
|
|
17
34
|
GitInvalidRequestError,
|
|
18
|
-
GitObjectNotFoundError,
|
|
19
35
|
GitPathNotFoundError,
|
|
20
|
-
GitRefNotFoundError,
|
|
21
36
|
inflate,
|
|
22
37
|
isSafeTreePath,
|
|
23
38
|
qualifyBranchRef,
|
|
@@ -25,7 +40,7 @@ import {
|
|
|
25
40
|
} from "./chunk-HO6OPXUS.js";
|
|
26
41
|
|
|
27
42
|
// src/ops/commit.ts
|
|
28
|
-
import
|
|
43
|
+
import git from "isomorphic-git";
|
|
29
44
|
|
|
30
45
|
// src/ops/packed-writes.ts
|
|
31
46
|
var OBJECT_TYPES = /* @__PURE__ */ new Set(["commit", "tree", "blob", "tag"]);
|
|
@@ -109,120 +124,6 @@ async function withPackedWrites(repo, fn) {
|
|
|
109
124
|
return result;
|
|
110
125
|
}
|
|
111
126
|
|
|
112
|
-
// src/ops/tree.ts
|
|
113
|
-
import git from "isomorphic-git";
|
|
114
|
-
var joinPath = (prefix, name) => prefix ? `${prefix.replace(/\/+$/, "")}/${name}` : name;
|
|
115
|
-
var BLOB_MODES = /* @__PURE__ */ new Set(["100644", "100755", "120000"]);
|
|
116
|
-
async function upsertTree(repo, treeOid, entries) {
|
|
117
|
-
const existing = treeOid ? (await git.readTree({ ...repo, oid: treeOid })).tree : [];
|
|
118
|
-
const byName = new Map(existing.map((e) => [e.path, e]));
|
|
119
|
-
const direct = /* @__PURE__ */ new Map();
|
|
120
|
-
const nested = /* @__PURE__ */ new Map();
|
|
121
|
-
for (const [filePath, blobOid] of entries) {
|
|
122
|
-
const slash = filePath.indexOf("/");
|
|
123
|
-
if (slash === -1) {
|
|
124
|
-
direct.set(filePath, blobOid);
|
|
125
|
-
} else {
|
|
126
|
-
const dir = filePath.slice(0, slash);
|
|
127
|
-
const rest = filePath.slice(slash + 1);
|
|
128
|
-
if (!nested.has(dir)) nested.set(dir, /* @__PURE__ */ new Map());
|
|
129
|
-
nested.get(dir)?.set(rest, blobOid);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
for (const [name, blobOid] of direct) {
|
|
133
|
-
const current = byName.get(name);
|
|
134
|
-
if (current && current.type !== "blob") {
|
|
135
|
-
throw new GitInvalidRequestError(`"${name}" is a directory, not a file`);
|
|
136
|
-
}
|
|
137
|
-
byName.set(name, {
|
|
138
|
-
mode: current && BLOB_MODES.has(current.mode) ? current.mode : "100644",
|
|
139
|
-
path: name,
|
|
140
|
-
oid: blobOid,
|
|
141
|
-
type: "blob"
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
const nestedResults = await Promise.all(
|
|
145
|
-
Array.from(nested, async ([dir, subEntries]) => {
|
|
146
|
-
const entry = byName.get(dir);
|
|
147
|
-
if (entry && entry.type !== "tree") {
|
|
148
|
-
throw new GitInvalidRequestError(`"${dir}" is a file, not a directory`);
|
|
149
|
-
}
|
|
150
|
-
const subtreeOid = entry?.oid;
|
|
151
|
-
const newOid = await upsertTree(repo, subtreeOid, subEntries);
|
|
152
|
-
return [dir, newOid];
|
|
153
|
-
})
|
|
154
|
-
);
|
|
155
|
-
for (const [dir, newOid] of nestedResults) {
|
|
156
|
-
byName.set(dir, { mode: "040000", path: dir, oid: newOid, type: "tree" });
|
|
157
|
-
}
|
|
158
|
-
return git.writeTree({ ...repo, tree: Array.from(byName.values()) });
|
|
159
|
-
}
|
|
160
|
-
async function deleteFromTree(repo, treeOid, filePath) {
|
|
161
|
-
const result = await deleteFromSubtree(repo, treeOid, filePath, filePath);
|
|
162
|
-
return result ?? git.writeTree({ ...repo, tree: [] });
|
|
163
|
-
}
|
|
164
|
-
async function deleteFromSubtree(repo, treeOid, filePath, fullPath) {
|
|
165
|
-
const existing = (await git.readTree({ ...repo, oid: treeOid })).tree;
|
|
166
|
-
const byName = new Map(existing.map((e) => [e.path, e]));
|
|
167
|
-
const slash = filePath.indexOf("/");
|
|
168
|
-
const name = slash === -1 ? filePath : filePath.slice(0, slash);
|
|
169
|
-
const entry = byName.get(name);
|
|
170
|
-
if (slash === -1) {
|
|
171
|
-
if (entry?.type !== "blob") {
|
|
172
|
-
throw new GitPathNotFoundError(`No such file: ${fullPath}`);
|
|
173
|
-
}
|
|
174
|
-
byName.delete(name);
|
|
175
|
-
} else {
|
|
176
|
-
if (entry?.type !== "tree") {
|
|
177
|
-
throw new GitPathNotFoundError(`No such file: ${fullPath}`);
|
|
178
|
-
}
|
|
179
|
-
const newOid = await deleteFromSubtree(
|
|
180
|
-
repo,
|
|
181
|
-
entry.oid,
|
|
182
|
-
filePath.slice(slash + 1),
|
|
183
|
-
fullPath
|
|
184
|
-
);
|
|
185
|
-
if (newOid === null) byName.delete(name);
|
|
186
|
-
else byName.set(name, { ...entry, oid: newOid });
|
|
187
|
-
}
|
|
188
|
-
if (byName.size === 0) return null;
|
|
189
|
-
return git.writeTree({ ...repo, tree: Array.from(byName.values()) });
|
|
190
|
-
}
|
|
191
|
-
async function findTreeEntry(repo, rootTreeOid, treePath) {
|
|
192
|
-
if (!treePath) {
|
|
193
|
-
return { path: "", mode: "040000", type: "tree", oid: rootTreeOid };
|
|
194
|
-
}
|
|
195
|
-
const parts = treePath.split("/").filter(Boolean);
|
|
196
|
-
let currentTreeOid = rootTreeOid;
|
|
197
|
-
let currentPath = "";
|
|
198
|
-
for (const [index, part] of parts.entries()) {
|
|
199
|
-
const tree = await git.readTree({ ...repo, oid: currentTreeOid });
|
|
200
|
-
const entry = tree.tree.find((candidate) => candidate.path === part);
|
|
201
|
-
if (!entry) return null;
|
|
202
|
-
currentPath = currentPath ? joinPath(currentPath, entry.path) : entry.path;
|
|
203
|
-
if (index === parts.length - 1) {
|
|
204
|
-
return {
|
|
205
|
-
path: currentPath,
|
|
206
|
-
mode: entry.mode,
|
|
207
|
-
type: entry.type,
|
|
208
|
-
oid: entry.oid
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
if (entry.type !== "tree") return null;
|
|
212
|
-
currentTreeOid = entry.oid;
|
|
213
|
-
}
|
|
214
|
-
return null;
|
|
215
|
-
}
|
|
216
|
-
async function listTreeEntries(repo, treeOid, prefix = "") {
|
|
217
|
-
const tree = await git.readTree({ ...repo, oid: treeOid });
|
|
218
|
-
return tree.tree.map((entry) => ({
|
|
219
|
-
path: prefix ? joinPath(prefix, entry.path) : entry.path,
|
|
220
|
-
mode: entry.mode,
|
|
221
|
-
type: entry.type,
|
|
222
|
-
oid: entry.oid
|
|
223
|
-
}));
|
|
224
|
-
}
|
|
225
|
-
|
|
226
127
|
// src/ops/commit.ts
|
|
227
128
|
function authorNow(name, email) {
|
|
228
129
|
return {
|
|
@@ -240,11 +141,11 @@ async function writeCommit(repo, options) {
|
|
|
240
141
|
let parentOid;
|
|
241
142
|
let parentTreeOid;
|
|
242
143
|
try {
|
|
243
|
-
parentOid = await
|
|
144
|
+
parentOid = await git.resolveRef({
|
|
244
145
|
...repo,
|
|
245
146
|
ref: `refs/heads/${options.branch}`
|
|
246
147
|
});
|
|
247
|
-
const { commit } = await
|
|
148
|
+
const { commit } = await git.readCommit({ ...repo, oid: parentOid });
|
|
248
149
|
parentTreeOid = commit.tree;
|
|
249
150
|
} catch (err) {
|
|
250
151
|
if (err?.code !== "NotFoundError") {
|
|
@@ -252,7 +153,7 @@ async function writeCommit(repo, options) {
|
|
|
252
153
|
}
|
|
253
154
|
}
|
|
254
155
|
const treeOid = await options.buildTree(parentTreeOid, repo);
|
|
255
|
-
const commitOid = await
|
|
156
|
+
const commitOid = await git.writeCommit({
|
|
256
157
|
...repo,
|
|
257
158
|
commit: {
|
|
258
159
|
message: options.message,
|
|
@@ -262,7 +163,7 @@ async function writeCommit(repo, options) {
|
|
|
262
163
|
committer: options.author
|
|
263
164
|
}
|
|
264
165
|
});
|
|
265
|
-
await
|
|
166
|
+
await git.writeRef({
|
|
266
167
|
...repo,
|
|
267
168
|
ref: `refs/heads/${options.branch}`,
|
|
268
169
|
value: commitOid,
|
|
@@ -293,7 +194,7 @@ async function commitFilesToBare(repo, options) {
|
|
|
293
194
|
await Promise.all(
|
|
294
195
|
options.files.map(async (file) => {
|
|
295
196
|
const content = typeof file.content === "string" ? new TextEncoder().encode(file.content) : file.content;
|
|
296
|
-
const oid = await
|
|
197
|
+
const oid = await git.writeBlob({ ...writeRepo, blob: content });
|
|
297
198
|
blobs.set(file.path, oid);
|
|
298
199
|
})
|
|
299
200
|
);
|
|
@@ -320,180 +221,7 @@ async function deleteFileFromBare(repo, options) {
|
|
|
320
221
|
|
|
321
222
|
// src/ops/diff.ts
|
|
322
223
|
import { createTwoFilesPatch } from "diff";
|
|
323
|
-
import
|
|
324
|
-
|
|
325
|
-
// src/ops/history.ts
|
|
326
|
-
import git3 from "isomorphic-git";
|
|
327
|
-
|
|
328
|
-
// src/ops/types.ts
|
|
329
|
-
var runStep = (hooks, label, fn) => hooks?.step ? hooks.step(label, fn) : fn();
|
|
330
|
-
function resultKeyPrefixes(gitdir) {
|
|
331
|
-
return [
|
|
332
|
-
`commitlog:${gitdir}:`,
|
|
333
|
-
`tree:${gitdir}:`,
|
|
334
|
-
`last-commits:${gitdir}:`,
|
|
335
|
-
`file-history:${gitdir}:`
|
|
336
|
-
];
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// src/ops/history.ts
|
|
340
|
-
var HISTORY_WALK_DEPTH = 400;
|
|
341
|
-
function summarizeCommit(commit) {
|
|
342
|
-
return {
|
|
343
|
-
sha: commit.oid,
|
|
344
|
-
message: commit.commit.message.trim(),
|
|
345
|
-
authorName: commit.commit.author.name,
|
|
346
|
-
authorEmail: commit.commit.author.email,
|
|
347
|
-
createdAt: new Date(commit.commit.author.timestamp * 1e3).toISOString()
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
var isNotFound = (err) => err?.code === "NotFoundError";
|
|
351
|
-
function wrapMissingObject(promise, context) {
|
|
352
|
-
return promise.catch((err) => {
|
|
353
|
-
if (isNotFound(err)) {
|
|
354
|
-
throw new GitObjectNotFoundError(
|
|
355
|
-
`Git data for ${context} is missing from storage`
|
|
356
|
-
);
|
|
357
|
-
}
|
|
358
|
-
throw err;
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
|
-
async function resolveRefOid(repo, ref) {
|
|
362
|
-
try {
|
|
363
|
-
return await git3.resolveRef({ ...repo, ref: qualifyBranchRef(ref) });
|
|
364
|
-
} catch (err) {
|
|
365
|
-
if (isNotFound(err))
|
|
366
|
-
throw new GitRefNotFoundError(`Ref ${ref} does not exist`);
|
|
367
|
-
throw err;
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
async function resolveCommit(repo, ref) {
|
|
371
|
-
const oid = await resolveRefOid(repo, ref);
|
|
372
|
-
const { commit } = await wrapMissingObject(
|
|
373
|
-
git3.readCommit({ ...repo, oid }),
|
|
374
|
-
`${repo.gitdir} commit ${oid}`
|
|
375
|
-
);
|
|
376
|
-
return { oid, commit };
|
|
377
|
-
}
|
|
378
|
-
async function getBlob(repo, sha) {
|
|
379
|
-
const { blob } = await wrapMissingObject(
|
|
380
|
-
git3.readBlob({ ...repo, oid: sha }),
|
|
381
|
-
`${repo.gitdir} blob ${sha}`
|
|
382
|
-
);
|
|
383
|
-
return blob;
|
|
384
|
-
}
|
|
385
|
-
async function getFileContent(repo, filePath, ref, hooks) {
|
|
386
|
-
if (hooks?.prefetch) await runStep(hooks, "prefetch", hooks.prefetch);
|
|
387
|
-
const { commit } = await resolveCommit(repo, ref);
|
|
388
|
-
const context = `${repo.gitdir}@${ref}:${filePath}`;
|
|
389
|
-
const entry = await wrapMissingObject(
|
|
390
|
-
findTreeEntry(repo, commit.tree, filePath),
|
|
391
|
-
context
|
|
392
|
-
);
|
|
393
|
-
if (entry?.type !== "blob") {
|
|
394
|
-
throw new GitPathNotFoundError(`File not found: ${filePath}`);
|
|
395
|
-
}
|
|
396
|
-
const { blob } = await wrapMissingObject(
|
|
397
|
-
git3.readBlob({ ...repo, oid: entry.oid }),
|
|
398
|
-
context
|
|
399
|
-
);
|
|
400
|
-
return blob;
|
|
401
|
-
}
|
|
402
|
-
async function getCommit(repo, sha) {
|
|
403
|
-
const result = await wrapMissingObject(
|
|
404
|
-
git3.readCommit({ ...repo, oid: sha }),
|
|
405
|
-
`${repo.gitdir} commit ${sha}`
|
|
406
|
-
);
|
|
407
|
-
return { oid: result.oid, commit: result.commit, payload: result.payload };
|
|
408
|
-
}
|
|
409
|
-
async function getCommitLog(repo, options, hooks) {
|
|
410
|
-
const { ref } = options;
|
|
411
|
-
const depth = options.depth ?? 50;
|
|
412
|
-
let headSha = options.knownHeadSha;
|
|
413
|
-
if (headSha === void 0) {
|
|
414
|
-
try {
|
|
415
|
-
headSha = await resolveRefOid(repo, ref);
|
|
416
|
-
} catch (err) {
|
|
417
|
-
if (err instanceof GitRefNotFoundError) return [];
|
|
418
|
-
throw err;
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
const cacheKey = `commitlog:${repo.gitdir}:${headSha}`;
|
|
422
|
-
const cached = hooks?.resultCache?.get(cacheKey);
|
|
423
|
-
const fullyWalked = cached?.at(-1)?.commit.parent.length === 0;
|
|
424
|
-
if (cached && (cached.length >= depth || fullyWalked)) {
|
|
425
|
-
hooks?.onNote?.(`getCommitLog: cache hit ${cacheKey} (depth=${depth})`);
|
|
426
|
-
return cached.slice(0, depth);
|
|
427
|
-
}
|
|
428
|
-
hooks?.onNote?.(`getCommitLog: cache miss ${cacheKey} (depth=${depth})`);
|
|
429
|
-
if (hooks?.prefetch) await runStep(hooks, "prefetch", hooks.prefetch);
|
|
430
|
-
const log = await wrapMissingObject(
|
|
431
|
-
runStep(
|
|
432
|
-
hooks,
|
|
433
|
-
`git.log ${ref} depth=${depth}`,
|
|
434
|
-
() => git3.log({ ...repo, ref: headSha, depth })
|
|
435
|
-
),
|
|
436
|
-
`${repo.gitdir}@${ref} history`
|
|
437
|
-
);
|
|
438
|
-
const result = log.map(({ oid, commit, payload }) => ({
|
|
439
|
-
oid,
|
|
440
|
-
commit,
|
|
441
|
-
payload: payload ?? ""
|
|
442
|
-
}));
|
|
443
|
-
if (!cached || result.length > cached.length) {
|
|
444
|
-
hooks?.resultCache?.set(cacheKey, result);
|
|
445
|
-
}
|
|
446
|
-
return result;
|
|
447
|
-
}
|
|
448
|
-
async function getTreeFromRef(repo, options, hooks) {
|
|
449
|
-
const { ref } = options;
|
|
450
|
-
const treePath = options.treePath ?? "";
|
|
451
|
-
const { commit, oid: headSha } = await resolveCommit(repo, ref);
|
|
452
|
-
const cacheKey = `tree:${repo.gitdir}:${headSha}:${treePath}`;
|
|
453
|
-
const cached = hooks?.resultCache?.get(cacheKey);
|
|
454
|
-
if (cached) {
|
|
455
|
-
hooks?.onNote?.(`getTreeFromRef: cache hit ${cacheKey}`);
|
|
456
|
-
return cached;
|
|
457
|
-
}
|
|
458
|
-
hooks?.onNote?.(`getTreeFromRef: cache miss ${cacheKey}`);
|
|
459
|
-
if (hooks?.prefetch) await runStep(hooks, "prefetch", hooks.prefetch);
|
|
460
|
-
const context = `${repo.gitdir}@${ref}:${treePath || "/"}`;
|
|
461
|
-
const dir = treePath ? await wrapMissingObject(
|
|
462
|
-
runStep(
|
|
463
|
-
hooks,
|
|
464
|
-
`findTreeEntry ${treePath}`,
|
|
465
|
-
() => findTreeEntry(repo, commit.tree, treePath)
|
|
466
|
-
),
|
|
467
|
-
context
|
|
468
|
-
) : { path: "", type: "tree", oid: commit.tree };
|
|
469
|
-
if (!dir) {
|
|
470
|
-
throw new GitPathNotFoundError(
|
|
471
|
-
`Path "${treePath}" does not exist at ${ref}`
|
|
472
|
-
);
|
|
473
|
-
}
|
|
474
|
-
const result = dir.type === "tree" ? await wrapMissingObject(
|
|
475
|
-
runStep(
|
|
476
|
-
hooks,
|
|
477
|
-
`listTreeEntries ${treePath || "/"}`,
|
|
478
|
-
() => listTreeEntries(repo, dir.oid, dir.path)
|
|
479
|
-
),
|
|
480
|
-
context
|
|
481
|
-
) : [];
|
|
482
|
-
hooks?.resultCache?.set(cacheKey, result);
|
|
483
|
-
return result;
|
|
484
|
-
}
|
|
485
|
-
async function getCommitHistory(repo, options, hooks) {
|
|
486
|
-
const limit = options.limit ?? 50;
|
|
487
|
-
const skip = options.skip ?? 0;
|
|
488
|
-
const log = await getCommitLog(
|
|
489
|
-
repo,
|
|
490
|
-
{ ref: options.ref, depth: limit + skip },
|
|
491
|
-
hooks
|
|
492
|
-
);
|
|
493
|
-
return log.slice(skip, skip + limit);
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
// src/ops/diff.ts
|
|
224
|
+
import git2 from "isomorphic-git";
|
|
497
225
|
var MAX_DIFF_TEXT_BYTES = 1024 * 1024;
|
|
498
226
|
var MAX_CONCURRENT_READS = 16;
|
|
499
227
|
function countContentLines(content) {
|
|
@@ -570,12 +298,12 @@ async function diffTrees(repo, oldTreeOid, newTreeOid) {
|
|
|
570
298
|
const readContent = async (side) => {
|
|
571
299
|
if (!side) return null;
|
|
572
300
|
const { blob } = await reads.run(
|
|
573
|
-
() =>
|
|
301
|
+
() => git2.readBlob({ ...repo, oid: side.oid })
|
|
574
302
|
);
|
|
575
303
|
return readBlobContent(blob);
|
|
576
304
|
};
|
|
577
|
-
const trees = oldTreeOid ? [
|
|
578
|
-
const changes = await
|
|
305
|
+
const trees = oldTreeOid ? [git2.TREE({ ref: oldTreeOid }), git2.TREE({ ref: newTreeOid })] : [git2.TREE({ ref: newTreeOid })];
|
|
306
|
+
const changes = await git2.walk({
|
|
579
307
|
...repo,
|
|
580
308
|
trees,
|
|
581
309
|
map: async (filepath, entries) => {
|
|
@@ -645,7 +373,7 @@ async function getDiffBetweenRefs(repo, baseRef, compareRef) {
|
|
|
645
373
|
resolveCommit(repo, baseRef),
|
|
646
374
|
resolveCommit(repo, compareRef)
|
|
647
375
|
]);
|
|
648
|
-
const [mergeBase] = await
|
|
376
|
+
const [mergeBase] = await git2.findMergeBase({
|
|
649
377
|
...repo,
|
|
650
378
|
oids: [base.oid, compare.oid]
|
|
651
379
|
});
|
|
@@ -823,16 +551,16 @@ async function getLastCommitsForTree(repo, options, hooks) {
|
|
|
823
551
|
}
|
|
824
552
|
|
|
825
553
|
// src/ops/merge.ts
|
|
826
|
-
import
|
|
554
|
+
import git3 from "isomorphic-git";
|
|
827
555
|
async function analyzeMerge(repo, sourceBranch, targetBranch) {
|
|
828
556
|
assertSafeBranchName(sourceBranch);
|
|
829
557
|
assertSafeBranchName(targetBranch);
|
|
830
558
|
try {
|
|
831
559
|
const [sourceOid, targetOid] = await Promise.all([
|
|
832
|
-
|
|
833
|
-
|
|
560
|
+
git3.resolveRef({ ...repo, ref: qualifyBranchRef(sourceBranch) }),
|
|
561
|
+
git3.resolveRef({ ...repo, ref: qualifyBranchRef(targetBranch) })
|
|
834
562
|
]);
|
|
835
|
-
const isDescendant = sourceOid === targetOid || await
|
|
563
|
+
const isDescendant = sourceOid === targetOid || await git3.isDescendent({
|
|
836
564
|
...repo,
|
|
837
565
|
oid: sourceOid,
|
|
838
566
|
ancestor: targetOid
|
|
@@ -859,19 +587,19 @@ async function fastForwardMerge(repo, sourceBranch, targetBranch) {
|
|
|
859
587
|
assertSafeBranchName(sourceBranch);
|
|
860
588
|
assertSafeBranchName(targetBranch);
|
|
861
589
|
const [sourceOid, targetOid] = await Promise.all([
|
|
862
|
-
|
|
863
|
-
|
|
590
|
+
git3.resolveRef({ ...repo, ref: `refs/heads/${sourceBranch}` }),
|
|
591
|
+
git3.resolveRef({ ...repo, ref: `refs/heads/${targetBranch}` })
|
|
864
592
|
]);
|
|
865
|
-
if (sourceOid === targetOid || await
|
|
593
|
+
if (sourceOid === targetOid || await git3.isDescendent({ ...repo, oid: targetOid, ancestor: sourceOid })) {
|
|
866
594
|
return { success: true, commitSha: targetOid };
|
|
867
595
|
}
|
|
868
|
-
const isFF = await
|
|
596
|
+
const isFF = await git3.isDescendent({
|
|
869
597
|
...repo,
|
|
870
598
|
oid: sourceOid,
|
|
871
599
|
ancestor: targetOid
|
|
872
600
|
});
|
|
873
601
|
if (!isFF) return null;
|
|
874
|
-
await
|
|
602
|
+
await git3.writeRef({
|
|
875
603
|
...repo,
|
|
876
604
|
ref: `refs/heads/${targetBranch}`,
|
|
877
605
|
value: sourceOid,
|
|
@@ -997,8 +725,10 @@ export {
|
|
|
997
725
|
getSubmodules,
|
|
998
726
|
getTreeFromRef,
|
|
999
727
|
listBranches,
|
|
728
|
+
listTags,
|
|
1000
729
|
listTreeEntries,
|
|
1001
730
|
parseGitmodules,
|
|
731
|
+
peelToCommit,
|
|
1002
732
|
resolveCommit,
|
|
1003
733
|
resolveLooseRefFast,
|
|
1004
734
|
resultKeyPrefixes,
|