Merge pull request #380 from shelljs/fix-cp-r-option

refactor(cp): clean up code and fix #376
This commit is contained in:
Ari Porad 2016-03-06 07:48:57 -08:00
commit 5f4d3901f2
3 changed files with 72 additions and 70 deletions

102
src/cp.js
View File

@ -53,8 +53,8 @@ function cpdirSyncRecursive(sourceDir, destDir, opts) {
if (!opts) opts = {};
/* Create the directory where all our junk is moving to; read the mode of the source directory and mirror it */
var checkDir = fs.statSync(sourceDir);
try {
var checkDir = fs.statSync(sourceDir);
fs.mkdirSync(destDir, checkDir.mode);
} catch (e) {
//if the directory already exists, that's okay
@ -117,95 +117,63 @@ function _cp(options, sources, dest) {
// Get sources, dest
if (arguments.length < 3) {
common.error('missing <source> and/or <dest>');
} else if (arguments.length > 3) {
} else {
sources = [].slice.call(arguments, 1, arguments.length - 1);
dest = arguments[arguments.length - 1];
} else if (typeof sources === 'string') {
sources = [sources];
} else if ('length' in sources) {
sources = sources; // no-op for array
} else {
common.error('invalid arguments');
}
var exists = fs.existsSync(dest),
stats = exists && fs.statSync(dest);
var destExists = fs.existsSync(dest),
destStat = destExists && fs.statSync(dest);
// Dest is not existing dir, but multiple sources given
if ((!exists || !stats.isDirectory()) && sources.length > 1)
if ((!destExists || !destStat.isDirectory()) && sources.length > 1)
common.error('dest is not a directory (too many sources)');
// Dest is an existing file, but no -f given
if (exists && stats.isFile() && options.no_force)
common.error('dest file already exists: ' + dest);
if (options.recursive) {
// Recursive allows the shortcut syntax "sourcedir/" for "sourcedir/*"
// (see Github issue #15)
sources.forEach(function(src, i) {
if (src[src.length - 1] === '/') {
sources[i] += '*';
// If src is a directory and dest doesn't exist, 'cp -r src dest' should copy src/* into dest
} else if (fs.statSync(src).isDirectory() && !exists) {
sources[i] += '/*';
}
});
// Create dest
try {
fs.mkdirSync(dest, parseInt('0777', 8));
} catch (e) {
// like Unix's cp, keep going even if we can't create dest dir
}
}
sources = common.expand(sources, {dot: true});
// Dest is an existing file, but -n is given
if (destExists && destStat.isFile() && options.no_force)
common.error('dest file already destExists: ' + dest);
sources.forEach(function(src) {
if (!fs.existsSync(src)) {
common.error('no such file or directory: '+src, true);
return; // skip file
}
// If here, src exists
if (fs.statSync(src).isDirectory()) {
var srcStat = fs.statSync(src);
if (srcStat.isDirectory()) {
if (!options.recursive) {
// Non-Recursive
common.log(src + ' is a directory (not copied)');
common.error("omitting directory '" + src + "'", true);
} else {
// Recursive
// 'cp /a/source dest' should create 'source' in 'dest'
var newDest = path.join(dest, path.basename(src)),
checkDir = fs.statSync(src);
try {
fs.mkdirSync(newDest, checkDir.mode);
} catch (e) {
//if the directory already exists, that's okay
if (e.code !== 'EEXIST') {
common.error('dest file no such file or directory: ' + newDest, true);
throw e;
}
}
var newDest = (destStat && destStat.isDirectory()) ?
path.join(dest, path.basename(src)) :
dest;
cpdirSyncRecursive(src, newDest, {no_force: options.no_force});
try {
fs.statSync(path.dirname(dest));
cpdirSyncRecursive(src, newDest, {no_force: options.no_force});
} catch(e) {
common.error("cannot create directory '" + dest + "': No such file or directory");
}
}
return; // done with dir
} else {
// If here, src is a file
// When copying to '/path/dir':
// thisDest = '/path/dir/file1'
var thisDest = dest;
if (destStat && destStat.isDirectory())
thisDest = path.normalize(dest + '/' + path.basename(src));
if (fs.existsSync(thisDest) && options.no_force) {
common.error('dest file already destExists: ' + thisDest, true);
return; // skip file
}
copyFileSync(src, thisDest);
}
// If here, src is a file
// When copying to '/path/dir':
// thisDest = '/path/dir/file1'
var thisDest = dest;
if (fs.existsSync(dest) && fs.statSync(dest).isDirectory())
thisDest = path.normalize(dest + '/' + path.basename(src));
if (fs.existsSync(thisDest) && options.no_force) {
common.error('dest file already exists: ' + thisDest, true);
return; // skip file
}
copyFileSync(src, thisDest);
}); // forEach(src)
}
module.exports = _cp;

View File

@ -142,7 +142,7 @@ assert.equal(shell.ls('-R', 'resources/cp') + '', shell.ls('-R', 'tmp/cp') + '')
shell.rm('-rf', 'tmp/*');
shell.cp('-R', 'resources/cp/', 'tmp/');
assert.equal(shell.error(), null);
assert.equal(shell.ls('-R', 'resources/cp') + '', shell.ls('-R', 'tmp') + '');
assert.equal(shell.ls('-R', 'resources/cp') + '', shell.ls('-R', 'tmp/cp') + '');
// recursive, globbing regular files with extension (see Github issue #376)
shell.rm('-rf', 'tmp/*');
@ -151,6 +151,13 @@ assert.equal(shell.error(), null);
assert.ok(fs.existsSync('tmp/file1.txt'));
assert.ok(fs.existsSync('tmp/file2.txt'));
// recursive, copying one regular file (also related to Github issue #376)
shell.rm('-rf', 'tmp/*');
shell.cp('-R', 'resources/file1.txt', 'tmp');
assert.equal(shell.error(), null);
assert.ok(fs.existsSync('tmp/file1.txt'));
assert.ok(!fs.statSync('tmp/file1.txt').isDirectory()); // don't let it be a dir
//recursive, everything exists, no force flag
shell.rm('-rf', 'tmp/*');
shell.cp('-R', 'resources/cp', 'tmp');
@ -179,12 +186,18 @@ shell.cp('-r', 'resources/issue44', 'tmp/dir2/dir3');
assert.ok(shell.error());
assert.equal(fs.existsSync('tmp/dir2'), false);
//recursive, creates dest dir, implicitly copies contents of source dir
//recursive, copies entire directory
shell.rm('-rf', 'tmp/*');
shell.cp('-r', 'resources/cp/dir_a', 'tmp/dest');
assert.equal(shell.error(), null);
assert.equal(fs.existsSync('tmp/dest/z'), true);
//recursive, with trailing slash, does the exact same
shell.rm('-rf', 'tmp/*');
shell.cp('-r', 'resources/cp/dir_a/', 'tmp/dest');
assert.equal(shell.error(), null);
assert.equal(fs.existsSync('tmp/dest/z'), true);
// On Windows, permission bits are quite different so skip those tests for now
if (common.platform !== 'win') {
//preserve mode bits
@ -201,4 +214,26 @@ shell.cp('-r', 'resources/ls/', 'tmp/');
assert.ok(!shell.error());
assert.ok(fs.existsSync('tmp/.hidden_file'));
// no-recursive will copy regular files only
shell.rm('-rf', 'tmp/');
shell.mkdir('tmp/');
shell.cp('resources/file1.txt', 'resources/ls/', 'tmp/');
assert.ok(shell.error());
assert.ok(!fs.existsSync('tmp/.hidden_file')); // doesn't copy dir contents
assert.ok(!fs.existsSync('tmp/ls')); // doesn't copy dir itself
assert.ok(fs.existsSync('tmp/file1.txt'));
// no-recursive will copy regular files only
shell.rm('-rf', 'tmp/');
shell.mkdir('tmp/');
shell.cp('resources/file1.txt', 'resources/file2.txt', 'resources/cp',
'resources/ls/', 'tmp/');
assert.ok(shell.error());
assert.ok(!fs.existsSync('tmp/.hidden_file')); // doesn't copy dir contents
assert.ok(!fs.existsSync('tmp/ls')); // doesn't copy dir itself
assert.ok(!fs.existsSync('tmp/a')); // doesn't copy dir contents
assert.ok(!fs.existsSync('tmp/cp')); // doesn't copy dir itself
assert.ok(fs.existsSync('tmp/file1.txt'));
assert.ok(fs.existsSync('tmp/file2.txt'));
shell.exit(123);

View File

@ -6,7 +6,6 @@ var oldConfigSilent = shell.config.silent;
shell.config.silent = true;
shell.rm('-rf', 'tmp');
shell.mkdir('tmp');
//
// Valids