Commit 7fab6566c09 for nodejs
commit 7fab6566c09400f8141ebe2a2c8d077d8abc934f
Author: Christian Aurich Zanettini Martins <christian.aurichzm@gmail.com>
Date: Fri Oct 2 19:33:38 2026 -0300
fs: keep timestamps of files skipped by cpSync
With `force: false` and `preserveTimestamps: true`, the native
directory copy used by `cpSync()` left existing destination files in
place but still copied the source timestamps onto them. The JavaScript
walk, used by `cpSync()` with a `filter` and by `fs.cp()`, leaves
those files untouched.
Only copy the timestamps of files that were actually copied, as is
already done for their mode.
Signed-off-by: Christian Aurich Zanettini Martins <christian.aurichzm@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/66305
Reviewed-By: Anna Henningsen <anna@addaleax.net>
diff --git a/src/node_file.cc b/src/node_file.cc
index 130691aca8d..0bc7511ccb5 100644
--- a/src/node_file.cc
+++ b/src/node_file.cc
@@ -5079,7 +5079,7 @@ CpError CopyDirRecursive(const std::filesystem::path& src_path,
return CpError::Std(error, dest_str);
}
- if (options.preserve_timestamps) {
+ if (copied && options.preserve_timestamps) {
CpError utimes = CopyUtimes(dir_entry.path(), dest_file_path);
if (utimes.kind != CpError::kNone) {
return utimes;
diff --git a/test/parallel/test-fs-cp-sync-preserve-timestamps-force-false.mjs b/test/parallel/test-fs-cp-sync-preserve-timestamps-force-false.mjs
new file mode 100644
index 00000000000..7f6ffe008ed
--- /dev/null
+++ b/test/parallel/test-fs-cp-sync-preserve-timestamps-force-false.mjs
@@ -0,0 +1,34 @@
+// This tests that cpSync with force: false and preserveTimestamps: true leaves
+// the timestamps of the destination files it skips untouched.
+import { mustNotMutateObjectDeep } from '../common/index.mjs';
+import { nextdir } from '../common/fs.js';
+import assert from 'node:assert';
+import { cpSync, mkdirSync, readFileSync, statSync, utimesSync, writeFileSync } from 'node:fs';
+import { join } from 'node:path';
+import tmpdir from '../common/tmpdir.js';
+
+tmpdir.refresh();
+
+const src = nextdir();
+mkdirSync(src, { recursive: true });
+writeFileSync(join(src, 'file.txt'), 'src', 'utf8');
+utimesSync(join(src, 'file.txt'), 1000, 1000);
+
+// Without a filter the tree is copied in C++, with one it is walked in
+// JavaScript.
+for (const filter of [undefined, () => true]) {
+ const dest = nextdir();
+ mkdirSync(dest, { recursive: true });
+ writeFileSync(join(dest, 'file.txt'), 'dest', 'utf8');
+ utimesSync(join(dest, 'file.txt'), 2000, 2000);
+
+ cpSync(src, dest, mustNotMutateObjectDeep({
+ filter,
+ force: false,
+ preserveTimestamps: true,
+ recursive: true,
+ }));
+
+ assert.strictEqual(readFileSync(join(dest, 'file.txt'), 'utf8'), 'dest');
+ assert.strictEqual(statSync(join(dest, 'file.txt')).mtime.getTime(), 2000 * 1000);
+}