feat(app): checkout diff pane styling and e2e test support

- Style diff file cards edge-to-edge with single divider between files
- Remove monospace font from filenames for better readability
- Add checkout-ship e2e test for app
- Export Page type from e2e fixtures
- Add checkout query hooks for diff, status, and PR status
This commit is contained in:
Mohamed Boudra
2026-01-23 08:37:15 +07:00
parent 746d413082
commit 53155ebcc9
15 changed files with 1450 additions and 135 deletions
+18 -8
View File
@@ -113,16 +113,24 @@ describe("checkout git utilities", () => {
expect(message).toBe("worktree update");
});
it("merges the current branch into base", async () => {
writeFileSync(join(repoDir, "merge.txt"), "feature\n");
execSync("git checkout -b feature", { cwd: repoDir });
execSync("git add merge.txt", { cwd: repoDir });
execSync("git -c commit.gpgsign=false commit -m 'feature commit'", { cwd: repoDir });
const featureCommit = execSync("git rev-parse HEAD", { cwd: repoDir })
it("merges the current branch into base from a worktree checkout", async () => {
const worktree = await createWorktree({
branchName: "main",
cwd: repoDir,
worktreeSlug: "merge",
});
writeFileSync(join(worktree.worktreePath, "merge.txt"), "feature\n");
execSync("git checkout -b feature", { cwd: worktree.worktreePath });
execSync("git add merge.txt", { cwd: worktree.worktreePath });
execSync("git -c commit.gpgsign=false commit -m 'feature commit'", {
cwd: worktree.worktreePath,
});
const featureCommit = execSync("git rev-parse HEAD", { cwd: worktree.worktreePath })
.toString()
.trim();
await mergeToBase(repoDir, { baseRef: "main" });
await mergeToBase(worktree.worktreePath, { baseRef: "main" });
const baseContainsFeature = execSync(`git merge-base --is-ancestor ${featureCommit} main`, {
cwd: repoDir,
@@ -130,7 +138,9 @@ describe("checkout git utilities", () => {
});
expect(baseContainsFeature).toBeDefined();
const currentBranch = execSync("git rev-parse --abbrev-ref HEAD", { cwd: repoDir })
const currentBranch = execSync("git rev-parse --abbrev-ref HEAD", {
cwd: worktree.worktreePath,
})
.toString()
.trim();
expect(currentBranch).toBe("feature");
+76 -7
View File
@@ -98,6 +98,68 @@ async function getCurrentBranch(cwd: string): Promise<string | null> {
return branch.length > 0 ? branch : null;
}
async function getWorktreeRoot(cwd: string): Promise<string | null> {
try {
const { stdout } = await execAsync(
"git rev-parse --path-format=absolute --show-toplevel",
{ cwd, env: READ_ONLY_GIT_ENV }
);
const root = stdout.trim();
return root.length > 0 ? root : null;
} catch {
return null;
}
}
type GitWorktreeEntry = {
path: string;
branchRef?: string;
};
function parseWorktreeList(output: string): GitWorktreeEntry[] {
const entries: GitWorktreeEntry[] = [];
let current: GitWorktreeEntry | null = null;
for (const line of output.split("\n")) {
const trimmed = line.trim();
if (!trimmed) {
continue;
}
if (trimmed.startsWith("worktree ")) {
if (current) {
entries.push(current);
}
current = { path: trimmed.slice("worktree ".length).trim() };
continue;
}
if (current && trimmed.startsWith("branch ")) {
current.branchRef = trimmed.slice("branch ".length).trim();
}
}
if (current) {
entries.push(current);
}
return entries;
}
async function getWorktreePathForBranch(
cwd: string,
branchName: string
): Promise<string | null> {
try {
const { stdout } = await execAsync("git worktree list --porcelain", {
cwd,
env: READ_ONLY_GIT_ENV,
});
const entries = parseWorktreeList(stdout);
const ref = branchName.startsWith("refs/heads/")
? branchName
: `refs/heads/${branchName}`;
return entries.find((entry) => entry.branchRef === ref)?.path ?? null;
} catch {
return null;
}
}
export async function renameCurrentBranch(
cwd: string,
newName: string
@@ -295,19 +357,26 @@ export async function mergeToBase(cwd: string, options: MergeToBaseOptions = {})
if (!currentBranch) {
throw new Error("Unable to determine current branch for merge");
}
if (baseRef === currentBranch) {
let normalizedBaseRef = baseRef;
if (normalizedBaseRef.startsWith("origin/")) {
normalizedBaseRef = normalizedBaseRef.slice("origin/".length);
}
if (normalizedBaseRef === currentBranch) {
return;
}
const operationCwd = repoInfo.path;
const isSameCheckout = resolve(operationCwd) === resolve(cwd);
const currentWorktreeRoot = (await getWorktreeRoot(cwd)) ?? cwd;
const baseWorktree = await getWorktreePathForBranch(repoInfo.path, normalizedBaseRef);
const operationCwd = baseWorktree ?? currentWorktreeRoot;
const isSameCheckout = resolve(operationCwd) === resolve(currentWorktreeRoot);
const originalBranch = await getCurrentBranch(operationCwd);
const mode = options.mode ?? "merge";
try {
await execAsync(`git checkout ${baseRef}`, { cwd: operationCwd });
await execAsync(`git checkout ${normalizedBaseRef}`, { cwd: operationCwd });
if (mode === "squash") {
await execAsync(`git merge --squash ${currentBranch}`, { cwd: operationCwd });
const message = options.commitMessage ?? `Squash merge ${currentBranch} into ${baseRef}`;
const message =
options.commitMessage ?? `Squash merge ${currentBranch} into ${normalizedBaseRef}`;
await execFileAsync(
"git",
["-c", "commit.gpgsign=false", "commit", "-m", message],
@@ -354,7 +423,7 @@ export async function mergeToBase(cwd: string, options: MergeToBaseOptions = {})
// ignore
}
throw new MergeConflictError({
baseRef,
baseRef: normalizedBaseRef,
currentBranch,
conflictFiles: conflicts.length > 0 ? conflicts : [],
});
@@ -368,7 +437,7 @@ export async function mergeToBase(cwd: string, options: MergeToBaseOptions = {})
throw error;
} finally {
if (isSameCheckout && originalBranch && originalBranch !== baseRef) {
if (isSameCheckout && originalBranch && originalBranch !== normalizedBaseRef) {
try {
await execAsync(`git checkout ${originalBranch}`, { cwd: operationCwd });
} catch {