UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

208 lines • 9.94 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 { HEAD, TERMINATED_KINDS } from "./constants.js"; import { dateClock, parseDateMode, showDate } from "./dates.js"; import { Decoration } from "./types.js"; import git from 'isomorphic-git'; import { IOResult } from "../../../../io/types.js"; import { concat } from "../../../../utils/bytes.js"; import { FlagView } from "../../../spec/flag_view.js"; import { UsageError } from "./errors.js"; import { decorations, displayed, prettyFormat } from "./history.js"; import { commitDeltas, commitEntry, commitOutput, commitShown, commitSides, parseDiffFlags, renamesEnabled, } from "./diff_output.js"; import { pathspecPatterns } from "./pathspec.js"; import { identDate } from "./ref_fields.js"; import { commitFacts, configBool, rawObject, repoArgs } from "./repo.js"; import { opened } from "./session.js"; import { resolveCommit, resolveObject } from "./revparse.js"; import { deltas, shownSides } from "./rows.js"; import { located, placeNamed, shownItems } from "./seen.js"; import { checkOperands, loneHelp, optionOperand, revisionArg, splitMarked, startPoint, verbUsage, } from "./util.js"; import { encodeText } from "../../../../shell/bytes.js"; /** Read the raw show flag kwargs into a frozen struct. */ function parseShowFlags(fl, defaultRenames = true, quotePathFully = true, env = null) { const pretty = prettyFormat(fl); return { mailmap: [], useMailmap: true, decorate: Decoration.NONE, abbrevCommit: fl.asBool('oneline'), date: parseDateMode(fl.asStr('date', 'default'), dateClock(env)), diff: parseDiffFlags(fl, true, 'dense-combined', true, defaultRenames, quotePathFully), pretty, }; } const DEC = new TextDecoder(); /** * The tagger as the format shows a person: nothing for oneline, the date under * medium, `TaggerDate` under fuller, and the name alone otherwise. */ function taggerLines(ident, flags) { const kind = flags.pretty.kind; const close = ident.indexOf('>', ident.indexOf(' <')); const date = identDate(ident); if (kind === 'oneline' || !ident.includes(' <') || close === -1 || date === null) return ''; const who = ident.slice(0, close + 1); const when = showDate(date[0], date[1], flags.date); if (kind === 'medium') return `Tagger: ${who}\nDate: ${when}\n`; if (kind === 'fuller') return `Tagger: ${who}\nTaggerDate: ${when}\n`; return `Tagger: ${who}\n`; } /** * What `git show` prints for an annotated tag ahead of the object it points * at, and that object: `tag <name>`, the tagger, then the rest of the tag from * its blank line on, which is its message as written. Pinned against git * 2.50.1. */ async function tagBlock(repo, oid, flags) { // The raw content is what git prints from, signature and all. const text = DEC.decode((await rawObject(repo, oid)).raw); const end = text.indexOf('\n\n'); const fields = (end === -1 ? text : text.slice(0, end)).split('\n'); const value = (key) => fields.find((line) => line.startsWith(`${key} `))?.slice(key.length + 1) ?? ''; const block = `tag ${value('tag')}\n${taggerLines(value('tagger'), flags)}`; return [ block + (end === -1 ? '' : text.slice(end + 1)), { oid: value('object'), type: value('type') }, ]; } /** * Every commit a line shows the diff of, tags peeled. Mirrors Python's * `_commits`. */ async function commits(repo, objects, flags) { const found = []; for (const [, obj] of objects) { let target = obj; while (target.type === 'tag') target = (await tagBlock(repo, target.oid, flags))[1]; if (target.type !== 'blob' && target.type !== 'tree') found.push(await commitFacts(repo, target.oid)); } return found; } /** * Show each object a line names, in order: a commit as its log entry and its * diff against its parent, an annotated tag as its own block ahead of what it * points at, a tree as its listing and a blob as its bytes. * * Every name resolves before anything prints, and a commit named twice prints * once. A blank line goes ahead of every tag and tree but the first thing shown, * and ahead of every later commit unless the format ends each entry itself * (oneline, tformat); a blob takes none and counts for none. Pinned against git * 2.50.1. * * Operands after `--` are pathspecs, read once the revision has resolved, as * git reads them; they limit the diff to the paths they name, and a commit * that changes nothing they name prints nothing at all. */ export async function show(_accessor, _paths, texts, ctx) { const fl = new FlagView(ctx.flags); checkOperands(ctx, texts); const [revisions, paths] = splitMarked(texts, ctx.argv ?? []); const repo = await opened(ctx, fl); const base = parseShowFlags(fl, await renamesEnabled(repo), await configBool(repo, 'core.quotepath', true), ctx.env); const [mailmap, mapped, decorate] = await displayed(repo, fl, base.pretty); const names = revisions.length > 0 ? revisions : [HEAD]; const objects = []; for (const name of names) objects.push([name, await resolveObject(repo, name)]); const pathspecs = pathspecPatterns(repo.location, startPoint(fl).virtual, paths); const parsed = { ...base, mailmap, useMailmap: mapped, decorate, diff: { ...base.diff, pathspecs }, }; const decor = await decorations(repo, parsed.decorate); const terminated = TERMINATED_KINDS.includes(parsed.pretty.kind); const [rows, spans] = await commitDeltas(repo, await commits(repo, objects, parsed), parsed.diff); const render = async (shown) => { const sides = commitShown(shown, spans); const parts = []; const shownCommits = new Set(); let shownOne = false; for (const [name, obj] of objects) { let target = obj; const tagged = target.type === 'tag'; while (target.type === 'tag') { const [block, next] = await tagBlock(repo, target.oid, parsed); parts.push(encodeText(`${shownOne ? '\n' : ''}${block}`)); shownOne = true; target = next; } // A tag can point past every commit at a tree or a blob, which is // shown only where the session sees it. if (tagged && (await located(repo, target)) === null) continue; if (target.type === 'blob') { parts.push((await git.readBlob({ ...repoArgs(repo), oid: target.oid })).blob); continue; } if (target.type === 'tree') { const base = await placeNamed(repo, name, target.oid); const body = (await shownItems(repo, target.oid, base)) .map((entry) => entry.path + (entry.type === 'tree' ? '/' : '') + '\n') .join(''); parts.push(encodeText(`${shownOne ? '\n' : ''}tree ${name}\n\n${body}`)); shownOne = true; continue; } if (shownCommits.has(target.oid)) continue; shownCommits.add(target.oid); const facts = await commitFacts(repo, target.oid); const entry = await commitEntry(repo, facts, parsed, parsed.diff, sides.get(facts.oid) ?? [], decor, parsed.diff.summary && !parsed.diff.noPatch && parsed.pretty.kind !== 'oneline'); parts.push(encodeText(`${shownOne && !terminated ? '\n' : ''}${entry}`)); shownOne = true; } return concat(parts); }; return [null, new IOResult({ rows: { items: rows, render } })]; } /** * Compare a commit with its parents. * * Every block opens with the commit id unless `--no-commit-id`, and a parent * the commit does not differ from prints nothing, id included. Operands after * `--` are pathspecs, read once the commit has resolved; they limit every block * to the paths they name. */ export async function diffTree(_accessor, _paths, texts, ctx) { const fl = new FlagView(ctx.flags); if (optionOperand(ctx, texts, loneHelp(ctx, texts)) !== null) { throw new UsageError('', verbUsage(ctx)); } const repo = await opened(ctx, fl); const parsed = parseDiffFlags(fl, false, 'off', false, true, await configBool(repo, 'core.quotepath', true)); const [revisions, paths] = splitMarked(texts, ctx.argv ?? []); const commit = await commitFacts(repo, await resolveCommit(repo, revisionArg(revisions))); const pathspecs = pathspecPatterns(repo.location, startPoint(fl).virtual, paths); const flags = { ...parsed, pathspecs }; const sides = await commitSides(repo, commit, flags, fl.asBool('r'), false); const render = async (shown) => { const bodies = await commitOutput(repo, commit, flags, shownSides(shown, sides.length)); const out = bodies .filter((body) => body !== null) .map((body) => (fl.asBool('no_commit_id') ? '' : commit.oid + '\n') + body) .join(''); return encodeText(out); }; return [null, new IOResult({ rows: { items: deltas(repo.location, sides), render } })]; } //# sourceMappingURL=show.js.map