1 | var util = require('util'),
|
2 | fs = require('fs');
|
3 |
|
4 | var FileIndex = exports.FileIndex = function(repo_path, callback) {
|
5 | var _repo_path = repo_path;
|
6 | var _index_file = repo_path + "/file-index";
|
7 | var self = this;
|
8 |
|
9 | var _sha_count = 0, _commit_index = {}, _commit_order = {}, _all_files = {};
|
10 |
|
11 |
|
12 | Object.defineProperty(this, "repo_path", { get: function() { return _repo_path; }, enumerable: true});
|
13 | Object.defineProperty(this, "index_file", { get: function() { return _index_file; }, enumerable: true});
|
14 |
|
15 | Object.defineProperty(this, "sha_count", { get: function() { return _sha_count; }, set: function(value) { _sha_count = value; }, enumerable: true});
|
16 | Object.defineProperty(this, "commit_index", { get: function() { return _commit_index; }, set: function(value) { _commit_index = value; }, enumerable: true});
|
17 | Object.defineProperty(this, "commit_order", { get: function() { return _commit_order; }, set: function(value) { _commit_order = value; }, enumerable: true});
|
18 | Object.defineProperty(this, "all_files", { get: function() { return _all_files; }, set: function(value) { _all_files = value; }, enumerable: true});
|
19 |
|
20 | fs.stat(_index_file, function(err, stat) {
|
21 | if(err) return callback(err, stat);
|
22 |
|
23 | if(stat.isFile() && stat.size < FileIndex.max_file_size) {
|
24 | read_index(self, _index_file, function(err, _index) {
|
25 | if(err) return callback(err, _index);
|
26 | callback(null, _index);
|
27 | })
|
28 | } else {
|
29 | callback("index file not found", null);
|
30 | }
|
31 | });
|
32 | }
|
33 |
|
34 |
|
35 | FileIndex.max_file_size = 10000000;
|
36 |
|
37 |
|
38 | var chomp = function chomp(raw_text) {
|
39 | return raw_text.replace(/(\n|\r)+$/, '');
|
40 | }
|
41 |
|
42 | var dirname = function(file_name) {
|
43 | var elements = file_name.split('/');
|
44 | elements.pop();
|
45 | if(elements.length == 0) return ".";
|
46 | return elements.join("/");
|
47 | }
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 | var read_index = function(file_index, _index_file, callback) {
|
54 | var current_sha = null;
|
55 |
|
56 | fs.readFile(_index_file, 'ascii', function(err, data) {
|
57 | if(err) return callback(err, data);
|
58 |
|
59 | var lines = data.split("\n");
|
60 |
|
61 | for(var i = 0; i < lines.length; i++) {
|
62 | var line = lines[i];
|
63 |
|
64 |
|
65 | if(line.match(/^(\w{40})/)) {
|
66 |
|
67 | var shas = line.match(/(\w{40})/g);
|
68 | current_sha = shas.shift();
|
69 |
|
70 | file_index.commit_index[current_sha] = {files:[], parents:shas}
|
71 | file_index.commit_order[current_sha] = file_index.sha_count;
|
72 | file_index.sha_count = file_index.sha_count + 1;
|
73 | } else {
|
74 | var file_name = chomp(line);
|
75 | var tree = '';
|
76 |
|
77 | var dir = dirname(file_name);
|
78 |
|
79 | if(line.length > 0) {
|
80 |
|
81 | var dir_parts = dir.split("/");
|
82 | for(var j = 0; j < dir_parts.length; j++) {
|
83 | var part = dir_parts[j];
|
84 |
|
85 | if(dir_parts[j] != '.') {
|
86 | tree = tree + part + '/'
|
87 | if(file_index.all_files[tree] == null) file_index.all_files[tree] = [];
|
88 | if(file_index.all_files[tree].indexOf(current_sha) == -1)
|
89 | file_index.all_files[tree].unshift(current_sha);
|
90 | }
|
91 | }
|
92 |
|
93 |
|
94 | if(!file_index.all_files[file_name]) file_index.all_files[file_name] = [];
|
95 | file_index.all_files[file_name].unshift(current_sha);
|
96 | file_index.commit_index[current_sha].files.push(file_name);
|
97 | }
|
98 | }
|
99 | }
|
100 |
|
101 | callback(null, file_index);
|
102 | });
|
103 | }
|
104 |
|
105 |
|
106 | FileIndex.prototype.commits_from = function(commit_sha, callback) {
|
107 | if(Array.isArray(commit_sha)) return callback("unsuported reference", null);
|
108 |
|
109 | var already = {};
|
110 | var final = [];
|
111 | var left_to_do = [commit_sha];
|
112 | var self = this;
|
113 |
|
114 | while(left_to_do.length > 0) {
|
115 | commit_sha = left_to_do.shift();
|
116 |
|
117 | if(!already[commit_sha]) {
|
118 |
|
119 | final.push(commit_sha);
|
120 | already[commit_sha] = true;
|
121 |
|
122 |
|
123 | var commit = self.commit_index[commit_sha];
|
124 | if(commit) {
|
125 | commit.parents.forEach(function(sha) {
|
126 | left_to_do.push(sha);
|
127 | });
|
128 | }
|
129 | }
|
130 | }
|
131 |
|
132 | final = this.sort_commits(final);
|
133 |
|
134 | callback(null, final);
|
135 | }
|
136 |
|
137 | FileIndex.prototype.sort_commits = function(sha_array) {
|
138 | var self = this;
|
139 |
|
140 | return sha_array.sort(function(a, b) {
|
141 | return compare(parseInt(self.commit_order[b]), parseInt(self.commit_order[a]));
|
142 | })
|
143 | }
|
144 |
|
145 | var convert = function(d) {
|
146 | return (
|
147 | d.constructor === Date ? d :
|
148 | d.constructor === Array ? new Date(d[0],d[1],d[2]) :
|
149 | d.constructor === Number ? new Date(d) :
|
150 | d.constructor === String ? new Date(d) :
|
151 | typeof d === "object" ? new Date(d.year,d.month,d.date) :
|
152 | NaN
|
153 | );
|
154 | }
|
155 |
|
156 | var compare = function(a,b) {
|
157 | return (
|
158 | isFinite(a=convert(a).valueOf()) &&
|
159 | isFinite(b=convert(b).valueOf()) ?
|
160 | (a>b)-(a<b) :
|
161 | NaN
|
162 | );
|
163 | }
|
164 |
|
165 |
|
166 | FileIndex.prototype.files = function(commit_sha, callback) {
|
167 | if(!this.commit_index[commit_sha]) return callback("no files found for sha: " + commit_sha, null);
|
168 | callback(null, this.commit_index[commit_sha].files);
|
169 | }
|
170 |
|
171 |
|
172 | FileIndex.prototype.count_all = function(callback) {
|
173 | callback(null, this.sha_count);
|
174 | }
|
175 |
|
176 |
|
177 | FileIndex.prototype.count = function(commit_sha, callback) {
|
178 | this.commits_from(commit_sha, function(err, commits) {
|
179 | if(err) return callback(err, commits);
|
180 | callback(null, commits.length);
|
181 | })
|
182 | }
|
183 |
|
184 |
|
185 | FileIndex.prototype.commits_for = function(file, callback) {
|
186 | if(!this.all_files[file]) return callback("could not locate any commits for file: " + file, null);
|
187 | callback(null, this.all_files[file])
|
188 | }
|
189 |
|
190 |
|
191 |
|
192 |
|
193 | FileIndex.prototype.last_commits = function(commit_sha, files_matcher, callback) {
|
194 | var self = this;
|
195 |
|
196 | this.commits_from(commit_sha, function(err, acceptable) {
|
197 | if(err) return callback(err, acceptable);
|
198 | var matches = {};
|
199 |
|
200 | if(files_matcher.constructor == RegExp) {
|
201 |
|
202 | files_matcher = Object.keys(self.all_files).filter(function(file) {
|
203 | return file.match(files_matcher);
|
204 | });
|
205 | }
|
206 |
|
207 | if(Array.isArray(files_matcher)) {
|
208 |
|
209 | for(var files_matcher_index = 0; files_matcher_index < files_matcher.length; files_matcher_index++) {
|
210 | var files = self.all_files[files_matcher[files_matcher_index]];
|
211 |
|
212 | for(var files_index = 0; files_index < files.length; files_index++) {
|
213 |
|
214 | if(acceptable.indexOf(files[files_index]) != -1) {
|
215 | matches[files_matcher[files_matcher_index]] = files[files_index];
|
216 | break;
|
217 | }
|
218 | }
|
219 | }
|
220 | }
|
221 |
|
222 |
|
223 | callback(null, matches);
|
224 | });
|
225 | }
|
226 |
|
227 |
|
228 |
|
229 |
|
230 |
|
231 |
|
232 |
|
233 |
|
234 |
|
235 |
|
236 |
|
237 |
|
238 |
|
239 |
|
240 |
|
241 |
|