git-off

git off handles large files in git repos
git clone https://noulin.net/git/git-off.git
Log | Files | Refs | README

rimraf.js (8341B)


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