Skip to content

Commit 0314876

Browse files
authored
Merge pull request #300 from jprichardson/rimraf
Inline Rimraf
2 parents 071f8ce + a837927 commit 0314876

File tree

4 files changed

+309
-8
lines changed

4 files changed

+309
-8
lines changed

lib/move/index.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
var fs = require('graceful-fs')
88
var ncp = require('../copy/ncp')
99
var path = require('path')
10-
var rimraf = require('rimraf')
10+
var remove = require('../remove').remove
1111
var mkdirp = require('../mkdirs').mkdirs
1212

1313
function mv (source, dest, options, callback) {
@@ -40,7 +40,7 @@ function mv (source, dest, options, callback) {
4040
if (!err) return callback()
4141

4242
if (err.code === 'ENOTEMPTY' || err.code === 'EEXIST') {
43-
rimraf(dest, function (err) {
43+
remove(dest, function (err) {
4444
if (err) return callback(err)
4545
options.clobber = false // just clobbered it, no need to do it again
4646
mv(source, dest, options, callback)
@@ -51,7 +51,7 @@ function mv (source, dest, options, callback) {
5151
// weird Windows shit
5252
if (err.code === 'EPERM') {
5353
setTimeout(function () {
54-
rimraf(dest, function (err) {
54+
remove(dest, function (err) {
5555
if (err) return callback(err)
5656
options.clobber = false
5757
mv(source, dest, options, callback)
@@ -142,12 +142,12 @@ function moveDirAcrossDevice (source, dest, clobber, limit, callback) {
142142
function startNcp () {
143143
ncp(source, dest, options, function (errList) {
144144
if (errList) return callback(errList[0])
145-
rimraf(source, callback)
145+
remove(source, callback)
146146
})
147147
}
148148

149149
if (clobber) {
150-
rimraf(dest, function (err) {
150+
remove(dest, function (err) {
151151
if (err) return callback(err)
152152
startNcp()
153153
})

lib/remove/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
var rimraf = require('rimraf')
1+
var rimraf = require('./rimraf')
22

33
function removeSync (dir) {
44
return rimraf.sync(dir, {disableGlob: true})

lib/remove/rimraf.js

Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
module.exports = rimraf
2+
rimraf.sync = rimrafSync
3+
4+
var assert = require('assert')
5+
var path = require('path')
6+
var fs = require('graceful-fs')
7+
8+
var isWindows = (process.platform === 'win32')
9+
10+
function defaults (options) {
11+
var methods = [
12+
'unlink',
13+
'chmod',
14+
'stat',
15+
'lstat',
16+
'rmdir',
17+
'readdir'
18+
]
19+
methods.forEach(function (m) {
20+
options[m] = options[m] || fs[m]
21+
m = m + 'Sync'
22+
options[m] = options[m] || fs[m]
23+
})
24+
25+
options.maxBusyTries = options.maxBusyTries || 3
26+
}
27+
28+
function rimraf (p, options, cb) {
29+
if (typeof options === 'function') {
30+
cb = options
31+
options = {}
32+
}
33+
34+
assert(p, 'rimraf: missing path')
35+
assert.equal(typeof p, 'string', 'rimraf: path should be a string')
36+
assert.equal(typeof cb, 'function', 'rimraf: callback function required')
37+
assert(options, 'rimraf: invalid options argument provided')
38+
assert.equal(typeof options, 'object', 'rimraf: options should be object')
39+
40+
defaults(options)
41+
42+
var busyTries = 0
43+
44+
rimraf_(p, options, function CB (er) {
45+
if (er) {
46+
if (isWindows && (er.code === 'EBUSY' || er.code === 'ENOTEMPTY' || er.code === 'EPERM') &&
47+
busyTries < options.maxBusyTries) {
48+
busyTries++
49+
var time = busyTries * 100
50+
// try again, with the same exact callback as this one.
51+
return setTimeout(function () {
52+
rimraf_(p, options, CB)
53+
}, time)
54+
}
55+
56+
// already gone
57+
if (er.code === 'ENOENT') er = null
58+
}
59+
60+
cb(er)
61+
})
62+
}
63+
64+
// Two possible strategies.
65+
// 1. Assume it's a file. unlink it, then do the dir stuff on EPERM or EISDIR
66+
// 2. Assume it's a directory. readdir, then do the file stuff on ENOTDIR
67+
//
68+
// Both result in an extra syscall when you guess wrong. However, there
69+
// are likely far more normal files in the world than directories. This
70+
// is based on the assumption that a the average number of files per
71+
// directory is >= 1.
72+
//
73+
// If anyone ever complains about this, then I guess the strategy could
74+
// be made configurable somehow. But until then, YAGNI.
75+
function rimraf_ (p, options, cb) {
76+
assert(p)
77+
assert(options)
78+
assert(typeof cb === 'function')
79+
80+
// sunos lets the root user unlink directories, which is... weird.
81+
// so we have to lstat here and make sure it's not a dir.
82+
options.lstat(p, function (er, st) {
83+
if (er && er.code === 'ENOENT') {
84+
return cb(null)
85+
}
86+
87+
// Windows can EPERM on stat. Life is suffering.
88+
if (er && er.code === 'EPERM' && isWindows) {
89+
fixWinEPERM(p, options, er, cb)
90+
}
91+
92+
if (st && st.isDirectory()) {
93+
return rmdir(p, options, er, cb)
94+
}
95+
96+
options.unlink(p, function (er) {
97+
if (er) {
98+
if (er.code === 'ENOENT') {
99+
return cb(null)
100+
}
101+
if (er.code === 'EPERM') {
102+
return (isWindows)
103+
? fixWinEPERM(p, options, er, cb)
104+
: rmdir(p, options, er, cb)
105+
}
106+
if (er.code === 'EISDIR') {
107+
return rmdir(p, options, er, cb)
108+
}
109+
}
110+
return cb(er)
111+
})
112+
})
113+
}
114+
115+
function fixWinEPERM (p, options, er, cb) {
116+
assert(p)
117+
assert(options)
118+
assert(typeof cb === 'function')
119+
if (er) {
120+
assert(er instanceof Error)
121+
}
122+
123+
options.chmod(p, 666, function (er2) {
124+
if (er2) {
125+
cb(er2.code === 'ENOENT' ? null : er)
126+
} else {
127+
options.stat(p, function (er3, stats) {
128+
if (er3) {
129+
cb(er3.code === 'ENOENT' ? null : er)
130+
} else if (stats.isDirectory()) {
131+
rmdir(p, options, er, cb)
132+
} else {
133+
options.unlink(p, cb)
134+
}
135+
})
136+
}
137+
})
138+
}
139+
140+
function fixWinEPERMSync (p, options, er) {
141+
assert(p)
142+
assert(options)
143+
if (er) {
144+
assert(er instanceof Error)
145+
}
146+
147+
try {
148+
options.chmodSync(p, 666)
149+
} catch (er2) {
150+
if (er2.code === 'ENOENT') {
151+
return
152+
} else {
153+
throw er
154+
}
155+
}
156+
157+
try {
158+
var stats = options.statSync(p)
159+
} catch (er3) {
160+
if (er3.code === 'ENOENT') {
161+
return
162+
} else {
163+
throw er
164+
}
165+
}
166+
167+
if (stats.isDirectory()) {
168+
rmdirSync(p, options, er)
169+
} else {
170+
options.unlinkSync(p)
171+
}
172+
}
173+
174+
function rmdir (p, options, originalEr, cb) {
175+
assert(p)
176+
assert(options)
177+
if (originalEr) {
178+
assert(originalEr instanceof Error)
179+
}
180+
assert(typeof cb === 'function')
181+
182+
// try to rmdir first, and only readdir on ENOTEMPTY or EEXIST (SunOS)
183+
// if we guessed wrong, and it's not a directory, then
184+
// raise the original error.
185+
options.rmdir(p, function (er) {
186+
if (er && (er.code === 'ENOTEMPTY' || er.code === 'EEXIST' || er.code === 'EPERM')) {
187+
rmkids(p, options, cb)
188+
} else if (er && er.code === 'ENOTDIR') {
189+
cb(originalEr)
190+
} else {
191+
cb(er)
192+
}
193+
})
194+
}
195+
196+
function rmkids (p, options, cb) {
197+
assert(p)
198+
assert(options)
199+
assert(typeof cb === 'function')
200+
201+
options.readdir(p, function (er, files) {
202+
if (er) {
203+
return cb(er)
204+
}
205+
var n = files.length
206+
if (n === 0) {
207+
return options.rmdir(p, cb)
208+
}
209+
var errState
210+
files.forEach(function (f) {
211+
rimraf(path.join(p, f), options, function (er) {
212+
if (errState) {
213+
return
214+
}
215+
if (er) {
216+
return cb(errState = er)
217+
}
218+
if (--n === 0) {
219+
options.rmdir(p, cb)
220+
}
221+
})
222+
})
223+
})
224+
}
225+
226+
// this looks simpler, and is strictly *faster*, but will
227+
// tie up the JavaScript thread and fail on excessively
228+
// deep directory trees.
229+
function rimrafSync (p, options) {
230+
options = options || {}
231+
defaults(options)
232+
233+
assert(p, 'rimraf: missing path')
234+
assert.equal(typeof p, 'string', 'rimraf: path should be a string')
235+
assert(options, 'rimraf: missing options')
236+
assert.equal(typeof options, 'object', 'rimraf: options should be object')
237+
238+
try {
239+
var st = options.lstatSync(p)
240+
} catch (er) {
241+
if (er.code === 'ENOENT') {
242+
return
243+
}
244+
245+
// Windows can EPERM on stat. Life is suffering.
246+
if (er.code === 'EPERM' && isWindows) {
247+
fixWinEPERMSync(p, options, er)
248+
}
249+
}
250+
251+
try {
252+
// sunos lets the root user unlink directories, which is... weird.
253+
if (st && st.isDirectory()) {
254+
rmdirSync(p, options, null)
255+
} else {
256+
options.unlinkSync(p)
257+
}
258+
} catch (er) {
259+
if (er.code === 'ENOENT') {
260+
return
261+
}
262+
if (er.code === 'EPERM') {
263+
return isWindows ? fixWinEPERMSync(p, options, er) : rmdirSync(p, options, er)
264+
}
265+
if (er.code !== 'EISDIR') {
266+
throw er
267+
}
268+
rmdirSync(p, options, er)
269+
}
270+
}
271+
272+
function rmdirSync (p, options, originalEr) {
273+
assert(p)
274+
assert(options)
275+
if (originalEr) {
276+
assert(originalEr instanceof Error)
277+
}
278+
279+
try {
280+
options.rmdirSync(p)
281+
} catch (er) {
282+
if (er.code === 'ENOENT') {
283+
return
284+
}
285+
if (er.code === 'ENOTDIR') {
286+
throw originalEr
287+
}
288+
if (er.code === 'ENOTEMPTY' || er.code === 'EEXIST' || er.code === 'EPERM') {
289+
rmkidsSync(p, options)
290+
}
291+
}
292+
}
293+
294+
function rmkidsSync (p, options) {
295+
assert(p)
296+
assert(options)
297+
options.readdirSync(p).forEach(function (f) {
298+
rimrafSync(path.join(p, f), options)
299+
})
300+
options.rmdirSync(p, options)
301+
}

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,7 @@
3636
"graceful-fs": "^4.1.2",
3737
"jsonfile": "^2.1.0",
3838
"klaw": "^1.0.0",
39-
"path-is-absolute": "^1.0.0",
40-
"rimraf": "^2.2.8"
39+
"path-is-absolute": "^1.0.0"
4140
},
4241
"devDependencies": {
4342
"coveralls": "^2.11.2",
@@ -46,6 +45,7 @@
4645
"mocha": "^2.1.0",
4746
"proxyquire": "^1.7.10",
4847
"read-dir-files": "^0.1.1",
48+
"rimraf": "^2.2.8",
4949
"secure-random": "^1.1.1",
5050
"semver": "^4.3.6",
5151
"standard": "^7.0.0-beta.0"

0 commit comments

Comments
 (0)