UNPKG

13.1 kBJavaScriptView Raw
1var util = require('util'),
2 fs = require('fs'),
3 Buffer = require('buffer').Buffer,
4 BinaryParser = require('./binary_parser').BinaryParser,
5 FileWindow = require('./file_window').FileWindow,
6 RawObject = require('./raw_object').RawObject;
7 Zlib = require('../zlib/zlib').Zlib;
8
9var PACK_IDX_SIGNATURE = '\xfftOc';
10var FAN_OUT_COUNT = 256;
11var IDX_OFFSET_SIZE = 4;
12var OFFSET_SIZE = 4;
13var OFFSET_START = FAN_OUT_COUNT * IDX_OFFSET_SIZE;
14var SHA1_SIZE = 20;
15var CRC_SIZE = 4;
16var SHA1_START = OFFSET_START + OFFSET_SIZE;
17var ENTRY_SIZE = OFFSET_SIZE + SHA1_SIZE;
18var ENTRY_SIZE_V2 = SHA1_SIZE + CRC_SIZE + OFFSET_SIZE;
19
20// Default types
21var OBJ_NONE = 0;
22var OBJ_COMMIT = 1;
23var OBJ_TREE = 2;
24var OBJ_BLOB = 3;
25var OBJ_TAG = 4;
26var OBJ_OFS_DELTA = 6;
27var OBJ_REF_DELTA = 7;
28var OBJ_TYPES = [null, "commit", "tree", "blob", "tag"];
29
30var PackStorage = exports.PackStorage = function(file) {
31 var _name = file, _cache = {}, _version = null, _offsets = null, _size = 0;
32 // Replace idx reference with pack
33 if(file.match(/\.idx$/)) {
34 file = file.substr(0, file.length - 3) + "pack";
35 }
36
37 Object.defineProperty(this, "name", { get: function() { return _name; }, set: function(value) { _name = value; }, enumerable: true});
38 Object.defineProperty(this, "cache", { get: function() { return _cache; }, set: function(value) { _cache = value; }, enumerable: true});
39 Object.defineProperty(this, "version", { get: function() { return _version; }, set: function(value) { _version = value; }, enumerable: true});
40 Object.defineProperty(this, "offsets", { get: function() { return _offsets; }, set: function(value) { _offsets = value; }, enumerable: true});
41 Object.defineProperty(this, "size", { get: function() { return _size; }, set: function(value) { _size = value; }, enumerable: true});
42 // Initialize pack
43 init_pack(this);
44}
45
46// Search for a sha1 in the pack
47PackStorage.prototype.find = function(sha1) {
48 // If we have the object in the cache return it
49 if(this.cache[sha1]) return this.cache[sha1];
50 // We need to search for the object in the pack file
51 var offset = find_object(this, sha1);
52 // If no object found return null
53 if(!offset) return null;
54 // Parse the object at the located offset
55 var obj = this.parse_object(this, offset);
56 this.cache[sha1] = obj;
57 return obj;
58}
59
60// Close the pack (nothing should be open, might be able to remove this TODO)
61PackStorage.prototype.close = function() {
62}
63
64PackStorage.prototype.parse_object = function(pack, offset) {
65 // Open the pack file
66 var packfile = fs.openSync(pack.name, "r");
67 var result = this.unpack_object(pack, packfile, offset);
68 var data = result[0];
69 var type = result[1];
70 // Close the packfile
71 fs.closeSync(packfile)
72 return new RawObject(OBJ_TYPES[type], data);
73}
74
75PackStorage.prototype.unpack_object = function(pack, packfile, offset, options) {
76 // Ensure valid options variable
77 options = options ? options : {};
78 var obj_offset = offset;
79
80 // TODO TODO TODO TODO TODO TODO
81 // TODO TODO TODO TODO TODO TODO
82 // TODO TODO TODO TODO TODO TODO
83
84 var buf = new Buffer(1);
85 fs.readSync(packfile, buf, 0, 1, offset);
86 // Fetch the first byte
87 var c = buf[0];
88 var size = c & 0xf
89 var type = (c >> 4) & 7;
90 var shift = 4;
91 var offset = offset + 1;
92 // unpack until we have decoded size
93 while((c & 0x80) != 0) {
94 fs.readSync(packfile, buf, 0, 1, offset);
95 c = buf[0];
96 // Adjust size for the byte
97 size = size | ((c & 0x7f) << shift);
98 shift = shift + 7;
99 offset = offset + 1;
100 }
101
102 // If it's not a commit or tree and caching is enabled then return false
103 if(!(type == OBJ_COMMIT || type == OBJ_TREE) && options['caching']) return [false, false];
104 // Check the type of object and either unpack the delta or the compressed data (gziped)
105 if(type == OBJ_OFS_DELTA || type == OBJ_REF_DELTA) {
106 return this.unpack_deltified(packfile, type, offset, obj_offset, size, options);
107 } else if(type == OBJ_COMMIT || type == OBJ_TREE || type == OBJ_BLOB || type == OBJ_TAG) {
108 var data = unpack_compressed(pack, offset, size);
109 return [data, type];
110 } else {
111 throw new "invalid type " + type;
112 }
113}
114
115PackStorage.prototype.unpack_deltified = function(packfile, type, offset, obj_offset, size, options) {
116 var data = new Buffer(SHA1_SIZE);
117 // Read the SHA
118 fs.readSync(packfile, data, 0, SHA1_SIZE, offset);
119
120 if(type == OBJ_OFS_DELTA) {
121 var i = 0;
122 var c = data[i];
123 var base_offset = c & 0x7f;
124
125 while((c & 0x80) != 0) {
126 c = data[++i];
127 base_offset = base_offset + 1;
128 base_offset = base_offset << 7;
129 base_offset = base_offset | (c & 0x7f);
130 }
131
132 base_offset = obj_offset - base_offset;
133 offset = offset + i + 1;
134 } else {
135 base_offset = find_object(this, data.toString())
136 offset = offset + SHA1_SIZE;
137 }
138
139 // Fetch the object at this offset and unpack the result
140 var object_array = this.unpack_object(this, packfile, base_offset);
141 var base = object_array[0];
142 var type = object_array[1];
143 // If it's not a Commit or Tree return an empty delta
144 if(!(type == OBJ_COMMIT || type == OBJ_TREE) && options['caching']) return [false, false];
145 // Unpack the the data
146 var delta = unpack_compressed(this, offset, size);
147 var delta2 = patch_delta(base, delta);
148 return [delta2, type];
149}
150
151var to_hex_string = function(string) {
152 var hexString = '';
153 for(var index = 0; index < string.length; index++) {
154 var value = BinaryParser.toByte(string.substr(index, 1));
155 var number = value <= 15 ? "0" + value.toString(16) : value.toString(16);
156 hexString = hexString + number;
157 }
158 return hexString;
159};
160
161var patch_delta = function(base, delta) {
162 var delta_header_parts = patch_delta_header_size(delta, 0);
163 var src_size = delta_header_parts[0];
164 var pos = delta_header_parts[1];
165
166 if(src_size != base.length) throw "invalid delta data";
167
168 delta_header_parts = patch_delta_header_size(delta, pos);
169 var dest_size = delta_header_parts[0];
170 pos = delta_header_parts[1];
171 var dest = '';
172
173 while(pos < delta.length) {
174 var c = delta.charCodeAt(pos);
175 pos = pos + 1;
176
177 // Keep reading until end of data pack
178 if((c & 0x80) != 0) {
179 pos = pos - 1;
180 var cp_off = 0;
181 var cp_size = 0;
182
183 if((c & 0x01) != 0) cp_off = delta.charCodeAt(pos += 1);
184 if((c & 0x02) != 0) cp_off = cp_off | (delta.charCodeAt(pos += 1) << 8);
185 if((c & 0x04) != 0) cp_off = cp_off | (delta.charCodeAt(pos += 1) << 16);
186 if((c & 0x08) != 0) cp_off = cp_off | (delta.charCodeAt(pos += 1) << 24);
187
188 if((c & 0x10) != 0) cp_size = delta.charCodeAt(pos += 1);
189 if((c & 0x20) != 0) cp_size = cp_size | (delta.charCodeAt(pos += 1) << 8);
190 if((c & 0x40) != 0) cp_size = cp_size | (delta.charCodeAt(pos += 1) << 16);
191 if(cp_size == 0) cp_size = 0x10000;
192
193 pos = pos + 1;
194 dest = dest + base.substr(cp_off, cp_size);
195 } else if(c != 0) {
196 dest = dest + delta.substr(pos, c);
197 pos = pos + c;
198 } else {
199 throw "invalid delta data";
200 }
201 }
202
203 return dest;
204}
205
206var patch_delta_header_size = function(delta, pos) {
207 var size = 0;
208 var shift = 0;
209
210 do {
211 var c = delta.charCodeAt(pos);
212 if(c == null) throw 'invalid delta data';
213 pos = pos + 1;
214 size = size | ((c & 0x7f) << shift);
215 shift = shift + 7
216
217 } while((c & 0x80) != 0);
218
219 // Return the header size and position
220 return [size, pos];
221}
222
223var unpack_compressed = function(pack, offset, destsize) {
224 var outdata = "";
225 var file_offset = offset;
226 var packfile = fs.openSync(pack.name, "r");
227
228 // Read in the compressed object (this could be huge :()
229 // TODO TODO TODO, change unzip method to allow for initializing the structure and then decoding
230 // pieces
231 var indata = new Buffer(destsize + 100);
232 var bytes_read = fs.readSync(packfile, indata, 0, destsize + 100, file_offset);
233 // Close the file
234 fs.closeSync(packfile);
235 // Adjust the file_offset
236 file_offset = file_offset + destsize;
237 outdata = outdata + new Zlib.Unzip(indata).unzip();
238
239 if(outdata.size > destsize) {
240 throw "error reading pack data";
241 }
242 // Return the data read from the compressed block
243 return outdata;
244}
245
246var find_object_in_index = function(pack, idx, sha1) {
247 // Parse the first value of the sha as an index
248 var slot = sha1.charCodeAt(0);
249 if(slot == NaN) return null;
250
251 // Unpack the variables
252 var first = pack.offsets[slot];
253 var last = pack.offsets[slot + 1];
254
255 while(first < last) {
256 var mid = parseInt((first + last) / 2);
257 // If we have a version 2 pack file
258 if(pack.version == 2) {
259 // Fetch the sha1
260 var midsha1 = idx.index([(OFFSET_START + (mid * SHA1_SIZE)), SHA1_SIZE]);
261 var compare_sha1 = '';
262 // Convert midsha1 to allow for correct string comparision
263 for(var i = 0; i < midsha1.length; i++) {
264 compare_sha1 = compare_sha1 + String.fromCharCode(midsha1[i]);
265 }
266
267 // Do a locale Compare
268 var cmp = compare_sha1.localeCompare(sha1);
269 if(cmp < 0) {
270 first = mid + 1;
271 } else if(cmp > 0) {
272 last = mid;
273 } else {
274 var pos = OFFSET_START + (pack.size * (SHA1_SIZE + CRC_SIZE)) + (mid * OFFSET_SIZE);
275 var offset = idx.index([pos, OFFSET_SIZE]);
276 offset = BinaryParser.toInt(reverse_buffer(offset).toString('binary', 0, 4));
277 return offset;
278 }
279 } else {
280 var midsha1 = idx.index([SHA1_START + mid * ENTRY_SIZE, SHA1_SIZE]);
281 var compare_sha1 = '';
282 // Convert midsha1 to allow for correct string comparision
283 for(var i = 0; i < midsha1.length; i++) {
284 compare_sha1 = compare_sha1 + String.fromCharCode(midsha1[i]);
285 }
286
287 // Do a locale Compare
288 var cmp = compare_sha1.localeCompare(sha1);
289 if(cmp < 0) {
290 first = mid + 1;
291 } else if(cmp > 0) {
292 last = mid;
293 } else {
294 var pos = OFFSET_START + mid * ENTRY_SIZE;
295 var offset = idx.index([pos, OFFSET_SIZE]);
296 offset = BinaryParser.toInt(reverse_buffer(offset).toString('binary', 0, 4));
297 return offset;
298 }
299 }
300 }
301 return null;
302}
303
304var find_object = function(pack, sha1) {
305 var obj = null;
306 // Should I not use the cached version in the future ? TODO
307 with_idx(pack, function(err, idx) {
308 obj = find_object_in_index(pack, idx, sha1);
309 })
310
311 return obj;
312}
313
314var reverse_buffer = function(buffer) {
315 var result_buffer = new Buffer(buffer.length);
316 var length = buffer.length;
317
318 for(var i = 0; i < length; i++) {
319 result_buffer[length - 1 - i] = buffer[i];
320 }
321
322 return result_buffer;
323}
324
325var init_pack = function(pack) {
326 // TODO TODO TODO
327 with_idx(pack, function(err, idx) {
328 // Reset pack offsets
329 pack.offsets = [0];
330 // Do a max of FAN_OUT_COUNT to avoid going crazy
331 for(var i = 0; i < FAN_OUT_COUNT; i++) {
332 // Each offset value is a 4 byte network encoded integer
333 var pos = idx.index([i * IDX_OFFSET_SIZE, IDX_OFFSET_SIZE])
334 pos = BinaryParser.toInt(reverse_buffer(pos).toString('binary', 0, 4));
335 // If the position is less than the pack offset stored the pack index is corrupt
336 if(pos < pack.offsets[i]) {
337 throw "pack " + pack.name + " has discontinuous index " + i;
338 }
339 // Add offset position to list of tracked offsets
340 pack.offsets.push(pos);
341 }
342 // Adjust the pack size
343 pack.size = pack.offsets[pack.offsets.length - 1];
344 // Close all files
345 idx.close();
346 });
347}
348
349var with_idx = function(pack, index_file, callback) {
350 var args = Array.prototype.slice.call(arguments, 1);
351 callback = args.pop();
352 index_file = args.length ? args.shift() : null;
353 // Final idx file name
354 var idx_file_name = null;
355 // Define file handle variable
356 var idxfile = null;
357
358 if(!index_file) {
359 index_file = pack.name;
360 idx_file_name = pack.name.substr(0, pack.name.length - 4) + "idx";
361 idxfile = fs.openSync(pack.name.substr(0, pack.name.length - 4) + "idx", "r");
362 } else {
363 idx_file_name = index_file;
364 idxfile = fs.openSync(index_file, "r");
365 }
366
367 // Read header
368 var sign_buffer = new Buffer(4);
369 var signature = '';
370 fs.readSync(idxfile, sign_buffer, 0, 4);
371 for(var i = 0; i < sign_buffer.length; i++) {
372 signature = signature + BinaryParser.fromByte(sign_buffer[i]);
373 }
374
375 // Extract version of pack
376 var ver_buffer = new Buffer(4);
377 fs.readSync(idxfile, ver_buffer, 0, 4);
378 var ver = BinaryParser.toInt(reverse_buffer(ver_buffer).toString('binary', 0, 4));
379 // Close idx file
380 fs.closeSync(idxfile);
381 // If we have a IDX pack signature this is at least version 2 of the file format
382 if(signature == PACK_IDX_SIGNATURE) {
383 if(ver != 2) {
384 throw ("pack " + pack.name + " has unknown pack file version " + ver);
385 }
386 pack.version = 2;
387 } else {
388 pack.version = 1;
389 }
390 // Create a file window and return it
391 var idx = new FileWindow(idx_file_name, pack.version);
392 callback(null, idx);
393}
\No newline at end of file