Files
awesome-copilot/extensions/git-worktree-explorer/git-data.test.mjs
T

375 lines
16 KiB
JavaScript

import assert from "node:assert/strict";
import test from "node:test";
import {
describeDefaultBranch,
gatherCommitDetails,
gatherCommits,
gatherGraphCommits,
gatherRepository,
isDefaultBranch,
isSameRepositoryPullRequest,
mapWithConcurrency,
normalizeRemoteUrl,
parseBranchRecords,
parseCommitRecords,
parseDivergence,
parseTracking,
parseWorktreePorcelain,
resolveDefaultBranch,
} from "./git-data.mjs";
test("parses linked, detached, and locked worktrees", () => {
const worktrees = parseWorktreePorcelain([
"worktree C:/repos/main",
"HEAD 1111111111111111111111111111111111111111",
"branch refs/heads/main",
"",
"worktree C:/repos/feature",
"HEAD 2222222222222222222222222222222222222222",
"detached",
"locked in use",
"",
].join("\n"));
assert.deepEqual(worktrees, [
{
path: "C:/repos/main",
head: "1111111111111111111111111111111111111111",
branch: "main",
detached: false,
bare: false,
locked: false,
prunable: false,
},
{
path: "C:/repos/feature",
head: "2222222222222222222222222222222222222222",
branch: null,
detached: true,
bare: false,
locked: "in use",
prunable: false,
},
]);
});
test("parses branch tracking and excludes symbolic remote HEAD", () => {
const separator = "\x1f";
const branches = parseBranchRecords([
["refs/heads/main", "main", "a".repeat(40), "origin/main", "[ahead 2, behind 3]", "2026-01-02T03:04:05Z", "Main"].join(separator),
["refs/remotes/origin/HEAD", "origin/HEAD", "a".repeat(40), "", "", "", ""].join(separator),
["refs/remotes/origin/main", "origin/main", "a".repeat(40), "", "", "2026-01-02T03:04:05Z", "Main"].join(separator),
].join("\n"));
assert.equal(branches.length, 2);
assert.deepEqual(branches[0].tracking, { ahead: 2, behind: 3, gone: false });
assert.equal(branches[1].remote, true);
assert.deepEqual(parseTracking("[gone]"), { ahead: 0, behind: 0, gone: true });
});
test("parses commit records with parents and timestamps", () => {
const separator = "\x1f";
const recordSeparator = "\x1e";
const output = [
"a".repeat(40),
"aaaaaaaa",
`${"b".repeat(40)} ${"c".repeat(40)}`,
"Ada",
"ada@example.com",
"2026-01-01T00:00:00Z",
"2026-01-01T01:00:00Z",
"Merge topic",
].join(separator) + recordSeparator;
const [commit] = parseCommitRecords(output);
assert.equal(commit.shortSha, "aaaaaaaa");
assert.equal(commit.parents.length, 2);
assert.equal(commit.subject, "Merge topic");
});
test("parses branch divergence from git rev-list output", () => {
assert.deepEqual(parseDivergence("3\t7"), { ahead: 7, behind: 3 });
assert.equal(parseDivergence("invalid"), null);
});
test("resolves a remote default branch when origin HEAD is unavailable", () => {
const branches = [
{ ref: "refs/heads/main", remote: false },
{ ref: "refs/remotes/origin/main", remote: true },
];
assert.equal(resolveDefaultBranch(null, branches), "refs/remotes/origin/main");
assert.equal(resolveDefaultBranch("refs/remotes/upstream/trunk", branches), "refs/remotes/upstream/trunk");
assert.equal(resolveDefaultBranch(null, [{ ref: "refs/heads/main", remote: false }]), null);
});
test("normalizes supported GitHub remote URL forms", () => {
assert.deepEqual(normalizeRemoteUrl("git@github.com:octo/repo.git"), {
raw: "git@github.com:octo/repo.git",
host: "github.com",
owner: "octo",
repo: "repo",
github: true,
webUrl: "https://github.com/octo/repo",
});
assert.equal(normalizeRemoteUrl("https://github.com/octo/repo.git").repo, "repo");
assert.equal(normalizeRemoteUrl("not a remote"), null);
assert.equal(normalizeRemoteUrl("https://github.com/too/many/parts"), null);
});
test("repository snapshot creates a virtual group for branches without worktrees", async () => {
const root = process.cwd();
const sha = "a".repeat(40);
const separator = "\x1f";
const runner = async (command, args) => {
const key = `${command} ${args.join(" ")}`;
if (key === "git rev-parse --show-toplevel") return { stdout: root, stderr: "" };
if (key === "git rev-parse --git-common-dir") return { stdout: ".git", stderr: "" };
if (key === "git rev-parse --verify HEAD") return { stdout: sha, stderr: "" };
if (key === "git remote get-url origin") return { stdout: "git@github.com:octo/repo.git", stderr: "" };
if (key === "git symbolic-ref --quiet refs/remotes/origin/HEAD") {
return { stdout: "refs/remotes/origin/main", stderr: "" };
}
if (key.startsWith("git status ")) return { stdout: "## main...origin/main\n M file.txt", stderr: "" };
if (key === "git worktree list --porcelain") {
return { stdout: `worktree ${root}\nHEAD ${sha}\nbranch refs/heads/main\n`, stderr: "" };
}
if (key.startsWith("git for-each-ref ") && key.includes("ahead-behind")) {
const error = new Error("unknown field name: ahead-behind");
return { stdout: "", stderr: error.message, error };
}
if (key.startsWith("git for-each-ref ")) {
return {
stdout: [
["refs/heads/main", "main", sha, "origin/main", "", "2026-01-01T00:00:00Z", "Main"].join(separator),
["refs/heads/topic", "topic", sha, "", "", "2026-01-01T00:00:00Z", "Topic"].join(separator),
].join("\n"),
stderr: "",
};
}
if (key.startsWith("git rev-list --left-right --count ")) {
return { stdout: key.includes("refs/heads/topic") ? "4\t2" : "0\t0", stderr: "" };
}
if (key.startsWith("gh pr list ")) {
const error = new Error("not found");
error.code = "ENOENT";
return { stdout: "", stderr: "not found", error };
}
throw new Error(`Unexpected command: ${key}`);
};
const snapshot = await gatherRepository(root, { commandRunner: runner });
assert.equal(snapshot.repository.dirty, true);
assert.equal(snapshot.repository.defaultBranch, "refs/remotes/origin/main");
assert.equal(snapshot.github.status, "unavailable");
assert.equal(snapshot.worktrees.length, 2);
assert.deepEqual(snapshot.worktrees[1].branchIds, ["branch:topic"]);
assert.equal(snapshot.branches[0].worktrees[0], root);
assert.equal(snapshot.branches[0].isDefault, true);
assert.equal(snapshot.branches[1].isDefault, false);
assert.equal(snapshot.repository.defaultBranchName, "main");
assert.deepEqual(snapshot.branches[1].defaultTracking, { ahead: 2, behind: 4 });
});
test("branch divergence uses a single for-each-ref query when Git supports ahead-behind", async () => {
const root = process.cwd();
const sha = "a".repeat(40);
const separator = "\x1f";
const commands = [];
const runner = async (command, args) => {
const key = `${command} ${args.join(" ")}`;
commands.push(key);
if (key === "git rev-parse --show-toplevel") return { stdout: root, stderr: "" };
if (key === "git rev-parse --git-common-dir") return { stdout: ".git", stderr: "" };
if (key === "git rev-parse --verify HEAD") return { stdout: sha, stderr: "" };
if (key === "git remote get-url origin") return { stdout: "", stderr: "", error: new Error("none") };
if (key === "git symbolic-ref --quiet refs/remotes/origin/HEAD") {
return { stdout: "refs/remotes/origin/feature/x", stderr: "" };
}
if (key.startsWith("git status ")) return { stdout: "## x", stderr: "" };
if (key === "git worktree list --porcelain") {
return { stdout: `worktree ${root}\nHEAD ${sha}\nbranch refs/heads/x\n`, stderr: "" };
}
if (key.includes("ahead-behind")) {
assert.ok(args.some((arg) => arg.includes("%(ahead-behind:refs/remotes/origin/feature/x)")));
return {
stdout: [
`refs/heads/feature/x${separator}0 0`,
`refs/heads/x${separator}3 1`,
].join("\n"),
stderr: "",
};
}
if (key.startsWith("git for-each-ref ")) {
return {
stdout: [
["refs/heads/feature/x", "feature/x", sha, "origin/feature/x", "", "2026-01-01T00:00:00Z", "Default"].join(separator),
["refs/heads/x", "x", sha, "", "", "2026-01-01T00:00:00Z", "Suffix"].join(separator),
].join("\n"),
stderr: "",
};
}
throw new Error(`Unexpected command: ${key}`);
};
const snapshot = await gatherRepository(root, { commandRunner: runner });
assert.ok(!commands.some((key) => key.startsWith("git rev-list ")), "should not spawn per-branch rev-list");
const byName = Object.fromEntries(snapshot.branches.map((branch) => [branch.name, branch]));
assert.deepEqual(byName["feature/x"].defaultTracking, { ahead: 0, behind: 0 });
assert.deepEqual(byName.x.defaultTracking, { ahead: 3, behind: 1 });
assert.equal(byName["feature/x"].isDefault, true);
assert.equal(byName.x.isDefault, false, "suffix of the default branch name must not be marked default");
});
test("per-branch divergence fallback is bounded to a small concurrency", async () => {
const defaultBranch = "refs/remotes/origin/main";
const branches = Array.from({ length: 40 }, (_, index) => ({ ref: `refs/heads/b${index}`, name: `b${index}` }));
let active = 0;
let peak = 0;
const runner = async (_command, args) => {
if (args.includes("for-each-ref")) {
return { stdout: "", stderr: "", error: new Error("old git") };
}
active++;
peak = Math.max(peak, active);
await new Promise((resolve) => setTimeout(resolve, 2));
active--;
return { stdout: "1\t2", stderr: "" };
};
const results = await mapWithConcurrency(branches, 8, async (branch) => {
const result = await runner("git", ["rev-list", branch.ref]);
return { ...branch, defaultTracking: result.error ? null : { ahead: 2, behind: 1 } };
});
assert.equal(results.length, 40);
assert.ok(peak <= 8, `peak concurrency was ${peak}`);
assert.deepEqual(results[39].defaultTracking, { ahead: 2, behind: 1 });
assert.equal(defaultBranch, "refs/remotes/origin/main");
});
test("default branch detection compares full branch names and upstreams", () => {
const info = describeDefaultBranch("refs/remotes/origin/feature/x");
assert.deepEqual(info, { ref: "refs/remotes/origin/feature/x", short: "origin/feature/x", name: "feature/x" });
assert.equal(isDefaultBranch({ name: "feature/x", upstream: null }, info), true);
assert.equal(isDefaultBranch({ name: "x", upstream: null }, info), false);
assert.equal(isDefaultBranch({ name: "local-main", upstream: "origin/feature/x" }, info), true);
assert.equal(isDefaultBranch({ name: "feature/x", upstream: "upstream/feature/x" }, info), false);
assert.equal(isDefaultBranch({ name: "main" }, null), false);
});
test("pull requests from forks are not attached to same-named local branches", () => {
const remote = { owner: "octo", repo: "repo" };
assert.equal(isSameRepositoryPullRequest({ headRefName: "main", isCrossRepository: true }, remote), false);
assert.equal(isSameRepositoryPullRequest({
headRefName: "main",
isCrossRepository: false,
headRepositoryOwner: { login: "Octo" },
}, remote), true);
assert.equal(isSameRepositoryPullRequest({
headRefName: "main",
headRepositoryOwner: { login: "contributor" },
}, remote), false);
assert.equal(isSameRepositoryPullRequest({ headRefName: "main" }, remote), true);
});
test("commit details run only metadata and file listing commands", async () => {
const separator = "\x1f";
const commands = [];
const sha = "a".repeat(40);
const details = await gatherCommitDetails(process.cwd(), sha, null, {
commandRunner: async (_command, args) => {
commands.push(args[0]);
if (args[0] === "show") {
return {
stdout: [sha, "aaaaaaaa", "", "Ada", "ada@example.com", "2026", "2026", "Subject", "Body"].join(separator),
stderr: "",
};
}
return { stdout: "M\tsrc/app.js", stderr: "" };
},
});
assert.deepEqual(commands.sort(), ["diff-tree", "show"]);
assert.equal(details.summary, undefined);
assert.deepEqual(details.files, [{ status: "M", path: "src/app.js" }]);
});
test("commit pagination returns a cursor only when more records exist", async () => {
const separator = "\x1f";
const recordSeparator = "\x1e";
const output = Array.from({ length: 51 }, (_, index) => [
String(index).padStart(40, "a"),
String(index).padStart(8, "a"),
"",
"Ada",
"ada@example.com",
"2026-01-01T00:00:00Z",
"2026-01-01T00:00:00Z",
`Commit ${index}`,
].join(separator) + recordSeparator).join("");
let receivedArgs;
const runner = async (_command, args) => {
receivedArgs = args;
return { stdout: output, stderr: "" };
};
const page = await gatherCommits(
process.cwd(),
"refs/heads/topic",
"refs/remotes/origin/main",
0,
50,
{ commandRunner: runner },
);
assert.equal(page.commits.length, 50);
assert.equal(page.nextOffset, 50);
assert.equal(page.comparisonBase, "refs/remotes/origin/main");
assert.equal(page.comparisonUnavailable, false);
assert.deepEqual(receivedArgs.slice(-4), [
"refs/heads/topic",
"--not",
"refs/remotes/origin/main",
"--",
]);
});
test("combined graph uses all local branch refs in topological order", async () => {
let receivedArgs;
const runner = async (_command, args) => {
receivedArgs = args;
return { stdout: "", stderr: "" };
};
const page = await gatherGraphCommits(
process.cwd(),
["refs/heads/main", "refs/heads/topic", "refs/remotes/origin/main"],
0,
100,
{ commandRunner: runner },
);
assert.equal(page.commits.length, 0);
assert.ok(receivedArgs.includes("--topo-order"));
assert.ok(receivedArgs.includes("--date-order"));
assert.ok(receivedArgs.includes("refs/heads/main"));
assert.ok(receivedArgs.includes("refs/heads/topic"));
assert.ok(!receivedArgs.includes("refs/remotes/origin/main"));
});
test("combined graph accepts pinned commit tips for stable pagination", async () => {
const tip = "a".repeat(40);
let receivedArgs;
const runner = async (_command, args) => {
receivedArgs = args;
return { stdout: "", stderr: "" };
};
await gatherGraphCommits(process.cwd(), [tip, "--all"], 100, 100, { commandRunner: runner });
assert.ok(receivedArgs.includes(tip));
assert.ok(!receivedArgs.includes("--all"));
assert.ok(receivedArgs.includes("--skip=100"));
});
test("commit details reject non-SHA revisions before executing Git", async () => {
await assert.rejects(
gatherCommitDetails(process.cwd(), "--all", null, {
commandRunner: async () => {
throw new Error("should not run");
},
}),
/Invalid commit SHA/,
);
});