UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

241 lines • 12.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 { IOResult } from "../../../../io/types.js"; import { FlagView } from "../../../spec/flag_view.js"; import { COMBINED_MODE, commitDeltas, commitEntry, commitOutput, commitShown, isCombined, parseDiffFlags, renamesEnabled, separatorLine, } from "./diff_output.js"; import { FULL_SHA, commitHeader, presetBlock, renderTemplate, } from "./format.js"; import { CommitGraph } from "./graph.js"; import { displayed, decorations, inPrintOrder, parseFlags, refCommits, walkEnds, walkOperands, walked, } from "./history.js"; import { configBool } from "./repo.js"; import { opened } from "./session.js"; import { checkOperands } from "./util.js"; import { HEAD, TEMPLATE_KINDS, TERMINATED_KINDS } from "./constants.js"; import { Decoration } from "./types.js"; import { encodeText } from "../../../../shell/bytes.js"; // How `--boundary` marks a commit outside `--graph`, and the word a block // preset's header starts with. const BOUNDARY_PREFIX = '- '; const COMMIT_WORD = 'commit '; /** * A `--boundary` commit's entry outside `--graph`: a oneline row starts `- `, * and a block preset's header reads `commit - <id>`; a user format and email * print it as they print any commit (git 2.47.3). * * @param entry the entry as any commit's would read * @param kind the pretty format's kind */ function marked(entry, kind) { if (kind === 'oneline') return BOUNDARY_PREFIX + entry; if (TEMPLATE_KINDS.includes(kind) || !entry.startsWith(COMMIT_WORD)) return entry; return COMMIT_WORD + BOUNDARY_PREFIX + entry.slice(COMMIT_WORD.length); } /** * The text a log invocation prints for its selected commits. * * `format:` separates entries with a newline and ends without one, and an * entry that renders empty still claims its separator, so `--pretty=format:` * prints one newline per commit past the first; a block preset separates its * entries the same way. `tformat:` (and any bare `%` string) and oneline * terminate every entry instead. Pinned against git 2.37 and 2.54. */ function rendered(commits, flags, width, decor, boundary = new Set(), parents = new Map()) { const terminated = TERMINATED_KINDS.includes(flags.pretty.kind); return commits .map((commit) => { const entry = commitHeader(commit, flags, width, decor, parents.get(commit.oid) ?? null); return boundary.has(commit.oid) ? marked(entry, flags.pretty.kind) : entry; }) .join(terminated ? '' : '\n'); } /** * The text a `--graph` log prints, commit by commit. * * Every walked commit moves the graph on, printed or not, so a commit the * pickaxe passed by leaves a `...` row. A printed commit gets the graph's lines * up to its own, then its header, then its text with the next graph line in * front of each further line, then whatever lines the graph still owes. The * formats that separate entries (medium and its kin, `format:`) put the * separator behind a padding line, so the graph never shows a gap; the ones * that terminate entries (oneline, `tformat:`) do the same after each entry. * A padding line is skipped wherever the text before it ended without a * newline, since it would then land on that text's own line. * * With a diff each block (one per parent under `-m`) is an entry of its own, * each naming its parent, and every diff line sits behind a padding line, * the one between the message and the diff included. That line is `---` when * both a diffstat and a patch follow, and is left out for oneline, except * before a combined diff, which git prints from its own path: a merge's * combined diff gets the line even when nothing follows it, as does an empty * `--summary` outside oneline, and the combined diff's `mode` lines and its * `--name-only` or `--name-status` rows go out with no graph in front of them * at all (git 2.47.3). * * @param repo the opened repository * @param walk the walked commits and the ones an edge may lead to * @param flags the parsed invocation * @param decor ref labels per commit, when the format prints any * @param diff the diff flags, null when no diff was asked for * @param sides each shown commit's comparisons, as the session sees them, * when a diff was asked for */ async function graphed(repo, walk, flags, decor, diff, sides) { const width = repo.abbrev; const steps = [...walk.steps, ...walk.boundary.map((commit) => ({ commit, shown: true }))]; const graph = new CommitGraph((oid) => walk.interesting.has(oid), flags.firstParent, steps.map((step) => step.commit), new Set(walk.boundary.map((commit) => commit.oid)), walk.parents.size ? walk.parents : null); const mailmap = flags.useMailmap ? flags.mailmap : []; const fmt = flags.pretty; const user = TEMPLATE_KINDS.includes(fmt.kind); const terminated = TERMINATED_KINDS.includes(fmt.kind); const empty = user && (fmt.template ?? '') === ''; const length = fmt.kind === 'oneline' && !flags.abbrevCommit ? FULL_SHA : width; let out = ''; let shownOne = false; let missingNewline = false; for (const { commit, shown } of steps) { graph.walk(commit); if (!shown) continue; const bodies = diff === null ? [] : await commitOutput(repo, commit, diff, sides.get(commit.oid) ?? []); const blocks = []; bodies.forEach((body, index) => { if (body !== null) blocks.push([bodies.length > 1 ? (commit.parents[index] ?? null) : null, body]); }); const differs = blocks.length > 0; if (!differs) blocks.push([null, '']); for (const [parent, body] of blocks) { if (shownOne && !terminated) { if (!missingNewline) out += graph.filler(); out += '\n'; } shownOne = true; out += graph.leadIn(); const from = !user && parent !== null ? ` (from ${parent.slice(0, fmt.kind === 'oneline' ? length : FULL_SHA)})` : ''; const labels = flags.decorate !== Decoration.NONE ? renderTemplate('%d', commit, width, decor) : ''; let text; if (fmt.kind === 'oneline') { out += `${renderTemplate('%h', commit, length, decor)}${from}${labels} `; text = renderTemplate('%s', commit, length, decor); } else if (user) { text = renderTemplate(fmt.template ?? '', commit, width, decor, flags.date, flags.mailmap, walk.parents.get(commit.oid) ?? null); } else { const [head = '', ...rest] = presetBlock(commit, fmt.kind, width, flags.date, mailmap, walk.parents.get(commit.oid) ?? null); out += `${head}${from}${labels}\n${graph.nextRow()}`; text = rest.map((line) => `${line}\n`).join(''); } missingNewline = !text.endsWith('\n'); out += graph.around(text); if (terminated && !empty) { if (!missingNewline) out += graph.filler(); out += '\n'; } const combined = diff !== null && isCombined(commit, diff); if (diff === null || !differs) continue; if (body === '' && !(combined || (diff.summary && fmt.kind !== 'oneline'))) continue; const separator = empty ? null : separatorLine(commit, fmt.kind, diff); if (separator !== null) out += `${graph.filler()}${separator}\n`; const names = combined && (diff.nameOnly || diff.nameStatus); for (const line of body.split('\n').slice(0, -1)) { const bare = names || (combined && line.startsWith(COMBINED_MODE)); out += `${bare ? '' : graph.filler()}${line}\n`; } } } return out; } /** * The commits a log walks from and the commits it hides: the revisions and * ranges given, HEAD when there are none, plus every ref for --all. */ async function startingPoints(repo, revisions, flags, dashed) { const [starts, hidden] = await walkEnds(repo, revisions.length ? revisions : [HEAD], dashed); if (flags.allRefs) for (const item of await refCommits(repo)) starts.push(item); return [starts, hidden]; } /** Show commit logs. */ export async function log(_accessor, paths, texts, ctx) { const fl = new FlagView(ctx.flags); checkOperands(ctx, texts); const flags = parseFlags(fl, ctx.env, ctx.signal); const repo = await opened(ctx, fl); const [revisions, pathspecs, dashed] = await walkOperands(ctx, fl, repo, texts); const [mailmap, mapped, decorate] = await displayed(repo, fl, flags.pretty); const parsed = { ...flags, mailmap, useMailmap: mapped, decorate, pathspecs }; const [starts, hidden] = await startingPoints(repo, revisions, parsed, dashed); const decor = await decorations(repo, parsed.decorate); let diffFlags = parseDiffFlags(fl, false); // The pickaxe searches the diff, renames paired, whether or not any of it is // printed, which is how `-S -m` comes to name the parent each printed entry // matched against. const diffing = parsed.pickaxe !== null || diffFlags.patch || diffFlags.stat || diffFlags.nameOnly || diffFlags.nameStatus || diffFlags.numstat || diffFlags.shortstat || diffFlags.summary || diffFlags.raw; if (diffing) { diffFlags = parseDiffFlags(fl, false, 'off', true, await renamesEnabled(repo), await configBool(repo, 'core.quotepath', true)); } diffFlags = { ...diffFlags, pickaxe: parsed.pickaxe, pathspecs }; const walk = await walked(repo, starts, parsed, hidden, diffFlags); const commits = inPrintOrder(walk, parsed); const boundary = new Set(walk.boundary.map((commit) => commit.oid)); if (!diffing) { const out = parsed.graph ? await graphed(repo, walk, parsed, decor, null, new Map()) : rendered(commits, parsed, repo.abbrev, decor, boundary, walk.parents); return [out === '' ? null : encodeText(out), new IOResult()]; } const shownCommits = parsed.graph ? [...walk.steps.filter((step) => step.shown).map((step) => step.commit), ...walk.boundary] : commits; const [rows, spans] = await commitDeltas(repo, shownCommits, diffFlags); // A commit whose summary is empty still gets the blank line under its // header, as `show` gives one, though not under oneline (git 2.47.3). const summary = diffFlags.summary && !diffFlags.noPatch && parsed.pretty.kind !== 'oneline'; const render = async (shown) => { const sides = commitShown(shown, spans); let out; if (parsed.graph) out = await graphed(repo, walk, parsed, decor, diffFlags, sides); else { const blocks = []; for (const commit of commits) { const entry = await commitEntry(repo, commit, parsed, diffFlags, sides.get(commit.oid) ?? [], decor, summary, walk.parents.get(commit.oid) ?? null); blocks.push(boundary.has(commit.oid) ? marked(entry, parsed.pretty.kind) : entry); } out = blocks.join(TERMINATED_KINDS.includes(parsed.pretty.kind) ? '' : '\n'); } return out === '' ? null : encodeText(out); }; return [null, new IOResult({ rows: { items: rows, render } })]; } //# sourceMappingURL=log.js.map