From 8edf1a0927ef52100a890b28b53577f34cac91b2 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Tue, 14 Jul 2026 12:10:36 -0400 Subject: [PATCH] fix: refuse root as a source in cp and mv Ports the follow-up commits on denoland/deno_task_shell#185. A source whose final path component is empty (ex. `/`) has no name to place inside the destination, so cp and mv now error instead of silently using the destination path itself. Also moves the raciness comment below the same-path guard since a string comparison isn't racy. --- mod.test.ts | 14 ++++++++++++++ src/commands/cpMv.ts | 28 ++++++++++++++++++++-------- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/mod.test.ts b/mod.test.ts index b4d44e9..a553ee8 100644 --- a/mod.test.ts +++ b/mod.test.ts @@ -2290,6 +2290,13 @@ Deno.test("cp -r with trailing dot copies directory contents", async () => { "cp: 'public/index.html' and 'public/index.html' are the same file\n", ); assertEquals(dir.join("public/index.html").readTextSync(), "test"); + + // refuses the root as a source since it has no name to place + // inside the destination + assertEquals( + await getStdErr($`cp -r / dist`), + "cp: cannot copy '/': the source has no final path component\n", + ); }); }); @@ -2339,6 +2346,13 @@ Deno.test("move test", async () => { "mv: cannot move '..': refusing to move '.' or '..'\n", ); assert(destDir.join("file1.txt").existsSync()); + + // refuses the root as a source since it has no name to place + // inside the destination + assertEquals( + await getStdErr($`mv / other`), + "mv: cannot move '/': the source has no final path component\n", + ); }); }); diff --git a/src/commands/cpMv.ts b/src/commands/cpMv.ts index 6631765..a15f6bb 100644 --- a/src/commands/cpMv.ts +++ b/src/commands/cpMv.ts @@ -50,10 +50,15 @@ export async function parseCpArgs(cwd: string, args: string[]): Promise