// @effect-diagnostics effect/strictEffectProvide:off
// @effect-diagnostics effect/anyUnknownInErrorContext:off
import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { Effect } from "effect";
import { BunServices } from "@effect/platform-bun";
import { GitService } from "../src/services/Git.js";
const DETECT_COMMIT_LIMIT = 2048;
const args = new Map(
process.argv.slice(2).map((arg) => {
const [key, value] = arg.split("=");
return [key ?? "", value ?? ""];
}),
);
const iterations = Number.parseInt(args.get("--iterations") ?? "5", 10);
const runCommand = async (cwd: string, command: string[]) => {
const proc = Bun.spawn(command, {
cwd,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
proc.exited,
]);
if (exitCode !== 0) {
throw new Error(stderr.trim() || `${command.join(" ")} failed`);
}
return stdout.trim();
};
const withCwd = async (cwd: string, run: () => Promise) => {
const previous = process.cwd();
process.chdir(cwd);
try {
return await run();
} finally {
process.chdir(previous);
}
};
const runGit = async (cwd: string, effect: Effect.Effect) =>
withCwd(cwd, async () =>
Effect.runPromise(
effect.pipe(Effect.provide(GitService.layer), Effect.provide(BunServices.layer)),
),
);
const writeCommit = async (cwd: string, file: string, message: string) => {
await Bun.write(join(cwd, file), `${message} ${Date.now()}\n`);
await runCommand(cwd, ["git", "add", file]);
await runCommand(cwd, ["git", "commit", "-m", message]);
};
const createFixtureRepo = async (name: string) => {
const cwd = await mkdtemp(join(tmpdir(), `stacked-detect-${name}-`));
await runCommand(cwd, ["git", "init", "-b", "main"]);
await runCommand(cwd, ["git", "config", "user.name", "Stacked Bench"]);
await runCommand(cwd, ["git", "config", "user.email", "bench@example.com"]);
await writeCommit(cwd, "README.md", "initial");
return cwd;
};
const createLinearFixture = async () => {
const cwd = await createFixtureRepo("linear");
let base = "main";
for (let i = 1; i <= 24; i++) {
const branch = `linear-${i}`;
await runCommand(cwd, ["git", "checkout", "-b", branch, base]);
await writeCommit(cwd, `${branch}.txt`, branch);
base = branch;
}
await runCommand(cwd, ["git", "checkout", "main"]);
return cwd;
};
const createWideFixture = async () => {
const cwd = await createFixtureRepo("wide");
await runCommand(cwd, ["git", "checkout", "-b", "root", "main"]);
await writeCommit(cwd, "root.txt", "root");
for (let i = 1; i <= 16; i++) {
const branch = `child-${i}`;
await runCommand(cwd, ["git", "checkout", "-b", branch, "root"]);
await writeCommit(cwd, `${branch}.txt`, branch);
}
await runCommand(cwd, ["git", "checkout", "main"]);
return cwd;
};
const createMixedFixture = async () => {
const cwd = await createFixtureRepo("mixed");
let base = "main";
for (let i = 1; i <= 10; i++) {
const branch = `stack-a-${i}`;
await runCommand(cwd, ["git", "checkout", "-b", branch, base]);
await writeCommit(cwd, `${branch}.txt`, branch);
base = branch;
}
await runCommand(cwd, ["git", "checkout", "main"]);
await writeCommit(cwd, "main-advance.txt", "main advance");
await runCommand(cwd, ["git", "checkout", "-b", "fork-root", "main"]);
await writeCommit(cwd, "fork-root.txt", "fork-root");
for (let i = 1; i <= 8; i++) {
const branch = `fork-${i}`;
await runCommand(cwd, ["git", "checkout", "-b", branch, "fork-root"]);
await writeCommit(cwd, `${branch}.txt`, branch);
}
await runCommand(cwd, ["git", "checkout", "main"]);
for (let i = 1; i <= 12; i++) {
const branch = `solo-${i}`;
await runCommand(cwd, ["git", "checkout", "-b", branch, "main"]);
await writeCommit(cwd, `${branch}.txt`, branch);
await runCommand(cwd, ["git", "checkout", "main"]);
}
return cwd;
};
const oldDetect = Effect.gen(function* () {
const git = yield* GitService;
const trunk = "main";
const candidates = (yield* git.listBranches()).filter((branch) => branch !== trunk);
const childOf = new Map();
yield* Effect.forEach(
candidates,
(branch) =>
Effect.gen(function* () {
const potentialAncestors = [trunk, ...candidates.filter((other) => other !== branch)];
const ancestryResults = yield* Effect.forEach(
potentialAncestors,
(other) =>
git.isAncestor(other, branch).pipe(
Effect.catchTag("GitError", () => Effect.succeed(false)),
Effect.map((is) => [other, is] as const),
),
{ concurrency: 8 },
);
const ancestors = ancestryResults.filter(([_, is]) => is).map(([name]) => name);
if (ancestors.length === 0) return;
let closest = ancestors[0] ?? trunk;
for (let i = 1; i < ancestors.length; i++) {
const candidate = ancestors[i];
if (candidate === undefined) continue;
const candidateIsCloser = yield* git
.isAncestor(closest, candidate)
.pipe(Effect.catchTag("GitError", () => Effect.succeed(false)));
if (candidateIsCloser) closest = candidate;
}
childOf.set(branch, closest);
}),
{ concurrency: 8 },
);
return childOf.size;
});
const newDetect = Effect.gen(function* () {
const git = yield* GitService;
const trunk = "main";
const candidates = (yield* git.listBranches()).filter((branch) => branch !== trunk);
const tipResults = yield* Effect.forEach(
candidates,
(branch) =>
git.revParse(branch).pipe(
Effect.map((oid) => [branch, oid] as const),
Effect.catchTag("GitError", () => Effect.succeed(null)),
),
{ concurrency: 8 },
);
const tipOwners = new Map();
for (const result of tipResults) {
if (result === null) continue;
const [branch, oid] = result;
const owners = tipOwners.get(oid) ?? [];
owners.push(branch);
tipOwners.set(oid, owners);
}
const childOf = new Map();
yield* Effect.forEach(
candidates,
(branch) =>
Effect.gen(function* () {
const commits = yield* git
.firstParentUniqueCommits(branch, trunk, { limit: DETECT_COMMIT_LIMIT })
.pipe(Effect.catchTag("GitError", () => Effect.succeed([])));
if (commits.length === 0) return;
let parent: string | null = null;
let ambiguous = false;
for (const oid of commits) {
const owners = (tipOwners.get(oid) ?? []).filter((owner) => owner !== branch);
if (owners.length > 1) {
ambiguous = true;
break;
}
const [owner] = owners;
if (owner !== undefined) {
parent = owner;
break;
}
}
if (ambiguous || (commits.length >= DETECT_COMMIT_LIMIT && parent === null)) {
return;
}
childOf.set(branch, parent ?? trunk);
}),
{ concurrency: 8 },
);
return childOf.size;
});
const average = (values: readonly number[]) =>
values.reduce((sum, value) => sum + value, 0) / values.length;
const timeEffect = async (cwd: string, effect: Effect.Effect) => {
const start = performance.now();
await runGit(cwd, effect);
return performance.now() - start;
};
const benchmarkFixture = async (cwd: string) => {
const oldSamples: number[] = [];
const newSamples: number[] = [];
for (let i = 0; i < iterations; i++) {
oldSamples.push(await timeEffect(cwd, oldDetect));
newSamples.push(await timeEffect(cwd, newDetect));
}
return {
oldMs: average(oldSamples),
newMs: average(newSamples),
};
};
const printRow = (fixture: string, result: { oldMs: number; newMs: number }) => {
const faster =
result.oldMs === result.newMs
? "tie"
: result.oldMs < result.newMs
? `old x${(result.newMs / result.oldMs).toFixed(2)}`
: `new x${(result.oldMs / result.newMs).toFixed(2)}`;
console.log(
`${fixture.padEnd(12)} ${result.oldMs.toFixed(2).padStart(9)} ${result.newMs.toFixed(2).padStart(9)} ${faster.padStart(10)}`,
);
};
const fixtures = [
{ name: "linear", create: createLinearFixture },
{ name: "wide", create: createWideFixture },
{ name: "mixed", create: createMixedFixture },
] as const;
console.log(`Detect benchmark iterations: ${iterations}`);
console.log("fixture old(ms) new(ms) faster");
const cleanup: Array<() => Promise> = [];
try {
for (const fixture of fixtures) {
const cwd = await fixture.create();
cleanup.push(() => rm(cwd, { recursive: true, force: true }));
const result = await benchmarkFixture(cwd);
printRow(fixture.name, result);
}
} finally {
for (const dispose of cleanup.reverse()) {
await dispose();
}
}