Rewrite copy to use recursive pattern for dirs, add more tests

This commit is contained in:
Mani Maghsoudlou 2017-10-19 03:29:50 -07:00
parent 2599b67848
commit fe0bfe2931
10 changed files with 839 additions and 278 deletions

View File

@ -0,0 +1,192 @@
'use strict'
const assert = require('assert')
const os = require('os')
const path = require('path')
const fs = require(process.cwd())
const klawSync = require('klaw-sync')
/* global beforeEach, afterEach, describe, it */
describe('+ copySync() - prevent copying identical files and dirs', () => {
let TEST_DIR = ''
let src = ''
let dest = ''
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy-sync-prevent-copying-identical')
fs.emptyDir(TEST_DIR, done)
})
afterEach(done => fs.remove(TEST_DIR, done))
it('should return an error if src and dest are the same', done => {
const fileSrc = path.join(TEST_DIR, 'TEST_fs-extra_copy_sync')
const fileDest = path.join(TEST_DIR, 'TEST_fs-extra_copy_sync')
fs.copy(fileSrc, fileDest, err => {
assert.equal(err.message, 'Source and destination must not be the same.')
done()
})
})
// src is directory:
// src is regular, dest is symlink
// src is symlink, dest is regular
// src is symlink, dest is symlink
describe('> when the source is a directory', () => {
describe(`>> when src is regular and dest is a symlink that points to src`, () => {
it('should not copy and return', done => {
src = path.join(TEST_DIR, 'src')
fs.mkdirsSync(src)
const subdir = path.join(TEST_DIR, 'src', 'subdir')
fs.mkdirsSync(subdir)
fs.writeFileSync(path.join(subdir, 'file.txt'), 'some data')
const destLink = path.join(TEST_DIR, 'dest-symlink')
fs.symlinkSync(src, destLink, 'dir')
const oldlen = klawSync(src).length
fs.copy(src, destLink, err => {
assert.ifError(err)
const newlen = klawSync(src).length
assert.strictEqual(newlen, oldlen)
const link = fs.readlinkSync(destLink)
assert.strictEqual(link, src)
done()
})
})
})
describe(`>> when src is a symlink that points to a regular dest`, () => {
it('should throw error', done => {
dest = path.join(TEST_DIR, 'dest')
fs.mkdirsSync(dest)
const subdir = path.join(TEST_DIR, 'dest', 'subdir')
fs.mkdirsSync(subdir)
fs.writeFileSync(path.join(subdir, 'file.txt'), 'some data')
const srcLink = path.join(TEST_DIR, 'src-symlink')
fs.symlinkSync(dest, srcLink, 'dir')
const oldlen = klawSync(dest).length
fs.copy(srcLink, dest, err => {
assert.ok(err)
// assert nothing copied
const newlen = klawSync(dest).length
assert.strictEqual(newlen, oldlen)
const link = fs.readlinkSync(srcLink)
assert.strictEqual(link, dest)
done()
})
})
})
describe('>> when src and dest are symlinks that point to the exact same path', () => {
it('should not copy and return', done => {
src = path.join(TEST_DIR, 'src')
fs.mkdirsSync(src)
const srcLink = path.join(TEST_DIR, 'src_symlink')
fs.symlinkSync(src, srcLink, 'dir')
const destLink = path.join(TEST_DIR, 'dest_symlink')
fs.symlinkSync(src, destLink, 'dir')
const srclenBefore = klawSync(srcLink).length
const destlenBefore = klawSync(destLink).length
fs.copy(srcLink, destLink, err => {
assert.ifError(err)
const srclenAfter = klawSync(srcLink).length
assert.strictEqual(srclenAfter, srclenBefore, 'src length should not change')
const destlenAfter = klawSync(destLink).length
assert.strictEqual(destlenAfter, destlenBefore, 'dest length should not change')
const srcln = fs.readlinkSync(srcLink)
assert.strictEqual(srcln, src)
const destln = fs.readlinkSync(destLink)
assert.strictEqual(destln, src)
done()
})
})
})
})
// src is file:
// src is regular, dest is symlink
// src is symlink, dest is regular
// src is symlink, dest is symlink
describe('> when the source is a file', () => {
describe(`>> when src is regular and dest is a symlink that points to src`, () => {
it('should not copy and return', done => {
src = path.join(TEST_DIR, 'src', 'somefile.txt')
fs.ensureFileSync(src)
fs.writeFileSync(src, 'some data')
const destLink = path.join(TEST_DIR, 'dest-symlink')
fs.symlinkSync(src, destLink, 'file')
fs.copy(src, destLink, err => {
assert.ifError(err)
const link = fs.readlinkSync(destLink)
assert.strictEqual(link, src)
assert(fs.readFileSync(link, 'utf8'), 'some data')
done()
})
})
})
describe(`>> when src is a symlink that points to a regular dest`, () => {
it('should throw error', done => {
dest = path.join(TEST_DIR, 'dest', 'somefile.txt')
fs.ensureFileSync(dest)
fs.writeFileSync(dest, 'some data')
const srcLink = path.join(TEST_DIR, 'src-symlink')
fs.symlinkSync(dest, srcLink, 'file')
fs.copy(srcLink, dest, err => {
assert.ok(err)
const link = fs.readlinkSync(srcLink)
assert.strictEqual(link, dest)
assert(fs.readFileSync(link, 'utf8'), 'some data')
done()
})
})
})
describe('>> when src and dest are symlinks that point to the exact same path', () => {
it('should not copy and return', done => {
src = path.join(TEST_DIR, 'src', 'srcfile.txt')
fs.ensureFileSync(src)
fs.writeFileSync(src, 'src data')
const srcLink = path.join(TEST_DIR, 'src_symlink')
fs.symlinkSync(src, srcLink, 'file')
const destLink = path.join(TEST_DIR, 'dest_symlink')
fs.symlinkSync(src, destLink, 'file')
fs.copy(srcLink, destLink, err => {
assert.ifError(err)
const srcln = fs.readlinkSync(srcLink)
assert.strictEqual(srcln, src)
const destln = fs.readlinkSync(destLink)
assert.strictEqual(destln, src)
assert(fs.readFileSync(srcln, 'utf8'), 'src data')
assert(fs.readFileSync(destln, 'utf8'), 'src data')
done()
})
})
})
})
})

View File

@ -0,0 +1,372 @@
'use strict'
const assert = require('assert')
const os = require('os')
const path = require('path')
const fs = require(process.cwd())
const klawSync = require('klaw-sync')
/* global beforeEach, afterEach, describe, it */
// these files are used for all tests
const FILES = [
'file0.txt',
path.join('dir1', 'file1.txt'),
path.join('dir1', 'dir2', 'file2.txt'),
path.join('dir1', 'dir2', 'dir3', 'file3.txt')
]
const dat0 = 'file0'
const dat1 = 'file1'
const dat2 = 'file2'
const dat3 = 'file3'
function testSuccess (src, dest, done) {
const srclen = klawSync(src).length
assert(srclen > 2)
fs.copy(src, dest, err => {
assert.ifError(err)
const destlen = klawSync(dest).length
assert.strictEqual(destlen, srclen)
FILES.forEach(f => assert(fs.existsSync(path.join(dest, f)), 'file copied'))
const o0 = fs.readFileSync(path.join(dest, FILES[0]), 'utf8')
const o1 = fs.readFileSync(path.join(dest, FILES[1]), 'utf8')
const o2 = fs.readFileSync(path.join(dest, FILES[2]), 'utf8')
const o3 = fs.readFileSync(path.join(dest, FILES[3]), 'utf8')
assert.strictEqual(o0, dat0, 'file contents matched')
assert.strictEqual(o1, dat1, 'file contents matched')
assert.strictEqual(o2, dat2, 'file contents matched')
assert.strictEqual(o3, dat3, 'file contents matched')
done()
})
}
function testError (src, dest, done) {
fs.copy(src, dest, err => {
assert.strictEqual(err.message, `Cannot copy '${src}' to a subdirectory of itself, '${dest}'.`)
done()
})
}
describe('+ copy() - prevent copying into itself', () => {
let TEST_DIR, src
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy-prevent-copying-into-itself-4')
src = path.join(TEST_DIR, 'src')
fs.mkdirpSync(src)
fs.outputFileSync(path.join(src, FILES[0]), dat0)
fs.outputFileSync(path.join(src, FILES[1]), dat1)
fs.outputFileSync(path.join(src, FILES[2]), dat2)
fs.outputFileSync(path.join(src, FILES[3]), dat3)
done()
})
afterEach(done => fs.remove(TEST_DIR, done))
describe('> when source is a file', () => {
it('should copy the file successfully even when dest parent is a subdir of src', done => {
const srcFile = path.join(TEST_DIR, 'src', 'srcfile.txt')
const destFile = path.join(TEST_DIR, 'src', 'dest', 'destfile.txt')
fs.writeFileSync(srcFile, dat0)
fs.copy(srcFile, destFile, err => {
assert.ifError(err)
assert(fs.existsSync(destFile, 'file copied'))
const out = fs.readFileSync(destFile, 'utf8')
assert.strictEqual(out, dat0, 'file contents matched')
done()
})
})
})
// test for these cases:
// - src is directory, dest is directory
// - src is directory, dest is symlink
// - src is symlink, dest is directory
// - src is symlink, dest is symlink
describe('> when source is a directory', () => {
describe('>> when dest is a directory', () => {
it(`should copy the directory successfully when dest is 'src_dest'`, done => {
const dest = path.join(TEST_DIR, 'src_dest')
return testSuccess(src, dest, done)
})
it(`should copy the directory successfully when dest is 'src-dest'`, done => {
const dest = path.join(TEST_DIR, 'src-dest')
return testSuccess(src, dest, done)
})
it(`should copy the directory successfully when dest is 'dest_src'`, done => {
const dest = path.join(TEST_DIR, 'dest_src')
return testSuccess(src, dest, done)
})
it(`should copy the directory successfully when dest is 'src_dest/src'`, done => {
const dest = path.join(TEST_DIR, 'src_dest', 'src')
return testSuccess(src, dest, done)
})
it(`should copy the directory successfully when dest is 'src-dest/src'`, done => {
const dest = path.join(TEST_DIR, 'src-dest', 'src')
return testSuccess(src, dest, done)
})
it(`should copy the directory successfully when dest is 'dest_src/src'`, done => {
const dest = path.join(TEST_DIR, 'dest_src', 'src')
return testSuccess(src, dest, done)
})
it(`should copy the directory successfully when dest is 'src_src/dest'`, done => {
const dest = path.join(TEST_DIR, 'src_src', 'dest')
return testSuccess(src, dest, done)
})
it(`should copy the directory successfully when dest is 'src-src/dest'`, done => {
const dest = path.join(TEST_DIR, 'src-src', 'dest')
return testSuccess(src, dest, done)
})
it(`should copy the directory successfully when dest is 'srcsrc/dest'`, done => {
const dest = path.join(TEST_DIR, 'srcsrc', 'dest')
return testSuccess(src, dest, done)
})
it(`should copy the directory successfully when dest is 'dest/src'`, done => {
const dest = path.join(TEST_DIR, 'dest', 'src')
return testSuccess(src, dest, done)
})
it('should copy the directory successfully when dest is very nested that all its parents need to be created', done => {
const dest = path.join(TEST_DIR, 'dest', 'src', 'foo', 'bar', 'baz', 'qux', 'quux', 'waldo',
'grault', 'garply', 'fred', 'plugh', 'thud', 'some', 'highly', 'deeply',
'badly', 'nasty', 'crazy', 'mad', 'nested', 'dest')
return testSuccess(src, dest, done)
})
it(`should error when dest is 'src/dest'`, done => {
const dest = path.join(TEST_DIR, 'src', 'dest')
return testError(src, dest, done)
})
it(`should error when dest is 'src/src_dest'`, done => {
const dest = path.join(TEST_DIR, 'src', 'src_dest')
return testError(src, dest, done)
})
it(`should error when dest is 'src/dest_src'`, done => {
const dest = path.join(TEST_DIR, 'src', 'dest_src')
return testError(src, dest, done)
})
it(`should error when dest is 'src/dest/src'`, done => {
const dest = path.join(TEST_DIR, 'src', 'dest', 'src')
return testError(src, dest, done)
})
})
describe('>> when dest is a symlink', () => {
it('should not copy and return when dest points exactly to src', done => {
const destLink = path.join(TEST_DIR, 'dest-symlink')
fs.symlinkSync(src, destLink, 'dir')
const srclenBefore = klawSync(src).length
assert(srclenBefore > 2)
fs.copy(src, destLink, err => {
assert.ifError(err)
const srclenAfter = klawSync(src).length
assert.strictEqual(srclenAfter, srclenBefore, 'src length should not change')
const link = fs.readlinkSync(destLink)
assert.strictEqual(link, src)
done()
})
})
it('should copy the directory successfully when src is a subdir of resolved dest path', done => {
const srcInDest = path.join(TEST_DIR, 'dest', 'some', 'nested', 'src')
const destLink = path.join(TEST_DIR, 'dest-symlink')
fs.copySync(src, srcInDest) // put some stuff in srcInDest
const dest = path.join(TEST_DIR, 'dest')
fs.symlinkSync(dest, destLink, 'dir')
const srclen = klawSync(srcInDest).length
const destlenBefore = klawSync(dest).length
assert(srclen > 2)
fs.copy(srcInDest, destLink, err => {
assert.ifError(err)
const destlenAfter = klawSync(dest).length
// assert dest length is oldlen + length of stuff copied from src
assert.strictEqual(destlenAfter, destlenBefore + srclen, 'dest length should be equal to old length + copied legnth')
FILES.forEach(f => assert(fs.existsSync(path.join(dest, f)), 'file copied'))
const o0 = fs.readFileSync(path.join(dest, FILES[0]), 'utf8')
const o1 = fs.readFileSync(path.join(dest, FILES[1]), 'utf8')
const o2 = fs.readFileSync(path.join(dest, FILES[2]), 'utf8')
const o3 = fs.readFileSync(path.join(dest, FILES[3]), 'utf8')
assert.strictEqual(o0, dat0, 'files contents matched')
assert.strictEqual(o1, dat1, 'files contents matched')
assert.strictEqual(o2, dat2, 'files contents matched')
assert.strictEqual(o3, dat3, 'files contents matched')
done()
})
})
})
})
describe('> when source is a symlink', () => {
describe('>> when dest is a directory', () => {
it('should error when resolved src path points to dest', done => {
const srcLink = path.join(TEST_DIR, 'src-symlink')
fs.symlinkSync(src, srcLink, 'dir')
const dest = path.join(TEST_DIR, 'src')
fs.copy(srcLink, dest, err => {
assert(err)
// assert source not affected
const link = fs.readlinkSync(srcLink)
assert.strictEqual(link, src)
done()
})
})
it('should error when dest is a subdir of resolved src path', done => {
const srcLink = path.join(TEST_DIR, 'src-symlink')
fs.symlinkSync(src, srcLink, 'dir')
const dest = path.join(TEST_DIR, 'src', 'some', 'nested', 'dest')
fs.mkdirsSync(dest)
fs.copy(srcLink, dest, err => {
assert(err)
const link = fs.readlinkSync(srcLink)
assert.strictEqual(link, src)
done()
})
})
it('should error when resolved src path is a subdir of dest', done => {
const dest = path.join(TEST_DIR, 'dest')
const resolvedSrcPath = path.join(dest, 'contains', 'src')
const srcLink = path.join(TEST_DIR, 'src-symlink')
fs.copySync(src, resolvedSrcPath)
// make symlink that points to a subdir in dest
fs.symlinkSync(resolvedSrcPath, srcLink, 'dir')
fs.copy(srcLink, dest, err => {
assert(err)
done()
})
})
it(`should copy the directory successfully when dest is 'src_src/dest'`, done => {
const srcLink = path.join(TEST_DIR, 'src-symlink')
fs.symlinkSync(src, srcLink, 'dir')
const dest = path.join(TEST_DIR, 'src_src', 'dest')
testSuccess(srcLink, dest, () => {
const link = fs.readlinkSync(dest)
assert.strictEqual(link, src)
done()
})
})
it(`should copy the directory successfully when dest is 'srcsrc/dest'`, done => {
const srcLink = path.join(TEST_DIR, 'src-symlink')
fs.symlinkSync(src, srcLink, 'dir')
const dest = path.join(TEST_DIR, 'srcsrc', 'dest')
testSuccess(srcLink, dest, () => {
const link = fs.readlinkSync(dest)
assert.strictEqual(link, src)
done()
})
})
})
describe('>> when dest is a symlink', () => {
it('should silently return when resolved dest path is exactly the same as resolved src path', done => {
const srcLink = path.join(TEST_DIR, 'src-symlink')
fs.symlinkSync(src, srcLink, 'dir')
const destLink = path.join(TEST_DIR, 'dest-symlink')
fs.symlinkSync(src, destLink, 'dir')
const srclenBefore = klawSync(srcLink).length
const destlenBefore = klawSync(destLink).length
assert(srclenBefore > 2)
assert(destlenBefore > 2)
fs.copy(srcLink, destLink, err => {
assert.ifError(err)
const srclenAfter = klawSync(srcLink).length
assert.strictEqual(srclenAfter, srclenBefore, 'src length should not change')
const destlenAfter = klawSync(destLink).length
assert.strictEqual(destlenAfter, destlenBefore, 'dest length should not change')
const srcln = fs.readlinkSync(srcLink)
assert.strictEqual(srcln, src)
const destln = fs.readlinkSync(destLink)
assert.strictEqual(destln, src)
done()
})
})
it('should error when resolved dest path is a subdir of resolved src path', done => {
const srcLink = path.join(TEST_DIR, 'src-symlink')
fs.symlinkSync(src, srcLink, 'dir')
const destLink = path.join(TEST_DIR, 'dest-symlink')
const resolvedDestPath = path.join(TEST_DIR, 'src', 'some', 'nested', 'dest')
fs.ensureFileSync(path.join(resolvedDestPath, 'subdir', 'somefile.txt'))
fs.symlinkSync(resolvedDestPath, destLink, 'dir')
fs.copy(srcLink, destLink, err => {
assert.ifError(err)
const destln = fs.readlinkSync(destLink)
assert.strictEqual(destln, src)
done()
})
})
it('should error when resolved src path is a subdir of resolved dest path', done => {
const srcInDest = path.join(TEST_DIR, 'dest', 'some', 'nested', 'src')
const srcLink = path.join(TEST_DIR, 'src-symlink')
const destLink = path.join(TEST_DIR, 'dest-symlink')
const dest = path.join(TEST_DIR, 'dest')
fs.mkdirSync(dest)
fs.symlinkSync(srcInDest, srcLink, 'dir')
fs.symlinkSync(dest, destLink, 'dir')
fs.copy(srcLink, destLink, err => {
assert.strictEqual(err.message, `Cannot overwrite '${dest}' with '${srcInDest}'.`)
const destln = fs.readlinkSync(destLink)
assert.strictEqual(destln, dest)
done()
})
})
})
})
})

View File

@ -30,6 +30,30 @@ describe('fs-extra', () => {
})
})
it('should error when overwrite=false and file exists', done => {
const src = path.join(TEST_DIR, 'src.txt')
const dest = path.join(TEST_DIR, 'dest.txt')
fse.ensureFileSync(src)
fse.ensureFileSync(dest)
fse.copy(src, dest, {overwrite: false, errorOnExist: true}, err => {
assert(err)
done()
})
})
it('should error when overwrite=false and file exists in a dir', done => {
const src = path.join(TEST_DIR, 'src', 'sfile.txt')
const dest = path.join(TEST_DIR, 'dest', 'dfile.txt')
fse.ensureFileSync(src)
fse.ensureFileSync(dest)
fse.copy(src, dest, {overwrite: false, errorOnExist: true}, err => {
assert(err)
done()
})
})
describe('> when the source is a file', () => {
it('should copy the file asynchronously', done => {
const fileSrc = path.join(TEST_DIR, 'TEST_fs-extra_src')
@ -98,6 +122,20 @@ describe('fs-extra', () => {
})
})
describe('> when dest exists and is a file', () => {
it('should return an error', done => {
const src = path.join(TEST_DIR, 'src')
const dest = path.join(TEST_DIR, 'file.txt')
fs.mkdirSync(src)
fse.ensureFileSync(dest)
fse.copy(src, dest, err => {
assert.strictEqual(err.message, `Cannot overwrite non-directory '${dest}' with directory '${src}'.`)
done()
})
})
})
it('should copy the directory asynchronously', done => {
const FILES = 2
const src = path.join(TEST_DIR, 'src')

View File

@ -3,7 +3,7 @@
const fs = require('fs')
const os = require('os')
const fse = require(process.cwd())
const ncp = require('../../ncp')
const ncp = require('../../copy')
const path = require('path')
const assert = require('assert')

View File

@ -5,7 +5,7 @@
const fs = require('fs')
const os = require('os')
const fse = require(process.cwd())
const ncp = require('../../ncp')
const ncp = require('../../copy')
const path = require('path')
const assert = require('assert')

View File

@ -1,7 +1,7 @@
'use strict'
const fs = require('fs')
const ncp = require('../../ncp')
const ncp = require('../../copy')
const path = require('path')
const rimraf = require('rimraf')
const assert = require('assert')

View File

@ -3,7 +3,7 @@
const fs = require('fs')
const os = require('os')
const fse = require(process.cwd())
const ncp = require('../../ncp')
const ncp = require('../../copy')
const path = require('path')
const assert = require('assert')

View File

@ -2,53 +2,246 @@
const fs = require('graceful-fs')
const path = require('path')
const ncp = require('./ncp')
const mkdir = require('../mkdirs')
const mkdirp = require('../mkdirs').mkdirs
const pathExists = require('../path-exists').pathExists
const utimes = require('../util/utimes').utimesMillis
function copy (src, dest, options, callback) {
if (typeof options === 'function' && !callback) {
callback = options
options = {}
} else if (typeof options === 'function' || options instanceof RegExp) {
options = {filter: options}
const notExist = Symbol('notExist')
const existsReg = Symbol('existsReg')
function copy (src, dest, opts, cb) {
if (typeof opts === 'function' && !cb) {
cb = opts
opts = {}
} else if (typeof opts === 'function' || opts instanceof RegExp) {
opts = {filter: opts}
}
callback = callback || function () {}
options = options || {}
// Warn about using preserveTimestamps on 32-bit node:
if (options.preserveTimestamps && process.arch === 'ia32') {
cb = cb || function () {}
opts = opts || {}
opts.clobber = 'clobber' in opts ? !!opts.clobber : true // default to true for now
opts.overwrite = 'overwrite' in opts ? !!opts.overwrite : opts.clobber // overwrite falls back to clobber
// Warn about using preserveTimestamps on 32-bit node
if (opts.preserveTimestamps && process.arch === 'ia32') {
console.warn(`fs-extra: Using the preserveTimestamps option in 32-bit node is not recommended;\n
see https://github.com/jprichardson/node-fs-extra/issues/269`)
}
src = path.resolve(src)
dest = path.resolve(dest)
// don't allow src and dest to be the same
const basePath = process.cwd()
const currentPath = path.resolve(basePath, src)
const targetPath = path.resolve(basePath, dest)
if (currentPath === targetPath) return callback(new Error('Source and destination must not be the same.'))
if (src === dest) return cb(new Error('Source and destination must not be the same.'))
fs.lstat(src, (err, stats) => {
if (err) return callback(err)
let dir = null
if (stats.isDirectory()) {
const parts = dest.split(path.sep)
parts.pop()
dir = parts.join(path.sep)
} else {
dir = path.dirname(dest)
}
pathExists(dir, (err, dirExists) => {
if (err) return callback(err)
if (dirExists) return ncp(src, dest, options, callback)
mkdir.mkdirs(dir, err => {
if (err) return callback(err)
ncp(src, dest, options, callback)
})
const destParent = path.dirname(dest)
pathExists(destParent, (err, dirExists) => {
if (err) return cb(err)
if (dirExists) return startCopy(src, dest, opts, cb)
mkdirp(destParent, err => {
if (err) return cb(err)
return startCopy(src, dest, opts, cb)
})
})
}
function startCopy (src, dest, opts, cb) {
if (opts.filter) {
if (opts.filter instanceof RegExp) {
console.warn('Warning: fs-extra: Passing a RegExp filter is deprecated, use a function')
if (!opts.filter.test(src)) return cb()
} else if (typeof opts.filter === 'function') {
if (!opts.filter(src, dest)) return cb()
}
}
return getStats(src, dest, opts, cb)
}
function getStats (src, dest, opts, cb) {
const stat = opts.dereference ? fs.stat : fs.lstat
stat(src, (err, st) => {
if (err) return cb(err)
if (st.isDirectory()) return onDir(st, src, dest, opts, cb)
else if (st.isFile() ||
st.isCharacterDevice() ||
st.isBlockDevice()) return onFile(st, src, dest, opts, cb)
else if (st.isSymbolicLink()) return onLink(src, dest, opts, cb)
})
}
function onFile (srcStat, src, dest, opts, cb) {
checkDest(dest, (err, resolvedPath) => {
if (err) return cb(err)
if (resolvedPath === notExist) {
return cpFile(srcStat, src, dest, opts, cb)
} else if (resolvedPath === existsReg) {
return mayCopyFile(srcStat, src, dest, opts, cb)
} else {
if (src === resolvedPath) return cb()
return mayCopyFile(srcStat, src, dest, opts, cb)
}
})
}
function mayCopyFile (srcStat, src, dest, opts, cb) {
if (opts.overwrite) {
fs.unlink(dest, err => {
if (err) return cb(err)
return cpFile(srcStat, src, dest, opts, cb)
})
} else if (opts.errorOnExist) {
return cb(new Error(`'${dest}' already exists`))
} else return cb()
}
function cpFile (srcStat, src, dest, opts, cb) {
const rs = fs.createReadStream(src)
const ws = fs.createWriteStream(dest, { mode: srcStat.mode })
rs.on('error', err => cb(err))
ws.on('error', err => cb(err))
ws.on('open', () => {
rs.pipe(ws)
}).once('close', () => {
fs.chmod(dest, srcStat.mode, err => {
if (err) return cb(err)
if (opts.preserveTimestamps) {
return utimes(dest, srcStat.atime, srcStat.mtime, cb)
}
return cb()
})
})
}
function onDir (srcStat, src, dest, opts, cb) {
checkDest(dest, (err, resolvedPath) => {
if (err) return cb(err)
if (resolvedPath === notExist) {
if (isSrcSubdir(src, dest)) {
return cb(new Error(`Cannot copy '${src}' to a subdirectory of itself, '${dest}'.`))
}
return mkDirAndCopy(srcStat, src, dest, opts, cb)
} else if (resolvedPath === existsReg) {
if (isSrcSubdir(src, dest)) {
return cb(new Error(`Cannot copy '${src}' to a subdirectory of itself, '${dest}'.`))
}
return mayCopyDir(src, dest, opts, cb)
} else {
if (src === resolvedPath) return cb()
return cpDir(src, dest, opts, cb)
}
})
}
function mayCopyDir (src, dest, opts, cb) {
fs.stat(dest, (err, st) => {
if (err) return cb(err)
if (!st.isDirectory()) {
return cb(new Error(`Cannot overwrite non-directory '${dest}' with directory '${src}'.`))
}
return cpDir(src, dest, opts, cb)
})
}
function mkDirAndCopy (srcStat, src, dest, opts, cb) {
fs.mkdir(dest, srcStat.mode, err => {
if (err) return cb(err)
fs.chmod(dest, srcStat.mode, err => {
if (err) return cb(err)
return cpDir(src, dest, opts, cb)
})
})
}
function cpDir (src, dest, opts, cb) {
fs.readdir(src, (err, items) => {
if (err) return cb(err)
return cpDirItems(items, src, dest, opts, cb)
})
}
function cpDirItems (items, src, dest, opts, cb) {
const item = items.pop()
if (!item) return cb()
startCopy(path.join(src, item), path.join(dest, item), opts, err => {
if (err) return cb(err)
return cpDirItems(items, src, dest, opts, cb)
})
}
function onLink (src, dest, opts, cb) {
fs.readlink(src, (err, resolvedSrcPath) => {
if (err) return cb(err)
if (opts.dereference) {
resolvedSrcPath = path.resolve(process.cwd(), resolvedSrcPath)
}
checkDest(dest, (err, resolvedDestPath) => {
if (err) return cb(err)
if (resolvedDestPath === notExist || resolvedDestPath === existsReg) {
// if dest already exists, fs throws error anyway,
// so no need to guard against it here.
return fs.symlink(resolvedSrcPath, dest, cb)
} else {
if (opts.dereference) {
resolvedDestPath = path.resolve(process.cwd(), resolvedDestPath)
}
if (resolvedDestPath === resolvedSrcPath) return cb()
// prevent copy if src is a subdir of dest since unlinking
// dest in this case results in removing src contents
// and therefore a broken symlink will be created.
fs.stat(dest, (err, st) => {
if (err) return cb(err)
if (st.isDirectory() && isSrcSubdir(resolvedDestPath, resolvedSrcPath)) {
return cb(new Error(`Cannot overwrite '${resolvedDestPath}' with '${resolvedSrcPath}'.`))
}
return cpLink(resolvedSrcPath, dest, cb)
})
}
})
})
}
function cpLink (resolvedSrcPath, dest, cb) {
fs.unlink(dest, err => {
if (err) return cb(err)
return fs.symlink(resolvedSrcPath, dest, cb)
})
}
// check dest to see if it exists and/or is a symlink
function checkDest (dest, cb) {
fs.readlink(dest, (err, resolvedPath) => {
if (err) {
if (err.code === 'ENOENT') return cb(null, notExist)
// dest exists and is a regular file or directory, Windows throws UNKNOWN error.
if (err.code === 'EINVAL' || err.code === 'UNKNOWN') return cb(null, existsReg)
return cb(err)
}
return cb(null, resolvedPath) // dest exists and is a symlink
})
}
// return true if dest is a subdir of src, otherwise false.
// extract dest base dir and check if that is the same as src basename
function isSrcSubdir (src, dest) {
const baseDir = dest.split(path.dirname(src) + path.sep)[1]
if (baseDir) {
const destBasename = baseDir.split(path.sep)[0]
if (destBasename) {
return src !== dest && dest.indexOf(src) > -1 && destBasename === path.basename(src)
}
return false
}
return false
}
module.exports = copy

View File

@ -1,234 +0,0 @@
// imported from ncp (this is temporary, will rewrite)
var fs = require('graceful-fs')
var path = require('path')
var utimes = require('../util/utimes')
function ncp (source, dest, options, callback) {
if (!callback) {
callback = options
options = {}
}
var basePath = process.cwd()
var currentPath = path.resolve(basePath, source)
var targetPath = path.resolve(basePath, dest)
var filter = options.filter
var transform = options.transform
var overwrite = options.overwrite
// If overwrite is undefined, use clobber, otherwise default to true:
if (overwrite === undefined) overwrite = options.clobber
if (overwrite === undefined) overwrite = true
var errorOnExist = options.errorOnExist
var dereference = options.dereference
var preserveTimestamps = options.preserveTimestamps === true
var started = 0
var finished = 0
var running = 0
var errored = false
startCopy(currentPath)
function startCopy (source) {
started++
if (filter) {
if (filter instanceof RegExp) {
console.warn('Warning: fs-extra: Passing a RegExp filter is deprecated, use a function')
if (!filter.test(source)) {
return doneOne(true)
}
} else if (typeof filter === 'function') {
if (!filter(source, dest)) {
return doneOne(true)
}
}
}
return getStats(source)
}
function getStats (source) {
var stat = dereference ? fs.stat : fs.lstat
running++
stat(source, function (err, stats) {
if (err) return onError(err)
// We need to get the mode from the stats object and preserve it.
var item = {
name: source,
mode: stats.mode,
mtime: stats.mtime, // modified time
atime: stats.atime, // access time
stats: stats // temporary
}
if (stats.isDirectory()) {
return onDir(item)
} else if (stats.isFile() || stats.isCharacterDevice() || stats.isBlockDevice()) {
return onFile(item)
} else if (stats.isSymbolicLink()) {
// Symlinks don't really need to know about the mode.
return onLink(source)
}
})
}
function onFile (file) {
var target = file.name.replace(currentPath, targetPath.replace('$', '$$$$')) // escapes '$' with '$$'
isWritable(target, function (writable) {
if (writable) {
copyFile(file, target)
} else {
if (overwrite) {
rmFile(target, function () {
copyFile(file, target)
})
} else if (errorOnExist) {
onError(new Error(target + ' already exists'))
} else {
doneOne()
}
}
})
}
function copyFile (file, target) {
var readStream = fs.createReadStream(file.name)
var writeStream = fs.createWriteStream(target, { mode: file.mode })
readStream.on('error', onError)
writeStream.on('error', onError)
if (transform) {
transform(readStream, writeStream, file)
} else {
writeStream.on('open', function () {
readStream.pipe(writeStream)
})
}
writeStream.once('close', function () {
fs.chmod(target, file.mode, function (err) {
if (err) return onError(err)
if (preserveTimestamps) {
utimes.utimesMillis(target, file.atime, file.mtime, function (err) {
if (err) return onError(err)
return doneOne()
})
} else {
doneOne()
}
})
})
}
function rmFile (file, done) {
fs.unlink(file, function (err) {
if (err) return onError(err)
return done()
})
}
function onDir (dir) {
var target = dir.name.replace(currentPath, targetPath.replace('$', '$$$$')) // escapes '$' with '$$'
isWritable(target, function (writable) {
if (writable) {
return mkDir(dir, target)
}
copyDir(dir.name)
})
}
function mkDir (dir, target) {
fs.mkdir(target, dir.mode, function (err) {
if (err) return onError(err)
// despite setting mode in fs.mkdir, doesn't seem to work
// so we set it here.
fs.chmod(target, dir.mode, function (err) {
if (err) return onError(err)
copyDir(dir.name)
})
})
}
function copyDir (dir) {
fs.readdir(dir, function (err, items) {
if (err) return onError(err)
items.forEach(function (item) {
startCopy(path.join(dir, item))
})
return doneOne()
})
}
function onLink (link) {
var target = link.replace(currentPath, targetPath)
fs.readlink(link, function (err, resolvedPath) {
if (err) return onError(err)
checkLink(resolvedPath, target)
})
}
function checkLink (resolvedPath, target) {
if (dereference) {
resolvedPath = path.resolve(basePath, resolvedPath)
}
isWritable(target, function (writable) {
if (writable) {
return makeLink(resolvedPath, target)
}
fs.readlink(target, function (err, targetDest) {
if (err) return onError(err)
if (dereference) {
targetDest = path.resolve(basePath, targetDest)
}
if (targetDest === resolvedPath) {
return doneOne()
}
return rmFile(target, function () {
makeLink(resolvedPath, target)
})
})
})
}
function makeLink (linkPath, target) {
fs.symlink(linkPath, target, function (err) {
if (err) return onError(err)
return doneOne()
})
}
function isWritable (path, done) {
fs.lstat(path, function (err) {
if (err) {
if (err.code === 'ENOENT') return done(true)
return done(false)
}
return done(false)
})
}
function onError (err) {
// ensure callback is defined & called only once:
if (!errored && callback !== undefined) {
errored = true
return callback(err)
}
}
function doneOne (skipped) {
if (!skipped) running--
finished++
if ((started === finished) && (running === 0)) {
if (callback !== undefined) {
return callback(null)
}
}
}
}
module.exports = ncp

View File

@ -8,7 +8,7 @@
const u = require('universalify').fromCallback
const fs = require('graceful-fs')
const ncp = require('../copy/ncp')
const copy = require('../copy/copy')
const path = require('path')
const remove = require('../remove').remove
const mkdirp = require('../mkdirs').mkdirs
@ -133,14 +133,14 @@ function moveDirAcrossDevice (src, dest, overwrite, callback) {
if (overwrite) {
remove(dest, err => {
if (err) return callback(err)
startNcp()
startCopy()
})
} else {
startNcp()
startCopy()
}
function startNcp () {
ncp(src, dest, options, err => {
function startCopy () {
copy(src, dest, options, err => {
if (err) return callback(err)
remove(src, callback)
})