mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
Fix committed diff excluding dirty worktree
This commit is contained in:
@@ -615,6 +615,41 @@ const x = 1;
|
||||
expect(diff.diff).toContain("feature.txt");
|
||||
});
|
||||
|
||||
it("does not include dirty working tree changes in base mode", async () => {
|
||||
writeFileSync(join(repoDir, "file.txt"), "dirty\n");
|
||||
writeFileSync(join(repoDir, "untracked.txt"), "untracked\n");
|
||||
|
||||
const diff = await getCheckoutDiff(repoDir, {
|
||||
mode: "base",
|
||||
baseRef: "main",
|
||||
includeStructured: true,
|
||||
});
|
||||
|
||||
expect(diff.diff).toBe("");
|
||||
expect(diff.structured).toEqual([]);
|
||||
});
|
||||
|
||||
it("shows committed branch changes without dirty working tree changes in base mode", async () => {
|
||||
execSync("git checkout -b feature", { cwd: repoDir });
|
||||
writeFileSync(join(repoDir, "feature.txt"), "feature\n");
|
||||
execSync("git add feature.txt", { cwd: repoDir });
|
||||
execSync("git -c commit.gpgsign=false commit -m 'feature commit'", { cwd: repoDir });
|
||||
|
||||
writeFileSync(join(repoDir, "file.txt"), "dirty\n");
|
||||
writeFileSync(join(repoDir, "untracked.txt"), "untracked\n");
|
||||
|
||||
const diff = await getCheckoutDiff(repoDir, {
|
||||
mode: "base",
|
||||
baseRef: "main",
|
||||
includeStructured: true,
|
||||
});
|
||||
|
||||
expect(diff.diff).toContain("feature.txt");
|
||||
expect(diff.diff).not.toContain("file.txt");
|
||||
expect(diff.diff).not.toContain("untracked.txt");
|
||||
expect(diff.structured?.map((file) => file.path)).toEqual(["feature.txt"]);
|
||||
});
|
||||
|
||||
it("warms shortstat cache in the background without blocking listing callers", async () => {
|
||||
expect(getCachedCheckoutShortstat(repoDir)).toBeUndefined();
|
||||
|
||||
@@ -1524,6 +1559,36 @@ const x = 1;
|
||||
expect(baseDiff.diff).not.toContain("file.txt");
|
||||
});
|
||||
|
||||
it("excludes dirty working tree changes from Paseo worktree base diffs", async () => {
|
||||
const worktree = await createLegacyWorktreeForTest({
|
||||
branchName: "feature",
|
||||
cwd: repoDir,
|
||||
baseBranch: "main",
|
||||
worktreeSlug: "dirty-feature",
|
||||
paseoHome,
|
||||
});
|
||||
|
||||
writeFileSync(join(worktree.worktreePath, "feature.txt"), "feature\n");
|
||||
execSync("git add feature.txt", { cwd: worktree.worktreePath });
|
||||
execSync("git -c commit.gpgsign=false commit -m 'feature commit'", {
|
||||
cwd: worktree.worktreePath,
|
||||
});
|
||||
|
||||
writeFileSync(join(worktree.worktreePath, "file.txt"), "dirty\n");
|
||||
writeFileSync(join(worktree.worktreePath, "untracked.txt"), "untracked\n");
|
||||
|
||||
const baseDiff = await getCheckoutDiff(
|
||||
worktree.worktreePath,
|
||||
{ mode: "base", includeStructured: true },
|
||||
{ paseoHome },
|
||||
);
|
||||
|
||||
expect(baseDiff.diff).toContain("feature.txt");
|
||||
expect(baseDiff.diff).not.toContain("file.txt");
|
||||
expect(baseDiff.diff).not.toContain("untracked.txt");
|
||||
expect(baseDiff.structured?.map((file) => file.path)).toEqual(["feature.txt"]);
|
||||
});
|
||||
|
||||
it("resolves the repository default branch from origin HEAD", async () => {
|
||||
execSync("git checkout -b develop", { cwd: repoDir });
|
||||
execSync("git checkout main", { cwd: repoDir });
|
||||
|
||||
@@ -103,6 +103,16 @@ type CheckoutFileChange = {
|
||||
isUntracked?: boolean;
|
||||
};
|
||||
|
||||
type CheckoutDiffRefs = {
|
||||
baseRef: string;
|
||||
targetRef?: string;
|
||||
includeUntracked: boolean;
|
||||
};
|
||||
|
||||
function getCheckoutDiffRefArgs(refs: CheckoutDiffRefs): string[] {
|
||||
return [refs.baseRef, ...(refs.targetRef ? [refs.targetRef] : [])];
|
||||
}
|
||||
|
||||
function normalizeBranchSuggestionName(raw: string): string | null {
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) {
|
||||
@@ -358,7 +368,7 @@ export async function checkoutResolvedBranch(
|
||||
|
||||
async function listCheckoutFileChanges(
|
||||
cwd: string,
|
||||
ref: string,
|
||||
refs: CheckoutDiffRefs,
|
||||
ignoreWhitespace = false,
|
||||
): Promise<CheckoutFileChange[]> {
|
||||
const changes: CheckoutFileChange[] = [];
|
||||
@@ -366,7 +376,7 @@ async function listCheckoutFileChanges(
|
||||
const { stdout: nameStatusOut } = await runGitCommand(
|
||||
buildGitDiffArgs({
|
||||
ignoreWhitespace,
|
||||
extra: ["--name-status", ref],
|
||||
extra: ["--name-status", ...getCheckoutDiffRefArgs(refs)],
|
||||
}),
|
||||
{ cwd, env: READ_ONLY_GIT_ENV },
|
||||
);
|
||||
@@ -405,24 +415,26 @@ async function listCheckoutFileChanges(
|
||||
});
|
||||
}
|
||||
|
||||
const { stdout: untrackedOut } = await runGitCommand(
|
||||
["ls-files", "--others", "--exclude-standard"],
|
||||
{
|
||||
cwd,
|
||||
env: READ_ONLY_GIT_ENV,
|
||||
},
|
||||
);
|
||||
for (const file of untrackedOut
|
||||
.split("\n")
|
||||
.map((l) => l.trim())
|
||||
.filter(Boolean)) {
|
||||
changes.push({
|
||||
path: file,
|
||||
status: "U",
|
||||
isNew: true,
|
||||
isDeleted: false,
|
||||
isUntracked: true,
|
||||
});
|
||||
if (refs.includeUntracked) {
|
||||
const { stdout: untrackedOut } = await runGitCommand(
|
||||
["ls-files", "--others", "--exclude-standard"],
|
||||
{
|
||||
cwd,
|
||||
env: READ_ONLY_GIT_ENV,
|
||||
},
|
||||
);
|
||||
for (const file of untrackedOut
|
||||
.split("\n")
|
||||
.map((l) => l.trim())
|
||||
.filter(Boolean)) {
|
||||
changes.push({
|
||||
path: file,
|
||||
status: "U",
|
||||
isNew: true,
|
||||
isDeleted: false,
|
||||
isUntracked: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Deduplicate by path (prefer tracked status over untracked marker if both appear).
|
||||
@@ -495,13 +507,13 @@ const TRACKED_MAX_CHANGED_LINES = 40_000;
|
||||
|
||||
async function getTrackedNumstatByPath(
|
||||
cwd: string,
|
||||
ref: string,
|
||||
refs: CheckoutDiffRefs,
|
||||
ignoreWhitespace = false,
|
||||
): Promise<Map<string, FileStat>> {
|
||||
const result = await runGitCommand(
|
||||
buildGitDiffArgs({
|
||||
ignoreWhitespace,
|
||||
extra: ["--numstat", ref],
|
||||
extra: ["--numstat", ...getCheckoutDiffRefArgs(refs)],
|
||||
}),
|
||||
{
|
||||
cwd,
|
||||
@@ -1507,10 +1519,10 @@ export async function getCheckoutDiff(
|
||||
): Promise<CheckoutDiffResult> {
|
||||
await requireGitRepo(cwd);
|
||||
|
||||
let refForDiff: string;
|
||||
let refsForDiff: CheckoutDiffRefs;
|
||||
|
||||
if (compare.mode === "uncommitted") {
|
||||
refForDiff = "HEAD";
|
||||
refsForDiff = { baseRef: "HEAD", includeUntracked: true };
|
||||
} else {
|
||||
const configured = await getConfiguredBaseRefForCwd(cwd, context);
|
||||
const baseRef = configured.baseRef ?? compare.baseRef ?? (await resolveBaseRef(cwd));
|
||||
@@ -1522,11 +1534,15 @@ export async function getCheckoutDiff(
|
||||
}
|
||||
|
||||
const bestBaseRef = await resolveBestComparisonBaseRef(cwd, baseRef);
|
||||
refForDiff = (await tryResolveMergeBase(cwd, bestBaseRef)) ?? bestBaseRef;
|
||||
refsForDiff = {
|
||||
baseRef: (await tryResolveMergeBase(cwd, bestBaseRef)) ?? bestBaseRef,
|
||||
targetRef: "HEAD",
|
||||
includeUntracked: false,
|
||||
};
|
||||
}
|
||||
|
||||
const ignoreWhitespace = compare.ignoreWhitespace === true;
|
||||
const changes = await listCheckoutFileChanges(cwd, refForDiff, ignoreWhitespace);
|
||||
const changes = await listCheckoutFileChanges(cwd, refsForDiff, ignoreWhitespace);
|
||||
changes.sort((a, b) => {
|
||||
if (a.path === b.path) return 0;
|
||||
return a.path < b.path ? -1 : 1;
|
||||
@@ -1557,7 +1573,7 @@ export async function getCheckoutDiff(
|
||||
|
||||
const trackedNumstatByPath =
|
||||
trackedChanges.length > 0
|
||||
? await getTrackedNumstatByPath(cwd, refForDiff, ignoreWhitespace)
|
||||
? await getTrackedNumstatByPath(cwd, refsForDiff, ignoreWhitespace)
|
||||
: new Map<string, FileStat>();
|
||||
const trackedDiffPaths: string[] = [];
|
||||
const trackedPlaceholderByPath = new Map<
|
||||
@@ -1584,7 +1600,7 @@ export async function getCheckoutDiff(
|
||||
const trackedDiffResult = await runGitCommand(
|
||||
buildGitDiffArgs({
|
||||
ignoreWhitespace,
|
||||
extra: [refForDiff, "--", ...trackedDiffPaths],
|
||||
extra: [...getCheckoutDiffRefArgs(refsForDiff), "--", ...trackedDiffPaths],
|
||||
}),
|
||||
{
|
||||
cwd,
|
||||
@@ -1621,7 +1637,13 @@ export async function getCheckoutDiff(
|
||||
return null;
|
||||
}
|
||||
const refPath = change.oldPath ?? change.path;
|
||||
return readGitFileContentAtRef(cwd, refForDiff, refPath);
|
||||
return readGitFileContentAtRef(cwd, refsForDiff.baseRef, refPath);
|
||||
},
|
||||
getNewFileContent: async (file) => {
|
||||
if (!refsForDiff.targetRef) {
|
||||
return null;
|
||||
}
|
||||
return readGitFileContentAtRef(cwd, refsForDiff.targetRef, file.path);
|
||||
},
|
||||
})
|
||||
: [];
|
||||
|
||||
Reference in New Issue
Block a user