Handle unborn repos in changes diff

This commit is contained in:
Mohamed Boudra
2026-05-15 15:08:41 +07:00
parent 1f79c4d039
commit 9bf36cdb56
2 changed files with 58 additions and 4 deletions
@@ -204,6 +204,41 @@ describe("checkout git utilities", () => {
expect(branch).toBeNull();
});
it("returns untracked files in an uncommitted diff before the first commit", async () => {
const unbornRepo = join(tempDir, "unborn-repo");
mkdirSync(unbornRepo, { recursive: true });
execFileSync("git", ["init", "-b", "main"], { cwd: unbornRepo });
writeFileSync(join(unbornRepo, "greeting.txt"), "hello\n");
const diff = await getCheckoutDiff(unbornRepo, {
mode: "uncommitted",
includeStructured: true,
});
expect(diff.structured).toEqual([
{
path: "greeting.txt",
isNew: true,
isDeleted: false,
additions: 1,
deletions: 0,
hunks: [
{
oldStart: 0,
oldCount: 0,
newStart: 1,
newCount: 1,
lines: [
{ type: "header", content: "@@ -0,0 +1 @@" },
{ type: "add", content: "hello" },
],
},
],
status: "ok",
},
]);
});
it("returns the branch being rebased when HEAD is detached during a rebase", async () => {
execFileSync("git", ["checkout", "-b", "feature/rebase-test"], { cwd: repoDir });
writeFileSync(join(repoDir, "file.txt"), "feature\n");
+23 -4
View File
@@ -539,6 +539,15 @@ function buildGitDiffArgs(args: { ignoreWhitespace?: boolean; extra: string[] })
const TRACKED_DIFF_NUMSTAT_MAX_BYTES = 2 * 1024 * 1024; // 2MB
const TRACKED_MAX_CHANGED_LINES = 40_000;
const EMPTY_TREE_OBJECT_ID = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
function isUnbornHeadDiffError(error: unknown): boolean {
return (
error instanceof Error &&
error.message.includes("--name-status HEAD") &&
error.message.includes("ambiguous argument 'HEAD'")
);
}
async function getTrackedNumstatByPath(
cwd: string,
@@ -1926,7 +1935,17 @@ export async function getCheckoutDiff(
}
const ignoreWhitespace = compare.ignoreWhitespace === true;
const changes = await listCheckoutFileChanges(cwd, refsForDiff, ignoreWhitespace);
let effectiveRefsForDiff = refsForDiff;
let changes: CheckoutFileChange[];
try {
changes = await listCheckoutFileChanges(cwd, effectiveRefsForDiff, ignoreWhitespace);
} catch (error) {
if (!isUnbornHeadDiffError(error)) {
throw error;
}
effectiveRefsForDiff = { ...refsForDiff, baseRef: EMPTY_TREE_OBJECT_ID };
changes = await listCheckoutFileChanges(cwd, effectiveRefsForDiff, ignoreWhitespace);
}
changes.sort((a, b) => {
if (a.path === b.path) return 0;
return a.path < b.path ? -1 : 1;
@@ -1957,7 +1976,7 @@ export async function getCheckoutDiff(
const trackedNumstatByPath =
trackedChanges.length > 0
? await getTrackedNumstatByPath(cwd, refsForDiff, ignoreWhitespace)
? await getTrackedNumstatByPath(cwd, effectiveRefsForDiff, ignoreWhitespace)
: new Map<string, FileStat>();
const trackedDiffPaths: string[] = [];
const trackedPlaceholderByPath = new Map<
@@ -1984,7 +2003,7 @@ export async function getCheckoutDiff(
const trackedDiffResult = await runGitCommand(
buildGitDiffArgs({
ignoreWhitespace,
extra: [...getCheckoutDiffRefArgs(refsForDiff), "--", ...trackedDiffPaths],
extra: [...getCheckoutDiffRefArgs(effectiveRefsForDiff), "--", ...trackedDiffPaths],
}),
{
cwd,
@@ -2020,7 +2039,7 @@ export async function getCheckoutDiff(
trackedPlaceholderByPath,
trackedDiffText,
trackedDiffTruncated,
refsForDiff,
refsForDiff: effectiveRefsForDiff,
ignoreWhitespace,
structured,
appendDiff,