mirror of
https://github.com/morluto/rea.git
synced 2026-11-03 21:36:13 +00:00
* fix(reference): Exclude Git pointer files from source identities Match .git pointer files in the default import policy after the existing directory rules, preserving ordinary-checkout exclusion provenance and Git HEAD discovery. Cover linked-worktree identities, repeated imports, nested metadata, similarly named source files, and configured-secret precedence. Independent review: parent independent review APPROVE for Draft CI on patch 61bddf108952c77858fa71c333b4bb8a05ab33e8b03f5c6bccd879cd19c7c379 and source tree2d2372a9ec. Review triggers: delegated shipping output, source inventory/file I/O behavior, and more than 50 lines. Validation: npm run check passed with Node 24.22.1/npm 11.16.0 on Windows; tracked Vitest regression 1 passed/3 Windows skips; test discovery passed. Eight offline Linux production imports passed on cached Node 22.19.0 as supporting evidence only. Pinned Linux/macOS Vitest cases await hosted CI. Fixes #7551 * test(reference): Isolate worktree fixtures from host Git settings Route every fixture Git command through one wrapper with a filtered child environment, owned empty global config/template/hook paths, and no system config reads. Keep resource cleanup under the existing temporary owner. These settings are scoped to synthetic child processes; host Git settings and this repository's commit/push checks are unchanged. Targeted independent review: parent review APPROVE on patch 4be9a2af3e40edac3d64f687170e966a87fcf762f1877f2c55016e120bcd90ac and treea2ba91ee9c. The reviewer confirmed the native proof helper hash, complete fixture command coverage, and owned cleanup. Review triggers: delegated follow-up and fixture file/process boundaries. Validation: npm run check:fast passed; targeted Vitest 1 passed/3 native Windows skips. On pinned Node 24.22.1, the actual helper rejects inherited hooks/templates while commit, worktree creation, HEAD and status checks pass. The unisolated control copies the injected template and its commit fails. Refs #7551
209 lines
7.1 KiB
TypeScript
209 lines
7.1 KiB
TypeScript
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import ignore from "ignore";
|
|
import { describe, expect, it } from "vitest";
|
|
import { DEFAULT_REFERENCE_SOURCE_IGNORE_PATTERNS } from "../../../src/application/ReferenceSourceImportTypes.js";
|
|
import { createHistoricalSourceManifest } from "../../../src/domain/referenceSourceGraph.js";
|
|
import { execFileOutput } from "../../../src/process/ExecFileOutput.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
import { importReferenceSource } from "../../support/referenceSourceResourceScope.js";
|
|
|
|
const importTree = async (
|
|
root: string,
|
|
secretPatterns: readonly string[] = [],
|
|
excludePaths: readonly string[] = [],
|
|
) => {
|
|
const result = await importReferenceSource({
|
|
root,
|
|
caller: "git-metadata-test",
|
|
policy: { secretPatterns },
|
|
excludePaths,
|
|
});
|
|
if (!result.ok) throw result.error;
|
|
return result.value;
|
|
};
|
|
|
|
const fixtureGit = (
|
|
directory: string,
|
|
configuration: string,
|
|
args: readonly string[],
|
|
) =>
|
|
execFileOutput(
|
|
"git",
|
|
["-c", `core.hooksPath=${join(configuration, "no-hooks")}`, ...args],
|
|
{
|
|
cwd: directory,
|
|
env: {
|
|
...Object.fromEntries(
|
|
Object.entries(process.env).filter(
|
|
([name]) => !name.toUpperCase().startsWith("GIT_"),
|
|
),
|
|
),
|
|
GIT_CONFIG_GLOBAL: join(configuration, "gitconfig"),
|
|
GIT_CONFIG_NOSYSTEM: "1",
|
|
GIT_TEMPLATE_DIR: join(configuration, "no-template"),
|
|
},
|
|
},
|
|
);
|
|
|
|
it("excludes Git metadata files and directories without broadening directory-only defaults", () => {
|
|
const matcher = ignore().add([...DEFAULT_REFERENCE_SOURCE_IGNORE_PATTERNS]);
|
|
for (const path of [".git", ".git/", "nested/.git", "nested/.git/"])
|
|
expect(matcher.ignores(path), path).toBe(true);
|
|
for (const path of [".gitignore", ".github", ".gitkeep", "dist"])
|
|
expect(matcher.ignores(path), path).toBe(false);
|
|
expect(matcher.ignores("dist/")).toBe(true);
|
|
expect(matcher.test(".git").rule?.pattern).toBe(".git");
|
|
expect(matcher.test(".git/").rule?.pattern).toBe(".git/");
|
|
});
|
|
|
|
describe.skipIf(process.platform === "win32")(
|
|
"reference source Git metadata exclusions",
|
|
() => {
|
|
it("keeps linked-worktree identities stable while retaining HEAD and ordinary-checkout provenance", async () => {
|
|
const root = await createTestTempDirectory("rea-reference-git-metadata-");
|
|
const repository = join(root, "repository");
|
|
await Promise.all([
|
|
mkdir(join(root, "no-hooks")),
|
|
mkdir(join(root, "no-template")),
|
|
writeFile(join(root, "gitconfig"), ""),
|
|
]);
|
|
await mkdir(join(repository, "src"), { recursive: true });
|
|
await writeFile(
|
|
join(repository, "src", "main.js"),
|
|
"export const value = 7;\n",
|
|
);
|
|
await writeFile(join(repository, ".gitignore"), "*.log\n");
|
|
const git = (...args: string[]) => fixtureGit(repository, root, args);
|
|
await git("init", "--initial-branch=main");
|
|
await git("add", ".gitignore", "src/main.js");
|
|
await git(
|
|
"-c",
|
|
"user.name=REA fixture",
|
|
"-c",
|
|
"user.email=fixture@example.invalid",
|
|
"-c",
|
|
"commit.gpgsign=false",
|
|
"commit",
|
|
"-m",
|
|
"fixture",
|
|
);
|
|
const head = (await git("rev-parse", "HEAD")).stdout.trim();
|
|
const leftRoot = join(root, "left");
|
|
const rightRoot = join(root, "right");
|
|
for (const worktree of [leftRoot, rightRoot]) {
|
|
await git("worktree", "add", "--detach", worktree, head);
|
|
expect(
|
|
(await fixtureGit(worktree, root, ["status", "--porcelain"])).stdout,
|
|
).toBe("");
|
|
}
|
|
expect(await readFile(join(leftRoot, ".git"), "utf8")).not.toBe(
|
|
await readFile(join(rightRoot, ".git"), "utf8"),
|
|
);
|
|
const importSelectedTree = async (source: string) => {
|
|
const graph = await importTree(source);
|
|
expect(graph.vcs).toEqual({ kind: "git", head, dirty: null });
|
|
expect(
|
|
graph.entries
|
|
.filter(({ kind }) => kind === "file")
|
|
.map(({ path }) => path),
|
|
).toEqual([".gitignore", "src/main.js"]);
|
|
expect(
|
|
graph.entries.some(
|
|
({ path }) => path === ".git" || path.startsWith(".git/"),
|
|
),
|
|
).toBe(false);
|
|
return graph;
|
|
};
|
|
const left = await importSelectedTree(leftRoot);
|
|
const right = await importSelectedTree(rightRoot);
|
|
const repeated = await importSelectedTree(leftRoot);
|
|
expect(createHistoricalSourceManifest(left)).toEqual(
|
|
createHistoricalSourceManifest(right),
|
|
);
|
|
expect(createHistoricalSourceManifest(repeated)).toEqual(
|
|
createHistoricalSourceManifest(left),
|
|
);
|
|
for (const graph of [left, right])
|
|
expect(graph.exclusions).toEqual([
|
|
{
|
|
path: ".git",
|
|
entry_kind: "file",
|
|
reason: "default-ignored",
|
|
pattern: ".git",
|
|
},
|
|
]);
|
|
const ordinary = await importSelectedTree(repository);
|
|
expect(ordinary.exclusions).toEqual([
|
|
{
|
|
path: ".git",
|
|
entry_kind: "directory",
|
|
reason: "default-ignored",
|
|
pattern: ".git/",
|
|
},
|
|
]);
|
|
});
|
|
|
|
it.each(["file", "directory"] as const)(
|
|
"prunes nested .git %s metadata and preserves configured-secret precedence",
|
|
async (kind) => {
|
|
const root = await createTestTempDirectory("rea-reference-nested-git-");
|
|
const nested = join(root, "nested");
|
|
await mkdir(nested);
|
|
if (kind === "file")
|
|
await writeFile(
|
|
join(nested, ".git"),
|
|
"gitdir: /checkout-specific/metadata\n",
|
|
);
|
|
else {
|
|
await mkdir(join(nested, ".git", "hooks"), { recursive: true });
|
|
await writeFile(
|
|
join(nested, ".git", "hooks", "pre-commit"),
|
|
"metadata\n",
|
|
);
|
|
}
|
|
for (const name of [
|
|
".gitignore",
|
|
".github",
|
|
".gitkeep",
|
|
"dist",
|
|
"source.js",
|
|
])
|
|
await writeFile(join(nested, name), "");
|
|
for (const secretPatterns of [[], [".git"]]) {
|
|
const graph = await importTree(
|
|
root,
|
|
secretPatterns,
|
|
secretPatterns.length === 0 ? [] : ["!.git"],
|
|
);
|
|
expect(graph.exclusions).toEqual([
|
|
{
|
|
path: "nested/.git",
|
|
entry_kind: kind,
|
|
reason:
|
|
secretPatterns.length === 0
|
|
? "default-ignored"
|
|
: "configured-secret",
|
|
pattern:
|
|
secretPatterns.length === 0 && kind === "directory"
|
|
? ".git/"
|
|
: ".git",
|
|
},
|
|
]);
|
|
expect(
|
|
graph.entries
|
|
.filter(({ kind }) => kind === "file")
|
|
.map(({ path }) => path),
|
|
).toEqual([
|
|
"nested/.github",
|
|
"nested/.gitignore",
|
|
"nested/.gitkeep",
|
|
"nested/dist",
|
|
"nested/source.js",
|
|
]);
|
|
}
|
|
},
|
|
);
|
|
},
|
|
);
|