mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 18:25:13 +08:00
fs: preserve directory timestamps in cp
This fixes an issue where fs.cp and fs.cpSync (when a filter is provided) correctly restore mtime and atime on copied files when preserveTimestamps: true is passed, but skip restoring them for directories. The fix applies the existing setDestTimestamps helper to directories, ensuring it is called after directory creation/copying but before any modes are restored (since restoring a read-only mode would prevent timestamp modification). Note: This fix covers fs.cp() (async, all cases) and fs.cpSync() when a filter is provided. The native fast path used by cpSync() without a filter (CpSyncCopyDir in src/node_file.cc) has the identical gap and is left for a separate follow-up contribution, since it requires native code changes and a different review path. Signed-off-by: Abhinandan Kumar <abhi128618@gmail.com> PR-URL: https://github.com/nodejs/node/pull/65540 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: LiviaMedeiros <livia@cirno.name>
This commit is contained in:
1 parent
cb20bb3bad
commit
8fe4b642ea
4 files changed
+95
-1
No files matched your search
@@ -179,6 +179,9 @@ function copyDir(src, dest, opts, mkDir, srcMode) {
|
||||
} finally {
|
||||
dir.closeSync();
|
||||
|
||||
if (opts.preserveTimestamps) {
|
||||
setDestTimestamps(src, dest);
|
||||
}
|
||||
if (srcMode !== undefined) {
|
||||
setDestMode(dest, srcMode);
|
||||
}
|
||||
|
||||
@@ -311,12 +311,18 @@ async function onDir(srcStat, destStat, src, dest, opts) {
|
||||
code: 'EEXIST',
|
||||
});
|
||||
}
|
||||
return copyDir(src, dest, opts);
|
||||
await copyDir(src, dest, opts);
|
||||
if (opts.preserveTimestamps) {
|
||||
await setDestTimestamps(src, dest);
|
||||
}
|
||||
}
|
||||
|
||||
async function mkDirAndCopy(srcMode, src, dest, opts) {
|
||||
await mkdir(dest);
|
||||
await copyDir(src, dest, opts);
|
||||
if (opts.preserveTimestamps) {
|
||||
await setDestTimestamps(src, dest);
|
||||
}
|
||||
return setDestMode(dest, srcMode);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
// This tests that cp() preserves directory timestamps
|
||||
// when preserveTimestamps is true.
|
||||
import { mustCall } from '../common/index.mjs';
|
||||
import { nextdir } from '../common/fs.js';
|
||||
import assert from 'node:assert';
|
||||
import { cp, mkdirSync, writeFileSync, utimesSync, statSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import tmpdir from '../common/tmpdir.js';
|
||||
|
||||
tmpdir.refresh();
|
||||
|
||||
// Build a source tree with a known past timestamp on directories.
|
||||
const src = nextdir();
|
||||
mkdirSync(join(src, 'subdir'), { recursive: true });
|
||||
writeFileSync(join(src, 'subdir', 'file.txt'), 'hello');
|
||||
|
||||
const pastDate = new Date('2020-01-01T00:00:00Z');
|
||||
utimesSync(join(src, 'subdir', 'file.txt'), pastDate, pastDate);
|
||||
utimesSync(join(src, 'subdir'), pastDate, pastDate);
|
||||
utimesSync(src, pastDate, pastDate);
|
||||
|
||||
// Copy with preserveTimestamps.
|
||||
const dest = nextdir();
|
||||
cp(src, dest, {
|
||||
recursive: true,
|
||||
preserveTimestamps: true,
|
||||
}, mustCall((err) => {
|
||||
assert.strictEqual(err, null);
|
||||
|
||||
// Verify file timestamps are preserved (existing behaviour).
|
||||
const srcFileStat = statSync(join(src, 'subdir', 'file.txt'));
|
||||
const destFileStat = statSync(join(dest, 'subdir', 'file.txt'));
|
||||
assert.strictEqual(srcFileStat.mtime.getTime(), destFileStat.mtime.getTime());
|
||||
|
||||
// Verify directory timestamps are preserved (the bug fix).
|
||||
const srcDirStat = statSync(join(src, 'subdir'));
|
||||
const destDirStat = statSync(join(dest, 'subdir'));
|
||||
assert.strictEqual(srcDirStat.mtime.getTime(), destDirStat.mtime.getTime());
|
||||
|
||||
const srcRootStat = statSync(src);
|
||||
const destRootStat = statSync(dest);
|
||||
assert.strictEqual(srcRootStat.mtime.getTime(), destRootStat.mtime.getTime());
|
||||
}));
|
||||
@@ -0,0 +1,42 @@
|
||||
// This tests that cpSync with a filter preserves directory timestamps
|
||||
// when preserveTimestamps is true.
|
||||
import '../common/index.mjs';
|
||||
import { nextdir } from '../common/fs.js';
|
||||
import assert from 'node:assert';
|
||||
import { cpSync, mkdirSync, writeFileSync, utimesSync, statSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import tmpdir from '../common/tmpdir.js';
|
||||
|
||||
tmpdir.refresh();
|
||||
|
||||
// Build a source tree with a known past timestamp on directories.
|
||||
const src = nextdir();
|
||||
mkdirSync(join(src, 'subdir'), { recursive: true });
|
||||
writeFileSync(join(src, 'subdir', 'file.txt'), 'hello');
|
||||
|
||||
const pastDate = new Date('2020-01-01T00:00:00Z');
|
||||
utimesSync(join(src, 'subdir', 'file.txt'), pastDate, pastDate);
|
||||
utimesSync(join(src, 'subdir'), pastDate, pastDate);
|
||||
utimesSync(src, pastDate, pastDate);
|
||||
|
||||
// Copy with preserveTimestamps and a filter (to exercise the JS fallback path).
|
||||
const dest = nextdir();
|
||||
cpSync(src, dest, {
|
||||
recursive: true,
|
||||
preserveTimestamps: true,
|
||||
filter: () => true,
|
||||
});
|
||||
|
||||
// Verify file timestamps are preserved (existing behaviour).
|
||||
const srcFileStat = statSync(join(src, 'subdir', 'file.txt'));
|
||||
const destFileStat = statSync(join(dest, 'subdir', 'file.txt'));
|
||||
assert.strictEqual(srcFileStat.mtime.getTime(), destFileStat.mtime.getTime());
|
||||
|
||||
// Verify directory timestamps are preserved (the bug fix).
|
||||
const srcDirStat = statSync(join(src, 'subdir'));
|
||||
const destDirStat = statSync(join(dest, 'subdir'));
|
||||
assert.strictEqual(srcDirStat.mtime.getTime(), destDirStat.mtime.getTime());
|
||||
|
||||
const srcRootStat = statSync(src);
|
||||
const destRootStat = statSync(dest);
|
||||
assert.strictEqual(srcRootStat.mtime.getTime(), destRootStat.mtime.getTime());
|
||||
Reference in new issue
Block a user