From 9227f17da62c272d20f98c2f9969d2d481bc5844 Mon Sep 17 00:00:00 2001 From: Abhinandan Kumar Date: Mon, 31 Aug 2026 17:18:59 +0530 Subject: [PATCH] fs: preserve directory timestamps in cpSync fast path This completes the work started in PR #65540. The previous PR fixed directory timestamp preservation for the JavaScript paths, but the native C++ fast path (CpSyncCopyDir in src/node_file.cc) used by fs.cpSync when no filter is provided still failed to preserve directory timestamps. This commit invokes the existing CopyUtimes helper for both the root destination directory and all subdirectories after their contents are recursively copied, aligning the native behavior with the JavaScript fallback. PR-URL: https://github.com/nodejs/node/pull/65678 Signed-off-by: Abhinandan Kumar --- src/node_file.cc | 8 ++++++- ...est-fs-cp-sync-preserve-timestamps-dir.mjs | 23 +++++++++++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/node_file.cc b/src/node_file.cc index 8a09b656e5a1..2ba10c4c739a 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -4207,6 +4207,10 @@ static void CpSyncCopyDir(const FunctionCallbackInfo& args) { if (!success) { return false; } + if (preserve_timestamps && + !CopyUtimes(entry_dir_path, dest_file_path, env)) { + return false; + } } else if (dir_entry.is_regular_file()) { std::filesystem::copy_file( dir_entry.path(), dest_file_path, file_copy_opts, error); @@ -4231,7 +4235,9 @@ static void CpSyncCopyDir(const FunctionCallbackInfo& args) { return true; }; - copy_dir_contents(src_path, dest_path); + if (copy_dir_contents(src_path, dest_path) && preserve_timestamps) { + CopyUtimes(src_path, dest_path, env); + } } BindingData::FilePathIsFileReturnType BindingData::FilePathIsFile( diff --git a/test/parallel/test-fs-cp-sync-preserve-timestamps-dir.mjs b/test/parallel/test-fs-cp-sync-preserve-timestamps-dir.mjs index 20d889075988..06d1c3521478 100644 --- a/test/parallel/test-fs-cp-sync-preserve-timestamps-dir.mjs +++ b/test/parallel/test-fs-cp-sync-preserve-timestamps-dir.mjs @@ -1,5 +1,6 @@ -// This tests that cpSync with a filter preserves directory timestamps -// when preserveTimestamps is true. +// This tests that cpSync preserves directory timestamps +// when preserveTimestamps is true, both on the JS fallback path (with filter) +// and the native fast path (without filter). import '../common/index.mjs'; import { nextdir } from '../common/fs.js'; import assert from 'node:assert'; @@ -40,3 +41,21 @@ assert.strictEqual(srcDirStat.mtime.getTime(), destDirStat.mtime.getTime()); const srcRootStat = statSync(src); const destRootStat = statSync(dest); assert.strictEqual(srcRootStat.mtime.getTime(), destRootStat.mtime.getTime()); + +// Copy with preserveTimestamps and NO filter (to exercise the native fast path). +const destFast = nextdir(); +cpSync(src, destFast, { + recursive: true, + preserveTimestamps: true, +}); + +// Verify file timestamps are preserved. +const destFastFileStat = statSync(join(destFast, 'subdir', 'file.txt')); +assert.strictEqual(srcFileStat.mtime.getTime(), destFastFileStat.mtime.getTime()); + +// Verify directory timestamps are preserved. +const destFastDirStat = statSync(join(destFast, 'subdir')); +assert.strictEqual(srcDirStat.mtime.getTime(), destFastDirStat.mtime.getTime()); + +const destFastRootStat = statSync(destFast); +assert.strictEqual(srcRootStat.mtime.getTime(), destFastRootStat.mtime.getTime());