mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-19 07:42:52 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00870740ef |
@@ -615,6 +615,41 @@ const x = 1;
|
|||||||
expect(diff.diff).toContain("feature.txt");
|
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 () => {
|
it("warms shortstat cache in the background without blocking listing callers", async () => {
|
||||||
expect(getCachedCheckoutShortstat(repoDir)).toBeUndefined();
|
expect(getCachedCheckoutShortstat(repoDir)).toBeUndefined();
|
||||||
|
|
||||||
@@ -1524,6 +1559,36 @@ const x = 1;
|
|||||||
expect(baseDiff.diff).not.toContain("file.txt");
|
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 () => {
|
it("resolves the repository default branch from origin HEAD", async () => {
|
||||||
execSync("git checkout -b develop", { cwd: repoDir });
|
execSync("git checkout -b develop", { cwd: repoDir });
|
||||||
execSync("git checkout main", { cwd: repoDir });
|
execSync("git checkout main", { cwd: repoDir });
|
||||||
|
|||||||
@@ -103,6 +103,16 @@ type CheckoutFileChange = {
|
|||||||
isUntracked?: boolean;
|
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 {
|
function normalizeBranchSuggestionName(raw: string): string | null {
|
||||||
const trimmed = raw.trim();
|
const trimmed = raw.trim();
|
||||||
if (!trimmed) {
|
if (!trimmed) {
|
||||||
@@ -358,7 +368,7 @@ export async function checkoutResolvedBranch(
|
|||||||
|
|
||||||
async function listCheckoutFileChanges(
|
async function listCheckoutFileChanges(
|
||||||
cwd: string,
|
cwd: string,
|
||||||
ref: string,
|
refs: CheckoutDiffRefs,
|
||||||
ignoreWhitespace = false,
|
ignoreWhitespace = false,
|
||||||
): Promise<CheckoutFileChange[]> {
|
): Promise<CheckoutFileChange[]> {
|
||||||
const changes: CheckoutFileChange[] = [];
|
const changes: CheckoutFileChange[] = [];
|
||||||
@@ -366,7 +376,7 @@ async function listCheckoutFileChanges(
|
|||||||
const { stdout: nameStatusOut } = await runGitCommand(
|
const { stdout: nameStatusOut } = await runGitCommand(
|
||||||
buildGitDiffArgs({
|
buildGitDiffArgs({
|
||||||
ignoreWhitespace,
|
ignoreWhitespace,
|
||||||
extra: ["--name-status", ref],
|
extra: ["--name-status", ...getCheckoutDiffRefArgs(refs)],
|
||||||
}),
|
}),
|
||||||
{ cwd, env: READ_ONLY_GIT_ENV },
|
{ cwd, env: READ_ONLY_GIT_ENV },
|
||||||
);
|
);
|
||||||
@@ -405,24 +415,26 @@ async function listCheckoutFileChanges(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { stdout: untrackedOut } = await runGitCommand(
|
if (refs.includeUntracked) {
|
||||||
["ls-files", "--others", "--exclude-standard"],
|
const { stdout: untrackedOut } = await runGitCommand(
|
||||||
{
|
["ls-files", "--others", "--exclude-standard"],
|
||||||
cwd,
|
{
|
||||||
env: READ_ONLY_GIT_ENV,
|
cwd,
|
||||||
},
|
env: READ_ONLY_GIT_ENV,
|
||||||
);
|
},
|
||||||
for (const file of untrackedOut
|
);
|
||||||
.split("\n")
|
for (const file of untrackedOut
|
||||||
.map((l) => l.trim())
|
.split("\n")
|
||||||
.filter(Boolean)) {
|
.map((l) => l.trim())
|
||||||
changes.push({
|
.filter(Boolean)) {
|
||||||
path: file,
|
changes.push({
|
||||||
status: "U",
|
path: file,
|
||||||
isNew: true,
|
status: "U",
|
||||||
isDeleted: false,
|
isNew: true,
|
||||||
isUntracked: true,
|
isDeleted: false,
|
||||||
});
|
isUntracked: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deduplicate by path (prefer tracked status over untracked marker if both appear).
|
// 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(
|
async function getTrackedNumstatByPath(
|
||||||
cwd: string,
|
cwd: string,
|
||||||
ref: string,
|
refs: CheckoutDiffRefs,
|
||||||
ignoreWhitespace = false,
|
ignoreWhitespace = false,
|
||||||
): Promise<Map<string, FileStat>> {
|
): Promise<Map<string, FileStat>> {
|
||||||
const result = await runGitCommand(
|
const result = await runGitCommand(
|
||||||
buildGitDiffArgs({
|
buildGitDiffArgs({
|
||||||
ignoreWhitespace,
|
ignoreWhitespace,
|
||||||
extra: ["--numstat", ref],
|
extra: ["--numstat", ...getCheckoutDiffRefArgs(refs)],
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
cwd,
|
cwd,
|
||||||
@@ -1507,10 +1519,10 @@ export async function getCheckoutDiff(
|
|||||||
): Promise<CheckoutDiffResult> {
|
): Promise<CheckoutDiffResult> {
|
||||||
await requireGitRepo(cwd);
|
await requireGitRepo(cwd);
|
||||||
|
|
||||||
let refForDiff: string;
|
let refsForDiff: CheckoutDiffRefs;
|
||||||
|
|
||||||
if (compare.mode === "uncommitted") {
|
if (compare.mode === "uncommitted") {
|
||||||
refForDiff = "HEAD";
|
refsForDiff = { baseRef: "HEAD", includeUntracked: true };
|
||||||
} else {
|
} else {
|
||||||
const configured = await getConfiguredBaseRefForCwd(cwd, context);
|
const configured = await getConfiguredBaseRefForCwd(cwd, context);
|
||||||
const baseRef = configured.baseRef ?? compare.baseRef ?? (await resolveBaseRef(cwd));
|
const baseRef = configured.baseRef ?? compare.baseRef ?? (await resolveBaseRef(cwd));
|
||||||
@@ -1522,11 +1534,15 @@ export async function getCheckoutDiff(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const bestBaseRef = await resolveBestComparisonBaseRef(cwd, baseRef);
|
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 ignoreWhitespace = compare.ignoreWhitespace === true;
|
||||||
const changes = await listCheckoutFileChanges(cwd, refForDiff, ignoreWhitespace);
|
const changes = await listCheckoutFileChanges(cwd, refsForDiff, ignoreWhitespace);
|
||||||
changes.sort((a, b) => {
|
changes.sort((a, b) => {
|
||||||
if (a.path === b.path) return 0;
|
if (a.path === b.path) return 0;
|
||||||
return a.path < b.path ? -1 : 1;
|
return a.path < b.path ? -1 : 1;
|
||||||
@@ -1557,7 +1573,7 @@ export async function getCheckoutDiff(
|
|||||||
|
|
||||||
const trackedNumstatByPath =
|
const trackedNumstatByPath =
|
||||||
trackedChanges.length > 0
|
trackedChanges.length > 0
|
||||||
? await getTrackedNumstatByPath(cwd, refForDiff, ignoreWhitespace)
|
? await getTrackedNumstatByPath(cwd, refsForDiff, ignoreWhitespace)
|
||||||
: new Map<string, FileStat>();
|
: new Map<string, FileStat>();
|
||||||
const trackedDiffPaths: string[] = [];
|
const trackedDiffPaths: string[] = [];
|
||||||
const trackedPlaceholderByPath = new Map<
|
const trackedPlaceholderByPath = new Map<
|
||||||
@@ -1584,7 +1600,7 @@ export async function getCheckoutDiff(
|
|||||||
const trackedDiffResult = await runGitCommand(
|
const trackedDiffResult = await runGitCommand(
|
||||||
buildGitDiffArgs({
|
buildGitDiffArgs({
|
||||||
ignoreWhitespace,
|
ignoreWhitespace,
|
||||||
extra: [refForDiff, "--", ...trackedDiffPaths],
|
extra: [...getCheckoutDiffRefArgs(refsForDiff), "--", ...trackedDiffPaths],
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
cwd,
|
cwd,
|
||||||
@@ -1621,7 +1637,13 @@ export async function getCheckoutDiff(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const refPath = change.oldPath ?? change.path;
|
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