UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

117 lines • 5.2 kB
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. ========= // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ========= Copyright 2026 @ Strukto.AI All Rights Reserved. ========= import git from 'isomorphic-git'; import { decodeText } from "../../../../shell/bytes.js"; import { toHex } from "../../../../utils/hex.js"; import { GitError } from "./errors.js"; import { resolveObject, unwrapped } from "./revparse.js"; import { rawObject, repoArgs } from "./repo.js"; import { rooted } from "./seen.js"; const SPACE = 0x20; const NUL = 0x00; const OID_BYTES = 20; const SHORT_TREE_MODE = '40000'; export const TREE_MODE = '040000'; const DEC = new TextDecoder(); /** * The entries of one tree object, read from its raw bytes. * * Parsed here rather than through isomorphic-git's readTree, which decodes each * name as UTF-8 and turns a byte that is not into U+FFFD. A name keeps every * byte, a byte UTF-8 cannot read carried as its surrogate escape, so the name * quotes and prints the way git's does. A subtree's `40000` is spelled * `040000`, as isomorphic-git spells it. */ export async function treeItems(repo, treeOid) { // The raw content is exactly what a byte-faithful name needs. const { raw } = await rawObject(repo, treeOid); const items = []; for (let at = 0; at < raw.length;) { const space = raw.indexOf(SPACE, at); const nul = space < 0 ? -1 : raw.indexOf(NUL, space); if (nul < 0 || nul + 1 + OID_BYTES > raw.length) throw new GitError(`unable to read tree (${treeOid})`); const mode = DEC.decode(raw.subarray(at, space)); const oid = toHex(raw.subarray(nul + 1, nul + 1 + OID_BYTES)); items.push({ path: decodeText(raw.subarray(space + 1, nul)), oid, mode: mode === SHORT_TREE_MODE ? TREE_MODE : mode, }); at = nul + 1 + OID_BYTES; } return items; } /** * Every blob a tree holds, keyed by repository-relative path. * * Flattened rather than walked per directory because every caller here compares * two whole trees; git's own diff does the same on the paths it has already * expanded. A submodule (`160000`) is carried through as an entry rather than * descended into: it names a commit in another repository. */ export async function treeEntries(repo, treeOid, prefix = '') { const rows = await listedTree(repo, treeOid, [], true, false, false, prefix); return new Map(rows.map(([path, mode, oid]) => [path, { mode, oid }])); } /** * Every blob a commit's tree holds, or an empty map for no commit at all. * * An empty map is what an unborn HEAD means: nothing is committed yet, so every * staged path reads as an addition. */ export async function commitEntries(repo, commitOid) { if (commitOid === null) return new Map(); const { commit } = await git.readCommit({ ...repoArgs(repo), oid: commitOid }); return treeEntries(repo, commit.tree); } /** Resolve a tree-ish through tags and commits. */ export async function resolveTree(repo, name, resolved) { const obj = await unwrapped(repo, resolved ?? (await resolveObject(repo, name)), name); if (obj.type === 'commit') { const tree = (await git.readCommit({ ...repoArgs(repo), oid: obj.oid })).commit.tree; rooted(repo, tree, name); return tree; } if (obj.type === 'tree') return obj.oid; throw new GitError('not a tree object'); } /** Literal tree prefixes; a trailing slash descends even without -r. */ export async function listedTree(repo, tree, patterns, recursive, trees, directories, prefix = '') { const out = []; for (const entry of await treeItems(repo, tree)) { const path = prefix ? `${prefix}/${entry.path}` : entry.path; const selected = patterns.length === 0 || patterns.some((pattern) => pattern === '' || path === pattern || path.startsWith(pattern.replace(/\/$/, '') + '/')); const row = [path, entry.mode, entry.oid]; if (entry.mode !== TREE_MODE) { if (selected && !directories) out.push(row); continue; } const descend = (recursive && selected) || patterns.some((pattern) => pattern.startsWith(path + '/')); const showTree = trees || (directories && (selected || recursive)); if ((selected && !descend) || (descend && showTree)) out.push(row); if (descend) { const children = await listedTree(repo, entry.oid, patterns, recursive, trees, directories, path); for (const child of children) out.push(child); } } return out; } //# sourceMappingURL=tree.js.map