1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
|
var util = require('util')
var pathUtils = require('path')
var fs = require('fs')
var compareSyncInternal = require('./compareSync')
var compareAsyncInternal = require('./compareAsync')
var defaultResultBuilderCallback = require('./resultBuilder/defaultResultBuilderCallback')
var defaultFileCompare = require('./fileCompareHandler/defaultFileCompare')
var lineBasedFileCompare = require('./fileCompareHandler/lineBasedFileCompare')
var defaultNameCompare = require('./nameCompare/defaultNameCompare')
var entryBuilder = require('./entry/entryBuilder')
var statsLifecycle = require('./statistics/statisticsLifecycle')
var loopDetector = require('./symlink/loopDetector')
var ROOT_PATH = pathUtils.sep
var compareSync = function (path1, path2, options) {
'use strict'
// realpathSync() is necessary for loop detection to work properly
var absolutePath1 = pathUtils.normalize(pathUtils.resolve(fs.realpathSync(path1)))
var absolutePath2 = pathUtils.normalize(pathUtils.resolve(fs.realpathSync(path2)))
var diffSet
options = prepareOptions(options)
if (!options.noDiffSet) {
diffSet = []
}
var statistics = statsLifecycle.initStats(options)
compareSyncInternal(
entryBuilder.buildEntry(absolutePath1, path1, pathUtils.basename(absolutePath1)),
entryBuilder.buildEntry(absolutePath2, path2, pathUtils.basename(absolutePath2)),
0, ROOT_PATH, options, statistics, diffSet, loopDetector.initSymlinkCache())
statsLifecycle.completeStatistics(statistics, options)
statistics.diffSet = diffSet
return statistics
}
var wrapper = {
realPath: function(path, options) {
return new Promise(function (resolve, reject) {
fs.realpath(path, options, function(err, resolvedPath) {
if(err){
reject(err)
} else {
resolve(resolvedPath)
}
})
})
}
}
var compareAsync = function (path1, path2, options) {
'use strict'
var absolutePath1, absolutePath2
return Promise.resolve()
.then(function () {
return Promise.all([wrapper.realPath(path1), wrapper.realPath(path2)])
})
.then(function (realPaths) {
var realPath1 = realPaths[0]
var realPath2 = realPaths[1]
// realpath() is necessary for loop detection to work properly
absolutePath1 = pathUtils.normalize(pathUtils.resolve(realPath1))
absolutePath2 = pathUtils.normalize(pathUtils.resolve(realPath2))
})
.then(function () {
options = prepareOptions(options)
var asyncDiffSet
if (!options.noDiffSet) {
asyncDiffSet = []
}
var statistics = statsLifecycle.initStats(options)
return compareAsyncInternal(
entryBuilder.buildEntry(absolutePath1, path1, pathUtils.basename(path1)),
entryBuilder.buildEntry(absolutePath2, path2, pathUtils.basename(path2)),
0, ROOT_PATH, options, statistics, asyncDiffSet, loopDetector.initSymlinkCache()).then(
function () {
statsLifecycle.completeStatistics(statistics, options)
if (!options.noDiffSet) {
var diffSet = []
rebuildAsyncDiffSet(statistics, asyncDiffSet, diffSet)
statistics.diffSet = diffSet
}
return statistics
})
})
}
var prepareOptions = function (options) {
options = options || {}
var clone = JSON.parse(JSON.stringify(options))
clone.resultBuilder = options.resultBuilder
clone.compareFileSync = options.compareFileSync
clone.compareFileAsync = options.compareFileAsync
clone.compareNameHandler = options.compareNameHandler
if (!clone.resultBuilder) {
clone.resultBuilder = defaultResultBuilderCallback
}
if (!clone.compareFileSync) {
clone.compareFileSync = defaultFileCompare.compareSync
}
if (!clone.compareFileAsync) {
clone.compareFileAsync = defaultFileCompare.compareAsync
}
if(!clone.compareNameHandler) {
clone.compareNameHandler = defaultNameCompare
}
clone.dateTolerance = clone.dateTolerance || 1000
clone.dateTolerance = Number(clone.dateTolerance)
if (isNaN(clone.dateTolerance)) {
throw new Error('Date tolerance is not a number')
}
return clone
}
// Async diffsets are kept into recursive structures.
// This method transforms them into one dimensional arrays.
var rebuildAsyncDiffSet = function (statistics, asyncDiffSet, diffSet) {
asyncDiffSet.forEach(function (rawDiff) {
if (!Array.isArray(rawDiff)) {
diffSet.push(rawDiff)
} else {
rebuildAsyncDiffSet(statistics, rawDiff, diffSet)
}
})
}
/**
* Options:
* compareSize: true/false - Compares files by size. Defaults to 'false'.
* compareDate: true/false - Compares files by date of modification (stat.mtime). Defaults to 'false'.
* dateTolerance: milliseconds - Two files are considered to have the same date if the difference between their modification dates fits within date tolerance. Defaults to 1000 ms.
* compareContent: true/false - Compares files by content. Defaults to 'false'.
* compareSymlink: true/false - Compares entries by symlink. Defaults to 'false'.
* skipSubdirs: true/false - Skips sub directories. Defaults to 'false'.
* skipSymlinks: true/false - Skips symbolic links. Defaults to 'false'.
* ignoreCase: true/false - Ignores case when comparing names. Defaults to 'false'.
* noDiffSet: true/false - Toggles presence of diffSet in output. If true, only statistics are provided. Use this when comparing large number of files to avoid out of memory situations. Defaults to 'false'.
* includeFilter: File name filter. Comma separated [minimatch](https://www.npmjs.com/package/minimatch) patterns.
* excludeFilter: File/directory name exclude filter. Comma separated [minimatch](https://www.npmjs.com/package/minimatch) patterns.
* resultBuilder: Callback for constructing result.
* function (entry1, entry2, state, level, relativePath, options, statistics, diffSet). Called for each compared entry pair. Updates 'statistics' and 'diffSet'.
* compareFileSync, compareFileAsync: Callbacks for file comparison.
* compareNameHandler: Callback for name comparison
*
* Result:
* same: true if directories are identical
* distinct: number of distinct entries
* equal: number of equal entries
* left: number of entries only in path1
* right: number of entries only in path2
* differences: total number of differences (distinct+left+right)
* total: total number of entries (differences+equal)
* distinctFiles: number of distinct files
* equalFiles: number of equal files
* leftFiles: number of files only in path1
* rightFiles: number of files only in path2
* differencesFiles: total number of different files (distinctFiles+leftFiles+rightFiles)
* totalFiles: total number of files (differencesFiles+equalFiles)
* distinctDirs: number of distinct directories
* equalDirs: number of equal directories
* leftDirs: number of directories only in path1
* rightDirs: number of directories only in path2
* differencesDirs: total number of different directories (distinctDirs+leftDirs+rightDirs)
* totalDirs: total number of directories (differencesDirs+equalDirs)
* brokenLinks:
* leftBrokenLinks: number of broken links only in path1
* rightBrokenLinks: number of broken links only in path2
* distinctBrokenLinks: number of broken links with same name appearing in both path1 and path2
* totalBrokenLinks: total number of broken links (leftBrokenLinks+rightBrokenLinks+distinctBrokenLinks)
* symlinks: Statistics available if 'compareSymlink' options is used
* distinctSymlinks: number of distinct links
* equalSymlinks: number of equal links
* leftSymlinks: number of links only in path1
* rightSymlinks: number of links only in path2
* differencesSymlinks: total number of different links (distinctSymlinks+leftSymlinks+rightSymlinks)
* totalSymlinks: total number of links (differencesSymlinks+equalSymlinks)
* diffSet - List of changes (present if Options.noDiffSet is false)
* path1: absolute path not including file/directory name,
* path2: absolute path not including file/directory name,
* relativePath: common path relative to root,
* name1: file/directory name
* name2: file/directory name
* state: one of equal, left, right, distinct,
* type1: one of missing, file, directory, broken-link
* type2: one of missing, file, directory, broken-link
* size1: file size
* size2: file size
* date1: modification date (stat.mtime)
* date2: modification date (stat.mtime)
* level: depth
* reason: Provides reason when two identically named entries are distinct
* Not available if entries are equal
* One of "different-size", "different-date", "different-content", "broken-link", "different-symlink"
*/
module.exports = {
compareSync: compareSync,
compare: compareAsync,
fileCompareHandlers: {
defaultFileCompare: defaultFileCompare,
lineBasedFileCompare: lineBasedFileCompare
}
}
|