var fs = require('fs'); var pathUtils = require('path'); var common = require('./common'); /** * Returns the sorted list of entries in a directory. */ var getEntries = function (path, options) { if (!path) { return []; } else { var statPath = fs.statSync(path); if (statPath.isDirectory()) { var entries = fs.readdirSync(path); var res = []; entries.forEach(function (entryName) { var entryPath = path + '/' + entryName; var lstatEntry = fs.lstatSync(entryPath); var isSymlink = lstatEntry.isSymbolicLink(); var statEntry; if(options.skipSymlinks && isSymlink){ statEntry = undefined; } else{ statEntry = fs.statSync(entryPath); } var entry = { name : entryName, path : entryPath, stat : statEntry, lstat : lstatEntry, symlink : isSymlink, toString : function () { return this.name; } }; if (common.filterEntry(entry, options)){ res.push(entry); } }); return options.ignoreCase?res.sort(common.compareEntryIgnoreCase):res.sort(common.compareEntryCaseSensitive); } else { var name = pathUtils.basename(path); return [ { name : name, path : path, stat : statPath, toString : function () { return this.name; } } ]; } } } /** * Compares two directories synchronously. */ var compare = function (rootEntry1, rootEntry2, level, relativePath, options, statistics, diffSet, symlinkCache) { symlinkCache = symlinkCache || { dir1 : {}, dir2 : {} }; var loopDetected1 = common.detectLoop(rootEntry1, symlinkCache.dir1); var loopDetected2 = common.detectLoop(rootEntry2, symlinkCache.dir2); var symlinkCachePath1, symlinkCachePath2; if(rootEntry1 && !loopDetected1){ symlinkCachePath1 = pathUtils.normalize(pathUtils.resolve(rootEntry1.symlink?fs.realpathSync(rootEntry1.path):rootEntry1.path)).toLowerCase(); symlinkCache.dir1[symlinkCachePath1] = true; } if(rootEntry2 && !loopDetected2){ symlinkCachePath2 = pathUtils.normalize(pathUtils.resolve(rootEntry2.symlink?fs.realpathSync(rootEntry2.path):rootEntry2.path)).toLowerCase(); symlinkCache.dir2[symlinkCachePath2] = true; } var path1 = rootEntry1?rootEntry1.path:undefined; var path2 = rootEntry2?rootEntry2.path:undefined; var entries1 = loopDetected1?[]:getEntries(path1, options); var entries2 = loopDetected2?[]:getEntries(path2, options); var i1 = 0, i2 = 0; while (i1 < entries1.length || i2 < entries2.length) { var entry1 = entries1[i1]; var entry2 = entries2[i2]; var n1 = entry1 ? entry1.name : undefined; var n2 = entry2 ? entry2.name : undefined; var p1 = entry1 ? entry1.path : undefined; var p2 = entry2 ? entry2.path : undefined; var fileStat1 = entry1 ? entry1.stat : undefined; var fileStat2 = entry2 ? entry2.stat : undefined; var type1, type2; // compare entry name (-1, 0, 1) var cmp; if (i1 < entries1.length && i2 < entries2.length) { cmp = options.ignoreCase?common.compareEntryIgnoreCase(entry1, entry2):common.compareEntryCaseSensitive(entry1, entry2); type1 = common.getType(fileStat1); type2 = common.getType(fileStat2); } else if (i1 < entries1.length) { type1 = common.getType(fileStat1); type2 = common.getType(undefined); cmp = -1; } else { type1 = common.getType(undefined); type2 = common.getType(fileStat2); cmp = 1; } // process entry if (cmp === 0) { // Both left/right exist and have the same name if (type1 === type2) { var same; if(type1==='file'){ if (options.compareSize && fileStat1.size !== fileStat2.size) { same = false; } else if(options.compareDate && !common.sameDate(fileStat1.mtime, fileStat2.mtime, options.dateTolerance)){ same = false; } else if(options.compareContent){ same = options.compareFileSync(p1, fileStat1, p2, fileStat2, options); } else{ same = true; } } else{ same = true; } if(!options.noDiffSet){ options.resultBuilder(entry1, entry2, same ? 'equal' : 'distinct', level, relativePath, options, statistics, diffSet); } same ? statistics.equal++ : statistics.distinct++; if(type1==='file'){ same ? statistics.equalFiles++ : statistics.distinctFiles++; } else{ same ? statistics.equalDirs++ : statistics.distinctDirs++; } } else { // File and directory with same name if(!options.noDiffSet){ options.resultBuilder(entry1, entry2, 'distinct', level, relativePath, options, statistics, diffSet); } statistics.distinct+=2; statistics.distinctFiles++; statistics.distinctDirs++; } i1++; i2++; if(!options.skipSubdirs){ if (type1 === 'directory' && type2 === 'directory') { compare(entry1, entry2, level + 1, relativePath + '/' + entry1.name, options, statistics, diffSet, common.cloneSymlinkCache(symlinkCache)); } else if (type1 === 'directory') { compare(entry1, undefined, level + 1, relativePath + '/' + entry1.name, options, statistics, diffSet, common.cloneSymlinkCache(symlinkCache)); } else if (type2 === 'directory') { compare(undefined, entry2, level + 1, relativePath + '/' + entry2.name, options, statistics, diffSet, common.cloneSymlinkCache(symlinkCache)); } } } else if (cmp < 0) { // Right missing if(!options.noDiffSet){ options.resultBuilder(entry1, undefined, 'left', level, relativePath, options, statistics, diffSet); } statistics.left++; if(type1==='file'){ statistics.leftFiles++; } else{ statistics.leftDirs++; } i1++; if (type1 === 'directory' && !options.skipSubdirs) { compare(entry1, undefined, level + 1, relativePath + '/' + entry1.name, options, statistics, diffSet, common.cloneSymlinkCache(symlinkCache)); } } else { // Left missing if(!options.noDiffSet){ options.resultBuilder(undefined, entry2, 'right', level, relativePath, options, statistics, diffSet); } statistics.right++; if(type2==='file'){ statistics.rightFiles++; } else{ statistics.rightDirs++; } i2++; if (type2 === 'directory' && !options.skipSubdirs) { compare(undefined, entry2, level + 1, relativePath + '/' + entry2.name, options, statistics, diffSet, common.cloneSymlinkCache(symlinkCache)); } } } }; module.exports = compare;