mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-06 00:14:54 +00:00
Restore selected changes from subdirectory workspaces (#6777)
* fix(git): discard repository paths from the worktree root * test(git): cover discard from nested workspaces through daemon RPC * test(git): close the daemon before removing discard fixtures Windows CI reaches the discard assertions but cannot remove the observed repository while the test daemon is alive. Run fixture removal after the daemon's afterEach cleanup, preserving all behavioral assertions. Co-authored-by: 见安 <73627805+Mar-garet@users.noreply.github.com> --------- Co-authored-by: 见安 <73627805+Mar-garet@users.noreply.github.com> Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
This commit is contained in:
@@ -31,6 +31,81 @@ afterEach(async () => {
|
||||
await ctx.cleanup();
|
||||
}, 60000);
|
||||
|
||||
describe("checkout discard", () => {
|
||||
test.each([".", "apps/web"])(
|
||||
"discards selected Changes from the %s workspace without changing siblings",
|
||||
async (workspacePath) => {
|
||||
const cwd = tmpCwd();
|
||||
function git(args: string[]): string {
|
||||
return execFileSync("git", args, { cwd, encoding: "utf8", stdio: "pipe" });
|
||||
}
|
||||
try {
|
||||
git(["init", "-b", "main"]);
|
||||
git(["config", "user.name", "Paseo Test"]);
|
||||
git(["config", "user.email", "test@getpaseo.local"]);
|
||||
git(["config", "core.autocrlf", "false"]);
|
||||
const nested = path.join(cwd, "apps", "web");
|
||||
mkdirSync(nested, { recursive: true });
|
||||
writeFileSync(path.join(nested, "target[ab].txt"), "target base\n");
|
||||
writeFileSync(path.join(nested, "targeta.txt"), "sibling base\n");
|
||||
writeFileSync(path.join(nested, "deleted.txt"), "deleted base\n");
|
||||
git(["add", "."]);
|
||||
git(["-c", "commit.gpgsign=false", "commit", "-m", "initial"]);
|
||||
const creation = await ctx.client.createWorkspace({
|
||||
source: { kind: "directory", path: path.join(cwd, workspacePath) },
|
||||
title: "Discard regression",
|
||||
});
|
||||
const workspaceCwd = creation.workspace?.workspaceDirectory;
|
||||
if (!workspaceCwd) {
|
||||
throw new Error(creation.error ?? "Workspace creation failed");
|
||||
}
|
||||
|
||||
writeFileSync(path.join(nested, "target[ab].txt"), "target edited\n");
|
||||
writeFileSync(path.join(nested, "targeta.txt"), "sibling edited\n");
|
||||
writeFileSync(path.join(nested, "staged.txt"), "staged new file\n");
|
||||
writeFileSync(path.join(nested, "scratch.txt"), "untracked file\n");
|
||||
git(["--literal-pathspecs", "add", "--", "apps/web/target[ab].txt", "apps/web/staged.txt"]);
|
||||
git(["rm", "apps/web/deleted.txt"]);
|
||||
|
||||
const subscription = ctx.client.observeCheckoutDiff(workspaceCwd, { mode: "uncommitted" });
|
||||
try {
|
||||
const changes = await subscription.ready;
|
||||
expect(changes.error).toBeNull();
|
||||
expect(changes.files.map((file) => file.path).sort()).toEqual([
|
||||
"apps/web/deleted.txt",
|
||||
"apps/web/scratch.txt",
|
||||
"apps/web/staged.txt",
|
||||
"apps/web/target[ab].txt",
|
||||
"apps/web/targeta.txt",
|
||||
]);
|
||||
const selectedPaths = changes.files
|
||||
.filter((file) => file.path !== "apps/web/targeta.txt")
|
||||
.map((file) => file.path);
|
||||
const discarded = await ctx.client.checkoutDiscardChanges(workspaceCwd, {
|
||||
paths: selectedPaths,
|
||||
});
|
||||
expect(discarded).toMatchObject({ success: true, error: null });
|
||||
|
||||
const target = await ctx.client.readFile(cwd, "apps/web/target[ab].txt");
|
||||
expect(new TextDecoder().decode(target.bytes)).toBe("target base\n");
|
||||
const restored = await ctx.client.readFile(cwd, "apps/web/deleted.txt");
|
||||
expect(new TextDecoder().decode(restored.bytes)).toBe("deleted base\n");
|
||||
const sibling = await ctx.client.readFile(cwd, "apps/web/targeta.txt");
|
||||
expect(new TextDecoder().decode(sibling.bytes)).toBe("sibling edited\n");
|
||||
await expect(ctx.client.readFile(cwd, "apps/web/staged.txt")).rejects.toThrow(/ENOENT/);
|
||||
await expect(ctx.client.readFile(cwd, "apps/web/scratch.txt")).rejects.toThrow(/ENOENT/);
|
||||
expect(git(["status", "--porcelain=v1", "-z"])).toBe(" M apps/web/targeta.txt\0");
|
||||
} finally {
|
||||
await subscription.release();
|
||||
}
|
||||
} finally {
|
||||
// afterEach shuts down the daemon and its repository observers first.
|
||||
onTestFinished(() => rmSync(cwd, { recursive: true, force: true }));
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("file explorer", () => {
|
||||
test.each(["..", "..."])(
|
||||
"workspace suggestions resolve exact paths starting with %s",
|
||||
|
||||
@@ -3553,13 +3553,13 @@ export async function commitAll(cwd: string, message: string): Promise<void> {
|
||||
}
|
||||
|
||||
export async function discardChanges(cwd: string, pathspecs: string[]): Promise<void> {
|
||||
await requireGitRepo(cwd);
|
||||
const worktreeRoot = await requireGitWorktreeRoot(cwd);
|
||||
if (pathspecs.length === 0) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await runGitCommand(["--literal-pathspecs", "reset", "-q", "HEAD", "--", ...pathspecs], {
|
||||
cwd,
|
||||
cwd: worktreeRoot,
|
||||
timeout: DISCARD_CHANGES_TIMEOUT_MS,
|
||||
});
|
||||
} catch {
|
||||
@@ -3567,7 +3567,7 @@ export async function discardChanges(cwd: string, pathspecs: string[]): Promise<
|
||||
await runGitCommand(
|
||||
["--literal-pathspecs", "rm", "--cached", "-r", "-q", "--ignore-unmatch", "--", ...pathspecs],
|
||||
{
|
||||
cwd,
|
||||
cwd: worktreeRoot,
|
||||
timeout: DISCARD_CHANGES_TIMEOUT_MS,
|
||||
},
|
||||
);
|
||||
@@ -3579,7 +3579,7 @@ export async function discardChanges(cwd: string, pathspecs: string[]): Promise<
|
||||
const status = await runGitCommand(
|
||||
["--literal-pathspecs", "status", "--porcelain=v1", "-z", "--", ...pathspecs],
|
||||
{
|
||||
cwd,
|
||||
cwd: worktreeRoot,
|
||||
timeout: DISCARD_CHANGES_TIMEOUT_MS,
|
||||
},
|
||||
);
|
||||
@@ -3605,13 +3605,13 @@ export async function discardChanges(cwd: string, pathspecs: string[]): Promise<
|
||||
}
|
||||
if (tracked.length > 0) {
|
||||
await runGitCommand(["--literal-pathspecs", "checkout", "-q", "--", ...tracked], {
|
||||
cwd,
|
||||
cwd: worktreeRoot,
|
||||
timeout: DISCARD_CHANGES_TIMEOUT_MS,
|
||||
});
|
||||
}
|
||||
if (untracked.length > 0) {
|
||||
await runGitCommand(["--literal-pathspecs", "clean", "-fd", "-q", "--", ...untracked], {
|
||||
cwd,
|
||||
cwd: worktreeRoot,
|
||||
timeout: DISCARD_CHANGES_TIMEOUT_MS,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user