@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
106 lines • 5.37 kB
JavaScript
// ========= 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 { branchUpstream } from "./branch.js";
import { assembled, collect } from "./changes.js";
import { GitError } from "./errors.js";
import { detachedLine } from "./ref_list.js";
import { readHead } from "./refs.js";
import { branchLine, longFormat, relativeEntries, shortFormat } from "./render.js";
import { configBool } from "./repo.js";
import { opened } from "./session.js";
import { startPoint, requireVfs } from "./util.js";
import { repoRelative } from "./pathspec.js";
import { UNTRACKED_ALL, UNTRACKED_NO, UNTRACKED_NORMAL } from "./worktree.js";
import { encodeText } from "../../../../shell/bytes.js";
/**
* Read the raw status flag kwargs into a frozen struct.
*
* `-u` carries its mode attached or not at all, and a bare one means `all`,
* which is why the value is read as a string first and only then as a boolean.
*/
function parseFlags(fl) {
const stated = fl.asStr('untracked_files');
const mode = stated ?? (fl.asBool('untracked_files') ? UNTRACKED_ALL : UNTRACKED_NORMAL);
const version = fl.asStr('porcelain');
if (version != null && !['1', 'v1'].includes(version))
throw new GitError(`unsupported porcelain version '${version}'`);
return {
porcelain: fl.asBool('porcelain') || fl.asStr('porcelain') != null,
short: fl.asBool('short'),
branch: fl.asBool('branch'),
ignored: fl.asBool('ignored'),
untracked: mode,
};
}
/**
* Where a person's status report names paths from, relative to the work tree;
* empty for paths named from its root.
*
* git's human formats name paths from the invocation directory unless
* `status.relativePaths` is false; porcelain never does. From outside the work
* tree they stay relative to its root. Mirrors Python's `relative_prefix`.
*/
async function relativePrefix(repo, start) {
if (!(await configBool(repo, 'status.relativePaths', true)))
return '';
return repoRelative(repo.location, start, '.');
}
/**
* The default status report, as rows and their renderer.
*
* Split out so `commit` can print it when it has nothing to commit: git shows
* the whole status there rather than a one-line refusal, and two renderings of
* the same thing would drift. Its paths are relative to `start`, where git runs.
*/
export async function reportRows(repo, vfs, head, start) {
const [rows, state, noCommits] = await collect(repo, vfs, UNTRACKED_NORMAL);
const fully = await configBool(repo, 'core.quotepath', true);
const detached = head.branch !== null ? '' : await detachedLine(repo, head);
const upstream = await branchUpstream(repo, head, noCommits);
const prefix = await relativePrefix(repo, start);
const render = async (shown) => encodeText(longFormat(relativeEntries(await assembled(repo, shown), prefix), head.branch, detached, noCommits, state.merging, false, fully, upstream));
return { items: rows, render };
}
/**
* Show the working tree status.
*
* Three sources, compared pairwise: HEAD's tree against the index says what a
* commit would record, and the index against the working tree says what it would
* leave behind. Everything the report prints is one of those two answers, or a
* path neither side knows about. Each path is handed over as a row, so the
* report names and counts only the paths the session sees.
*/
export async function status(_accessor, paths, texts, ctx) {
const fl = new FlagView(ctx.flags);
const vfs = requireVfs(ctx);
const parsed = parseFlags(fl);
const repo = await opened(ctx, fl, true);
const head = await readHead(vfs, repo.location.gitdir);
const [rows, state, noCommits] = await collect(repo, vfs, parsed.untracked, parsed.ignored);
const fully = await configBool(repo, 'core.quotepath', true);
const prefix = parsed.porcelain ? '' : await relativePrefix(repo, startPoint(fl).virtual);
const upstream = await branchUpstream(repo, head, noCommits);
const detached = head.branch !== null ? '' : await detachedLine(repo, head);
const render = async (shown) => {
const entries = relativeEntries(await assembled(repo, shown), prefix);
const body = parsed.porcelain || parsed.short
? shortFormat(entries, parsed.branch ? branchLine(head.branch, noCommits, upstream) : null, fully)
: longFormat(entries, head.branch, detached, noCommits, state.merging, parsed.untracked === UNTRACKED_NO, fully, upstream);
return encodeText(body);
};
return [null, new IOResult({ rows: { items: rows, render } })];
}
//# sourceMappingURL=status.js.map