feat(FN-4476): complete Step 3 — add fully-subsumed branch conflict kind

Fusion-Task-Id: FN-4476
Fusion-Task-Lineage: 8673c1d9-f47e-4d02-89e6-646040b9be32
This commit is contained in:
Fusion
2026-05-14 08:16:34 -07:00
committed by gsxdsm
parent 69d35585cb
commit ab4729099c
2 changed files with 91 additions and 8 deletions

View File

@@ -102,6 +102,49 @@ describe("branch-conflicts", () => {
expect(result).toEqual({ kind: "stale-resolved" });
});
it("returns fully-subsumed when git cherry reports no unique commits", async () => {
mockedExecSync.mockImplementation((cmd: string | string[]) => {
const command = typeof cmd === "string" ? cmd : cmd[0];
if (command === "git worktree prune") return Buffer.from("");
if (command === "git worktree list --porcelain") {
return Buffer.from(["worktree /tmp/existing-wt", "HEAD 2222222", "branch refs/heads/fusion/fn-4068", ""].join("\n"));
}
if (command.includes("git rev-parse --verify 'refs/heads/fusion/fn-4068^{commit}'")) {
return Buffer.from("abc123def456\n");
}
if (command.includes("git rev-parse --verify 'fusion/fn-4068^{commit}'")) {
return Buffer.from("abc123def456\n");
}
if (command.includes("git rev-parse --verify 'main^{commit}'")) {
return Buffer.from("mainsha\n");
}
if (command === "git merge-base 'main' 'fusion/fn-4068'") {
return Buffer.from("base123\n");
}
if (command === "git cherry 'main' 'fusion/fn-4068' 'base123'") {
return Buffer.from("- abc111\n");
}
if (command.includes("git log --format=%H%x00%s%x00%b 'main..fusion/fn-4068'")) {
return Buffer.from("");
}
throw new Error(`Unexpected command: ${command}`);
});
const result = await inspectBranchConflict({
repoDir: "/tmp/repo",
branchName: "fusion/fn-4068",
conflictingWorktreePath: "/tmp/existing-wt",
requestingTaskId: "FN-4068",
startPoint: "main",
});
expect(result).toEqual({
kind: "fully-subsumed",
livePath: "/tmp/existing-wt",
tipSha: "abc123def456",
});
});
it("returns reclaimable for same-task live conflicts with stranded commits", async () => {
mockedExecSync.mockImplementation((cmd: string | string[]) => {
const command = typeof cmd === "string" ? cmd : cmd[0];
@@ -115,8 +158,26 @@ describe("branch-conflicts", () => {
if (command.includes("git rev-parse --verify 'fusion/fn-4068^{commit}'")) {
return Buffer.from("abc123def456\n");
}
if (command.includes("git log --reverse --format=%H%x09%s 'main..fusion/fn-4068'")) {
return Buffer.from("aaa111\tPreserve prior fix\nbbb222\tAdd regression coverage\n");
if (command.includes("git rev-parse --verify 'main^{commit}'")) {
return Buffer.from("mainsha\n");
}
if (command === "git merge-base 'main' 'fusion/fn-4068'") {
return Buffer.from("base123\n");
}
if (command === "git cherry 'main' 'fusion/fn-4068' 'base123'") {
return Buffer.from("+ aaa111\n+ bbb222\n");
}
if (command.includes("git rev-parse --verify 'aaa111^{commit}'")) {
return Buffer.from("aaa111\n");
}
if (command.includes("git rev-parse --verify 'bbb222^{commit}'")) {
return Buffer.from("bbb222\n");
}
if (command === "git log -1 --format=%s 'aaa111'") {
return Buffer.from("Preserve prior fix\n");
}
if (command === "git log -1 --format=%s 'bbb222'") {
return Buffer.from("Add regression coverage\n");
}
if (command.includes("git log --format=%H%x00%s%x00%b 'main..fusion/fn-4068'")) {
return Buffer.from("aaa111\u0000feat(FN-4068): preserve\u0000Fusion-Task-Id: FN-4068\u0000" +
@@ -161,8 +222,20 @@ describe("branch-conflicts", () => {
if (command.includes("git rev-parse --verify 'fusion/fn-4068^{commit}'")) {
return Buffer.from("abc123def456\n");
}
if (command.includes("git log --reverse --format=%H%x09%s 'main..fusion/fn-4068'")) {
return Buffer.from("aaa111\tPreserve prior fix\n");
if (command.includes("git rev-parse --verify 'main^{commit}'")) {
return Buffer.from("mainsha\n");
}
if (command === "git merge-base 'main' 'fusion/fn-4068'") {
return Buffer.from("base123\n");
}
if (command === "git cherry 'main' 'fusion/fn-4068' 'base123'") {
return Buffer.from("+ aaa111\n");
}
if (command.includes("git rev-parse --verify 'aaa111^{commit}'")) {
return Buffer.from("aaa111\n");
}
if (command === "git log -1 --format=%s 'aaa111'") {
return Buffer.from("Preserve prior fix\n");
}
if (command.includes("git log --format=%H%x00%s%x00%b 'main..fusion/fn-4068'")) {
return Buffer.from("aaa111\u0000feat(FN-9999): foreign\u0000Fusion-Task-Id: FN-9999\u0000");
@@ -183,6 +256,7 @@ describe("branch-conflicts", () => {
throw new Error("expected live-foreign conflict");
}
expect(result.error).toBeInstanceOf(BranchConflictError);
expect(result.error.message).toContain("1 stranded commit since main");
expect(result.error.message).toContain("Run branch recovery");
});

View File

@@ -101,6 +101,7 @@ export interface InspectBranchConflictInput {
export type BranchConflictInspectionResult =
| { kind: "stale" }
| { kind: "stale-resolved" }
| { kind: "fully-subsumed"; livePath: string; tipSha: string }
| { kind: "reclaimable"; livePath: string; tipSha: string; taskAttributedCommitCount: number; strandedCommits: BranchConflictCommit[] }
| { kind: "live-foreign"; livePath: string; error: BranchConflictError };
@@ -516,20 +517,28 @@ export async function inspectBranchConflict(
}
const existingTipSha = await revParse(input.repoDir, input.branchName);
const strandedCommits = await listStrandedCommits(input.repoDir, startPoint, input.branchName);
const uniqueCommitResult = await listUniqueBranchCommits(input.repoDir, startPoint, input.branchName);
const taskAttributedCommitCount = await countTaskAttributedCommits(
input.repoDir,
`${startPoint}..${input.branchName}`,
input.requestingTaskId,
);
if (!uniqueCommitResult.degraded && uniqueCommitResult.commits.length === 0) {
return {
kind: "fully-subsumed",
livePath,
tipSha: existingTipSha,
};
}
if (taskAttributedCommitCount > 0) {
return {
kind: "reclaimable",
livePath,
tipSha: existingTipSha,
taskAttributedCommitCount,
strandedCommits,
strandedCommits: uniqueCommitResult.commits,
};
}
@@ -540,8 +549,8 @@ export async function inspectBranchConflict(
branchName: input.branchName,
conflictingWorktreePath: livePath,
existingTipSha,
strandedCommits,
startPoint,
strandedCommits: uniqueCommitResult.commits,
startPoint: uniqueCommitResult.mainRef,
recommendedAction: "Run branch recovery and explicitly choose whether to reclaim or discard prior work.",
}),
};