UNPKG

8.16 kBJavaScriptView Raw
1var util = require('util'),
2 fs = require('fs');
3
4var 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 // Set up internal index info
9 var _sha_count = 0, _commit_index = {}, _commit_order = {}, _all_files = {};
10
11 // Set up properites for instance
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 // Other values that allow setting
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// Max size for file index
35FileIndex.max_file_size = 10000000;
36
37// Chomp text removing end carriage returns
38var chomp = function chomp(raw_text) {
39 return raw_text.replace(/(\n|\r)+$/, '');
40}
41
42var 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// TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO
50// TODO Needs to be async reading files in pieces and parsing them
51// TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO
52// Read and parse the file index for git
53var 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 // Split the text into lines
59 var lines = data.split("\n");
60 // Iterate over all the lines
61 for(var i = 0; i < lines.length; i++) {
62 var line = lines[i];
63
64 // Ensure it's a line with a starting sha
65 if(line.match(/^(\w{40})/)) {
66 // Unpack all the sha values (first one being the current_sha and the rest the parents)
67 var shas = line.match(/(\w{40})/g);
68 current_sha = shas.shift();
69 // The rest of the sha's are the parents
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 // Retrieve the directory name for the file passed in
77 var dir = dirname(file_name);
78 // Ensure it's not an empty line
79 if(line.length > 0) {
80 // Split up the directory
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 // Finish up
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 // Return the parsed index
101 callback(null, file_index);
102 });
103}
104
105// Builds a list of all commits reachable from a single commit
106FileIndex.prototype.commits_from = function(commit_sha, callback) {
107 if(Array.isArray(commit_sha)) return callback("unsuported reference", null);
108 // Define some holding structures
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 // Add commit to list of final commits
119 final.push(commit_sha);
120 already[commit_sha] = true;
121
122 // Get parents of the commit and add them to the list
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 // Sort the commits
132 final = this.sort_commits(final);
133 // Callback
134 callback(null, final);
135}
136
137FileIndex.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
145var 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
156var 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// Returns files changed at commit sha
166FileIndex.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// Returns count of all commits
172FileIndex.prototype.count_all = function(callback) {
173 callback(null, this.sha_count);
174}
175
176// returns count of all commits reachable from SHA
177FileIndex.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// returns all commits for a provided file
185FileIndex.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// returns the shas of the last commits for all
191// the files in [] from commit_sha
192// files_matcher can be a regexp or an array
193FileIndex.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 // Filter all the files by the matching regular expression
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 // Locate the last commit for each file in the files_matcher array
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 // If the file is included in the list of commits_from then add it to the matches
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 // Return matches
223 callback(null, matches);
224 });
225}
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241