Skip to content

Commit f21048b

Browse files
manidlouRyanZim
authored andcommitted
BREAKING: copy*(): allow copying broken symlinks (#779)
1 parent 7498c9c commit f21048b

20 files changed

+215
-142
lines changed

lib/copy-sync/__tests__/broken-symlink.test.js renamed to lib/copy-sync/__tests__/copy-sync-broken-symlink.test.js

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,17 @@
22

33
const fs = require('fs')
44
const os = require('os')
5-
const fse = require('../../')
5+
const fse = require('../..')
66
const path = require('path')
77
const assert = require('assert')
88
const copySync = require('../copy-sync')
99

1010
/* global afterEach, beforeEach, describe, it */
1111

1212
describe('copy-sync / broken symlink', () => {
13-
const TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy-sync-broken-symlinks')
13+
const TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy-sync-broken-symlink')
1414
const src = path.join(TEST_DIR, 'src')
15-
const out = path.join(TEST_DIR, 'out')
15+
const dest = path.join(TEST_DIR, 'dest')
1616

1717
beforeEach(done => {
1818
fse.emptyDir(TEST_DIR, err => {
@@ -23,12 +23,20 @@ describe('copy-sync / broken symlink', () => {
2323

2424
afterEach(done => fse.remove(TEST_DIR, done))
2525

26-
it('should error if symlink is broken', () => {
27-
assert.throws(() => copySync(src, out))
28-
})
26+
describe('when symlink is broken', () => {
27+
it('should not throw error if dereference is false', () => {
28+
let err = null
29+
try {
30+
copySync(src, dest)
31+
} catch (e) {
32+
err = e
33+
}
34+
assert.strictEqual(err, null)
35+
})
2936

30-
it('should throw an error when dereference=true', () => {
31-
assert.throws(() => copySync(src, out, { dereference: true }), err => err.code === 'ENOENT')
37+
it('should throw error if dereference is true', () => {
38+
assert.throws(() => copySync(src, dest, { dereference: true }), err => err.code === 'ENOENT')
39+
})
3240
})
3341
})
3442

lib/copy-sync/__tests__/copy-sync-file.test.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,21 @@ describe('+ copySync() / file', () => {
2121
afterEach(done => fs.remove(TEST_DIR, done))
2222

2323
describe('> when src is a file', () => {
24+
describe('> when dest exists and is a directory', () => {
25+
it('should throw error', () => {
26+
const src = path.join(TEST_DIR, 'file.txt')
27+
const dest = path.join(TEST_DIR, 'dir')
28+
fs.ensureFileSync(src)
29+
fs.ensureDirSync(dest)
30+
31+
try {
32+
fs.copySync(src, dest)
33+
} catch (err) {
34+
assert.strictEqual(err.message, `Cannot overwrite directory '${dest}' with non-directory '${src}'.`)
35+
}
36+
})
37+
})
38+
2439
it('should copy the file synchronously', () => {
2540
const fileSrc = path.join(TEST_DIR, 'TEST_fs-extra_src')
2641
const fileDest = path.join(TEST_DIR, 'TEST_fs-extra_copy')

lib/copy-sync/__tests__/copy-sync-prevent-copying-identical.test.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ describe('+ copySync() - prevent copying identical files and dirs', () => {
113113

114114
describe('> when src is a directory', () => {
115115
describe('>> when src is regular and dest is a symlink that points to src', () => {
116-
it('should error', () => {
116+
it('should error if dereference is true', () => {
117117
src = path.join(TEST_DIR, 'src')
118118
fs.mkdirsSync(src)
119119
const subdir = path.join(TEST_DIR, 'src', 'subdir')
@@ -126,7 +126,7 @@ describe('+ copySync() - prevent copying identical files and dirs', () => {
126126
const oldlen = klawSync(src).length
127127

128128
try {
129-
fs.copySync(src, destLink)
129+
fs.copySync(src, destLink, { dereference: true })
130130
} catch (err) {
131131
assert.strictEqual(err.message, 'Source and destination must not be the same.')
132132
}
@@ -166,7 +166,7 @@ describe('+ copySync() - prevent copying identical files and dirs', () => {
166166
})
167167

168168
describe('>> when src and dest are symlinks that point to the exact same path', () => {
169-
it('should error src and dest are the same', () => {
169+
it('should error if src and dest are the same and dereferene is true', () => {
170170
src = path.join(TEST_DIR, 'src')
171171
fs.mkdirsSync(src)
172172
const srcLink = path.join(TEST_DIR, 'src_symlink')
@@ -178,7 +178,7 @@ describe('+ copySync() - prevent copying identical files and dirs', () => {
178178
const destlenBefore = klawSync(destLink).length
179179

180180
try {
181-
fs.copySync(srcLink, destLink)
181+
fs.copySync(srcLink, destLink, { dereference: true })
182182
} catch (err) {
183183
assert.strictEqual(err.message, 'Source and destination must not be the same.')
184184
}
@@ -203,7 +203,7 @@ describe('+ copySync() - prevent copying identical files and dirs', () => {
203203

204204
describe('> when src is a file', () => {
205205
describe('>> when src is regular and dest is a symlink that points to src', () => {
206-
it('should error', () => {
206+
it('should error if dereference is true', () => {
207207
src = path.join(TEST_DIR, 'src', 'somefile.txt')
208208
fs.ensureFileSync(src)
209209
fs.writeFileSync(src, 'some data')
@@ -212,7 +212,7 @@ describe('+ copySync() - prevent copying identical files and dirs', () => {
212212
fs.symlinkSync(src, destLink, 'file')
213213

214214
try {
215-
fs.copySync(src, destLink)
215+
fs.copySync(src, destLink, { dereference: true })
216216
} catch (err) {
217217
assert.strictEqual(err.message, 'Source and destination must not be the same.')
218218
}
@@ -243,7 +243,7 @@ describe('+ copySync() - prevent copying identical files and dirs', () => {
243243
})
244244

245245
describe('>> when src and dest are symlinks that point to the exact same path', () => {
246-
it('should error src and dest are the same', () => {
246+
it('should error if src and dest are the same and dereference is true', () => {
247247
src = path.join(TEST_DIR, 'src', 'srcfile.txt')
248248
fs.outputFileSync(src, 'src data')
249249

@@ -254,7 +254,7 @@ describe('+ copySync() - prevent copying identical files and dirs', () => {
254254
fs.symlinkSync(src, destLink, 'file')
255255

256256
try {
257-
fs.copySync(srcLink, destLink)
257+
fs.copySync(srcLink, destLink, { dereference: true })
258258
} catch (err) {
259259
assert.strictEqual(err.message, 'Source and destination must not be the same.')
260260
}

lib/copy-sync/__tests__/copy-sync-prevent-copying-into-itself.test.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -146,15 +146,15 @@ describe('+ copySync() - prevent copying into itself', () => {
146146
})
147147

148148
describe('>> when dest is a symlink', () => {
149-
it('should error when dest points exactly to src', () => {
149+
it('should error when dest points exactly to src and dereference is true', () => {
150150
const destLink = path.join(TEST_DIR, 'dest-symlink')
151151
fs.symlinkSync(src, destLink, 'dir')
152152

153153
const srclenBefore = klawSync(src).length
154154
assert(srclenBefore > 2)
155155

156156
try {
157-
fs.copySync(src, destLink)
157+
fs.copySync(src, destLink, { dereference: true })
158158
} catch (err) {
159159
assert.strictEqual(err.message, 'Source and destination must not be the same.')
160160
}
@@ -216,7 +216,7 @@ describe('+ copySync() - prevent copying into itself', () => {
216216
}
217217
})
218218

219-
it('should copy the directory successfully when src is a subdir of resolved dest path', () => {
219+
it('should copy the directory successfully when src is a subdir of resolved dest path and dereferene is true', () => {
220220
const srcInDest = path.join(TEST_DIR, 'dest', 'some', 'nested', 'src')
221221
const destLink = path.join(TEST_DIR, 'dest-symlink')
222222
fs.copySync(src, srcInDest) // put some stuff in srcInDest
@@ -226,9 +226,9 @@ describe('+ copySync() - prevent copying into itself', () => {
226226

227227
const srclen = klawSync(srcInDest).length
228228
const destlenBefore = klawSync(dest).length
229-
230229
assert(srclen > 2)
231-
fs.copySync(srcInDest, destLink)
230+
231+
fs.copySync(srcInDest, destLink, { dereference: true })
232232

233233
const destlenAfter = klawSync(dest).length
234234

@@ -323,7 +323,7 @@ describe('+ copySync() - prevent copying into itself', () => {
323323
})
324324

325325
describe('>> when dest is a symlink', () => {
326-
it('should error when resolved dest path is exactly the same as resolved src path', () => {
326+
it('should error when resolved dest path is exactly the same as resolved src path and dereference is true', () => {
327327
const srcLink = path.join(TEST_DIR, 'src-symlink')
328328
fs.symlinkSync(src, srcLink, 'dir')
329329
const destLink = path.join(TEST_DIR, 'dest-symlink')
@@ -335,7 +335,7 @@ describe('+ copySync() - prevent copying into itself', () => {
335335
assert(destlenBefore > 2)
336336

337337
try {
338-
fs.copySync(srcLink, destLink)
338+
fs.copySync(srcLink, destLink, { dereference: true })
339339
} catch (err) {
340340
assert.strictEqual(err.message, 'Source and destination must not be the same.')
341341
} finally {

lib/copy-sync/__tests__/symlink.test.js renamed to lib/copy-sync/__tests__/copy-sync-symlink.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use strict'
22

33
const os = require('os')
4-
const fs = require('../../')
4+
const fs = require('../..')
55
const path = require('path')
66
const assert = require('assert')
77
const copySync = require('../copy-sync')

lib/copy-sync/copy-sync.js

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ function copySync (src, dest, opts) {
2121
see https://github.com/jprichardson/node-fs-extra/issues/269`)
2222
}
2323

24-
const { srcStat, destStat } = stat.checkPathsSync(src, dest, 'copy')
24+
const { srcStat, destStat } = stat.checkPathsSync(src, dest, 'copy', opts)
2525
stat.checkParentPathsSync(src, srcStat, dest, 'copy')
2626
return handleFilterAndCopy(destStat, src, dest, opts)
2727
}
@@ -98,13 +98,8 @@ function setDestTimestamps (src, dest) {
9898
}
9999

100100
function onDir (srcStat, destStat, src, dest, opts) {
101-
if (destStat) {
102-
if (destStat.isDirectory()) {
103-
return copyDir(src, dest, opts)
104-
}
105-
throw new Error(`Cannot overwrite non-directory '${dest}' with directory '${src}'.`)
106-
}
107-
return mkDirAndCopy(srcStat.mode, src, dest, opts)
101+
if (!destStat) return mkDirAndCopy(srcStat.mode, src, dest, opts)
102+
return copyDir(src, dest, opts)
108103
}
109104

110105
function mkDirAndCopy (srcMode, src, dest, opts) {
@@ -120,7 +115,7 @@ function copyDir (src, dest, opts) {
120115
function copyDirItem (item, src, dest, opts) {
121116
const srcItem = path.join(src, item)
122117
const destItem = path.join(dest, item)
123-
const { destStat } = stat.checkPathsSync(srcItem, destItem, 'copy')
118+
const { destStat } = stat.checkPathsSync(srcItem, destItem, 'copy', opts)
124119
return startCopy(destStat, srcItem, destItem, opts)
125120
}
126121

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
'use strict'
2+
3+
const fs = require('fs')
4+
const os = require('os')
5+
const fse = require('../..')
6+
const path = require('path')
7+
const assert = require('assert')
8+
const copy = require('../copy')
9+
10+
/* global afterEach, beforeEach, describe, it */
11+
12+
describe('copy / broken symlink', () => {
13+
const TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy-broken-symlink')
14+
const src = path.join(TEST_DIR, 'src')
15+
const dest = path.join(TEST_DIR, 'dest')
16+
17+
beforeEach(done => {
18+
fse.emptyDir(TEST_DIR, err => {
19+
assert.ifError(err)
20+
createFixtures(src, done)
21+
})
22+
})
23+
24+
afterEach(done => fse.remove(TEST_DIR, done))
25+
26+
describe('when symlink is broken', () => {
27+
it('should not throw error if dereference is false', done => {
28+
copy(src, dest, err => {
29+
assert.strictEqual(err, null)
30+
done()
31+
})
32+
})
33+
34+
it('should throw error if dereference is true', done => {
35+
copy(src, dest, { dereference: true }, err => {
36+
assert.strictEqual(err.code, 'ENOENT')
37+
done()
38+
})
39+
})
40+
})
41+
})
42+
43+
function createFixtures (srcDir, callback) {
44+
fs.mkdir(srcDir, err => {
45+
let brokenFile
46+
let brokenFileLink
47+
48+
if (err) return callback(err)
49+
50+
try {
51+
brokenFile = path.join(srcDir, 'does-not-exist')
52+
brokenFileLink = path.join(srcDir, 'broken-symlink')
53+
fs.writeFileSync(brokenFile, 'does not matter')
54+
fs.symlinkSync(brokenFile, brokenFileLink, 'file')
55+
} catch (err) {
56+
callback(err)
57+
}
58+
59+
// break the symlink now
60+
fse.remove(brokenFile, callback)
61+
})
62+
}

lib/copy/__tests__/copy-prevent-copying-identical.test.js

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ describe('+ copy() - prevent copying identical files and dirs', () => {
104104

105105
describe('> when src is a directory', () => {
106106
describe('>> when src is regular and dest is a symlink that points to src', () => {
107-
it('should error', done => {
107+
it('should error if dereference is true', done => {
108108
src = path.join(TEST_DIR, 'src')
109109
fs.mkdirsSync(src)
110110
const subdir = path.join(TEST_DIR, 'src', 'subdir')
@@ -116,7 +116,7 @@ describe('+ copy() - prevent copying identical files and dirs', () => {
116116

117117
const oldlen = klawSync(src).length
118118

119-
fs.copy(src, destLink, err => {
119+
fs.copy(src, destLink, { dereference: true }, err => {
120120
assert.strictEqual(err.message, 'Source and destination must not be the same.')
121121

122122
const newlen = klawSync(src).length
@@ -155,7 +155,7 @@ describe('+ copy() - prevent copying identical files and dirs', () => {
155155
})
156156

157157
describe('>> when src and dest are symlinks that point to the exact same path', () => {
158-
it('should error src and dest are the same', done => {
158+
it('should error src and dest are the same and dereference is true', done => {
159159
src = path.join(TEST_DIR, 'src')
160160
fs.mkdirsSync(src)
161161
const srcLink = path.join(TEST_DIR, 'src_symlink')
@@ -166,7 +166,7 @@ describe('+ copy() - prevent copying identical files and dirs', () => {
166166
const srclenBefore = klawSync(srcLink).length
167167
const destlenBefore = klawSync(destLink).length
168168

169-
fs.copy(srcLink, destLink, err => {
169+
fs.copy(srcLink, destLink, { dereference: true }, err => {
170170
assert.strictEqual(err.message, 'Source and destination must not be the same.')
171171

172172
const srclenAfter = klawSync(srcLink).length
@@ -198,22 +198,22 @@ describe('+ copy() - prevent copying identical files and dirs', () => {
198198
const destLink = path.join(TEST_DIR, 'dest-symlink')
199199
fs.symlinkSync(src, destLink, 'file')
200200

201-
fs.copy(src, destLink, err => {
201+
fs.copy(src, destLink, { dereference: true }, err => {
202202
assert.strictEqual(err.message, 'Source and destination must not be the same.')
203203
done()
204204
})
205205
})
206206
})
207207

208208
describe('>> when src is a symlink that points to a regular dest', () => {
209-
it('should throw error', done => {
209+
it('should throw error if dereference is true', done => {
210210
dest = path.join(TEST_DIR, 'dest', 'somefile.txt')
211211
fs.outputFileSync(dest, 'some data')
212212

213213
const srcLink = path.join(TEST_DIR, 'src-symlink')
214214
fs.symlinkSync(dest, srcLink, 'file')
215215

216-
fs.copy(srcLink, dest, err => {
216+
fs.copy(srcLink, dest, { dereference: true }, err => {
217217
assert.strictEqual(err.message, 'Source and destination must not be the same.')
218218

219219
const link = fs.readlinkSync(srcLink)
@@ -225,7 +225,7 @@ describe('+ copy() - prevent copying identical files and dirs', () => {
225225
})
226226

227227
describe('>> when src and dest are symlinks that point to the exact same path', () => {
228-
it('should error src and dest are the same', done => {
228+
it('should error src and dest are the same and dereferene is true', done => {
229229
src = path.join(TEST_DIR, 'src', 'srcfile.txt')
230230
fs.outputFileSync(src, 'src data')
231231

@@ -235,7 +235,7 @@ describe('+ copy() - prevent copying identical files and dirs', () => {
235235
const destLink = path.join(TEST_DIR, 'dest_symlink')
236236
fs.symlinkSync(src, destLink, 'file')
237237

238-
fs.copy(srcLink, destLink, err => {
238+
fs.copy(srcLink, destLink, { dereference: true }, err => {
239239
assert.strictEqual(err.message, 'Source and destination must not be the same.')
240240

241241
const srcln = fs.readlinkSync(srcLink)

0 commit comments

Comments
 (0)