Skip to content

Commit a72bc8c

Browse files
committed
fix(cp): add recursive support for cp -p
This adds recursive support for `cp -p`. This also sets atime/mtime on the directory itself.
1 parent 73bc897 commit a72bc8c

2 files changed

Lines changed: 50 additions & 4 deletions

File tree

src/cp.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,9 @@ function cpdirSyncRecursive(sourceDir, destDir, currentDepth, opts) {
103103

104104
var isWindows = process.platform === 'win32';
105105

106-
// Create the directory where all our junk is moving to; read the mode of the
107-
// source directory and mirror it
106+
// Create the directory where all our junk is moving to; read the mode/etc. of
107+
// the source directory (we'll set this on the destDir at the end).
108+
var checkDir = common.statFollowLinks(sourceDir);
108109
try {
109110
fs.mkdirSync(destDir);
110111
} catch (e) {
@@ -157,7 +158,10 @@ function cpdirSyncRecursive(sourceDir, destDir, currentDepth, opts) {
157158

158159
// finally change the mode for the newly created directory (otherwise, we
159160
// couldn't add files to a read-only directory).
160-
var checkDir = common.statFollowLinks(sourceDir);
161+
// var checkDir = common.statFollowLinks(sourceDir);
162+
if (opts.preserve) {
163+
fs.utimesSync(destDir, checkDir.atime, checkDir.mtime);
164+
}
161165
fs.chmodSync(destDir, checkDir.mode);
162166
} // cpdirSyncRecursive
163167

@@ -266,7 +270,7 @@ function _cp(options, sources, dest) {
266270

267271
try {
268272
common.statFollowLinks(path.dirname(dest));
269-
cpdirSyncRecursive(src, newDest, 0, { no_force: options.no_force, followsymlink: options.followsymlink, update: options.update });
273+
cpdirSyncRecursive(src, newDest, 0, options);
270274
} catch (e) {
271275
/* istanbul ignore next */
272276
common.error("cannot create directory '" + dest + "': No such file or directory");

test/cp.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -838,6 +838,48 @@ test('cp -p should preserve mode, ownership, and timestamp (regular file)', t =>
838838
t.is(stat.gid, statOfResult.gid);
839839
});
840840

841+
test('cp -p should preserve mode, ownership, and timestamp (directory)', t => {
842+
// Setup: copy to srcFile and modify mode and timestamp
843+
const srcDir = `${t.context.tmp}/srcDir`;
844+
const srcFile = `${srcDir}/srcFile`;
845+
shell.mkdir(srcDir);
846+
shell.cp('test/resources/cp/file1', srcFile);
847+
// Make this a round number of seconds, since the underlying system may not
848+
// have millisecond precision.
849+
const newModifyTimeMs = 12345000;
850+
const newAccessTimeMs = 67890000;
851+
shell.touch({ '-d': new Date(newModifyTimeMs), '-m': true }, srcFile);
852+
shell.touch({ '-d': new Date(newAccessTimeMs), '-a': true }, srcFile);
853+
fs.utimesSync(srcDir, new Date(newAccessTimeMs), new Date(newModifyTimeMs));
854+
const mode = '444';
855+
shell.chmod(mode, srcFile);
856+
857+
// Now re-copy (the whole dir) with '-p' and verify metadata of file contents.
858+
const result = shell.cp('-pr', srcDir, `${t.context.tmp}/preservedDir`);
859+
const stat = common.statFollowLinks(srcFile);
860+
const statDir = common.statFollowLinks(srcDir);
861+
const statOfResult = common.statFollowLinks(`${t.context.tmp}/preservedDir/srcFile`);
862+
const statOfResultDir = common.statFollowLinks(`${t.context.tmp}/preservedDir`);
863+
864+
t.is(result.code, 0);
865+
866+
// Both original file and original dir should be unchanged:
867+
t.is(statDir.mtime.getTime(), newModifyTimeMs);
868+
t.is(stat.mtime.getTime(), newModifyTimeMs);
869+
// cp appears to update the atime, but only of the srcFile & srcDir
870+
t.is(stat.mode.toString(8), '100' + mode);
871+
872+
// Both new file and new dir should keep same attributes
873+
t.is(statOfResultDir.mtime.getTime(), newModifyTimeMs);
874+
t.is(statOfResultDir.atime.getTime(), newAccessTimeMs);
875+
t.is(statOfResult.mtime.getTime(), newModifyTimeMs);
876+
t.is(statOfResult.atime.getTime(), newAccessTimeMs);
877+
t.is(statOfResult.mode.toString(8), '100' + mode);
878+
879+
t.is(stat.uid, statOfResult.uid);
880+
t.is(stat.gid, statOfResult.gid);
881+
});
882+
841883
test('cp -p should preserve mode, ownership, and timestamp (symlink)', t => {
842884
// Skip in Windows because symlinks require elevated permissions.
843885
utils.skipOnWin(t, () => {

0 commit comments

Comments
 (0)