git ssb

0+

cel / pull-git-remote-helper



Tree: 4a1283c958a5ddcc50da3e75ed1c3c71b0236c56

Files: 4a1283c958a5ddcc50da3e75ed1c3c71b0236c56 / lib / pack.js

10613 bytesRaw
1var buffered = require('pull-buffered')
2var pull = require('pull-stream')
3var toPull = require('stream-to-pull-stream')
4var pako = require('pako')
5var createHash = require('./util').createHash
6var cat = require('pull-cat')
7
8exports.decode = decodePack
9exports.encode = encodePack
10
11var PACK_VERSION = 2
12
13var objectTypes = [
14 'none', 'commit', 'tree', 'blob',
15 'tag', 'unused', 'ofs-delta', 'ref-delta'
16]
17var objectTypeNums = {
18 commit: 1,
19 tree: 2,
20 blob: 3,
21 tag: 4,
22 'ofs-delta': 6,
23 'ref-delta': 7
24}
25
26function error(cb) {
27 return function (err) {
28 cb(err || true)
29 }
30}
31
32function inflateBytes(read) {
33 var inflate = new pako.Inflate()
34 var ended, dataOut
35
36 inflate.onData = function (data) {
37 dataOut = new Buffer(data)
38 // console.error('inflated data', data.length)
39 }
40
41 inflate.onEnd = function (status) {
42 ended = (status === 0) ? true : new Error(inflate.strm.msg)
43 // console.error('inflated end', status, ended)
44 }
45
46 return function (abort, cb) {
47 if (ended) return cb(ended)
48 read(abort, function next(end, data) {
49 if (end === true) {
50 end = null
51 data = []
52 }
53 if (ended = end) return cb(end)
54 if (data.length > 1) return cb(new Error('got more than one byte'))
55 dataOut = null
56 inflate.push(data, end === true)
57 if (dataOut)
58 cb(null, dataOut)
59 else if (ended)
60 cb(ended)
61 else
62 // let the stack unwind
63 setImmediate(function () {
64 read(null, next)
65 })
66 })
67 }
68}
69
70function deflate(read) {
71 var def = new pako.Deflate()
72 var queue = []
73 var ended
74
75 def.onData = function (data) {
76 queue.push([null, new Buffer(data)])
77 }
78
79 def.onEnd = function (status) {
80 queue.push([(status === 0) ? true : new Error(def.strm.msg)])
81 }
82
83 return function readOut(abort, cb) {
84 if (ended)
85 cb(ended)
86 else if (queue.length)
87 cb.apply(this, queue.shift())
88 else
89 read(abort, function next(end, data) {
90 if (end === true) def.push([], true)
91 else if (end) return cb(end)
92 else def.push(data)
93 readOut(null, cb)
94 })
95 }
96}
97
98function decodePack(opts, repo, onEnd, read) {
99 if (read === undefined)
100 return decodePack.bind(this, opts, repo, onEnd)
101
102 var ended
103 var numObjects = -1
104 var checksum = createHash('sha1')
105 var b = buffered(read)
106 // TODO: optimize to pass through buffers to checksum
107 var readByte = checksum(b.chunks(1))
108 var readWord = checksum(b.chunks(4))
109 var readHash = checksum(b.chunks(20))
110 var readChecksum = b.chunks(20)
111 var expectChecksum = true
112
113 function readHeader(cb) {
114 readWord(null, function (end, header) {
115 if (ended = end) return cb(end)
116 if (!header.equals(header, new Buffer('PACK')))
117 read(new Error('Invalid packfile header'), error(cb))
118 else
119 readVersion(cb)
120 })
121 }
122
123 function readVersion(cb) {
124 readWord(null, function (end, word) {
125 if (ended = end) return cb(end)
126 var version = word.readUInt32BE()
127 if (version < 2 || version > 3)
128 read(new Error('Invalid packfile version ' + version), error(cb))
129 else
130 readNumObjects(cb)
131 })
132 }
133
134 function readNumObjects(cb) {
135 readWord(null, function (end, word) {
136 if (ended = end) return cb(end)
137 numObjects = word.readUInt32BE()
138 if (opts.verbosity >= 2)
139 console.error(numObjects + ' objects')
140 readObject(null, cb)
141 })
142 }
143
144 function readTypedVarInt(cb) {
145 var type, value, shift
146 // https://codewords.recurse.com/images/three/varint.svg
147 readByte(null, function (end, buf) {
148 if (ended = end) return cb(end)
149 var firstByte = buf[0]
150 type = objectTypes[(firstByte >> 4) & 7]
151 value = firstByte & 15
152 // console.error('byte1', firstByte, firstByte.toString(2))
153 shift = 4
154 checkByte(firstByte)
155 })
156
157 function checkByte(byte) {
158 if (byte & 0x80)
159 readByte(null, gotByte)
160 else
161 cb(null, type, value)
162 }
163
164 function gotByte(end, buf) {
165 if (ended = end) return cb(end)
166 var byte = buf[0]
167 value += (byte & 0x7f) << shift
168 shift += 7
169 // console.error('byte', byte, byte.toString(2))
170 checkByte(byte)
171 }
172 }
173
174 function getObject(cb) {
175 readTypedVarInt(function (end, type, length) {
176 if (opts.verbosity >= 2)
177 console.error('read object header', end, type, length)
178 numObjects--
179 if (end === true && expectChecksum)
180 onEnd(new Error('Missing checksum'))
181 if (ended = end) return cb(end)
182 // TODO: verify that the inflated data is the correct length
183 if (type == 'ref-delta')
184 getObjectFromRefDelta(length, cb)
185 else
186 cb(null, {
187 type: type,
188 length: length,
189 read: inflateBytes(readByte)
190 })
191 })
192 }
193
194 // TODO: test with ref-delta objects in pack
195 function getObjectFromRefDelta(length, cb) {
196 readHash(null, function (end, sourceHash) {
197 if (end) return cb(end)
198 sourceHash = sourceHash.toString('hex')
199 var b = buffered(inflateBytes(readByte))
200 var readInflatedByte = b.chunks(1)
201 readVarInt(readInflatedByte, function (err, expectedSourceLength) {
202 if (err) return cb(err)
203 readVarInt(readInflatedByte, function (err, expectedTargetLength) {
204 if (err) return cb(err)
205 if (opts.verbosity >= 3)
206 console.error('getting object', sourceHash)
207 repo.getObject(sourceHash, function (err, sourceObject) {
208 if (opts.verbosity >= 3)
209 console.error('got object', sourceHash, sourceObject, err)
210 if (err) return cb(err)
211 if (sourceObject.length != expectedSourceLength)
212 cb(new Error('Incorrect source object size in ref delta'))
213 else
214 patchObject(opts, b, length, sourceObject,
215 expectedTargetLength, cb)
216 })
217 })
218 })
219 })
220 }
221
222 function readTrailer(cb) {
223 // read the checksum before it updates to include the trailer
224 var expected = checksum.digest()
225 readChecksum(null, function (end, value) {
226 cb(true)
227 if (end === true && expectChecksum)
228 onEnd(new Error('Missing checksum'))
229 if (!value.equals(expected)) {
230 onEnd(new Error('Checksum mismatch: ' +
231 expected.hexSlice() + ' != ' + value.hexSlice()))
232 } else {
233 if (opts.verbosity >= 3)
234 console.error('checksum ok', expected.hexSlice())
235 onEnd(null)
236 }
237 })
238 }
239
240 function readObject(abort, cb) {
241 if (ended) cb(ended)
242 else if (abort) read(abort, function (err) { cb(ended = err || abort) })
243 else if (numObjects < 0) readHeader(cb)
244 else if (numObjects > 0) getObject(cb)
245 else if (expectChecksum) readTrailer(cb)
246 }
247
248 return readObject
249}
250
251function readVarInt(readByte, cb) {
252 var value = 0, shift = 0
253 readByte(null, function gotByte(end, buf) {
254 if (ended = end) return cb(end)
255 var byte = buf[0]
256 value += (byte & 0x7f) << shift
257 shift += 7
258 if (byte & 0x80)
259 readByte(null, gotByte)
260 else
261 cb(null, value)
262 })
263}
264
265function patchObject(opts, deltaB, deltaLength, srcObject, targetLength, cb) {
266 var readByte = deltaB.chunks(1)
267 var srcBuf
268 var ended
269
270 if (opts.verbosity >= 2)
271 console.error('patching', srcObject.type, targetLength)
272 pull(
273 srcObject.read,
274 pull.collect(function (err, bufs) {
275 srcBuf = Buffer.concat(bufs, srcObject.length)
276 cb(null, {
277 type: srcObject.type,
278 length: targetLength,
279 read: read
280 })
281 })
282 )
283
284 function read(abort, cb) {
285 if (ended) return cb(ended)
286 readByte(null, function (end, dBuf) {
287 if (ended = end) return cb(end)
288 var cmd = dBuf[0]
289 if (cmd & 0x80)
290 // skip a variable amount and then pass through a variable amount
291 readOffsetSize(cmd, deltaB, function (err, offset, size) {
292 if (err) return earlyEnd(err)
293 var buf = srcBuf.slice(offset, offset + size)
294 cb(end, buf)
295 })
296 else if (cmd)
297 // insert `cmd` bytes from delta
298 deltaB.chunks(cmd)(null, cb)
299 else
300 cb(new Error("unexpected delta opcode 0"))
301 })
302
303 function earlyEnd(err) {
304 cb(err === true ? new Error('stream ended early') : err)
305 }
306 }
307}
308
309function readOffsetSize(cmd, b, readCb) {
310 var readByte = b.chunks(1)
311 var offset = 0, size = 0
312
313 function addByte(bit, outPos, cb) {
314 if (cmd & (1 << bit))
315 readByte(null, function (err, buf) {
316 if (err) readCb(err)
317 else cb(buf[0] << (outPos << 3))
318 })
319 else
320 cb(0)
321 }
322
323 addByte(0, 0, function (val) {
324 offset = val
325 addByte(1, 1, function (val) {
326 offset |= val
327 addByte(2, 2, function (val) {
328 offset |= val
329 addByte(3, 3, function (val) {
330 offset |= val
331 addSize()
332 })
333 })
334 })
335 })
336 function addSize() {
337 addByte(4, 0, function (val) {
338 size = val
339 addByte(5, 1, function (val) {
340 size |= val
341 addByte(6, 2, function (val) {
342 size |= val
343 readCb(null, offset, size || 0x10000)
344 })
345 })
346 })
347 }
348}
349
350function encodeTypedVarInt(typeStr, length, cb) {
351 var type = objectTypeNums[typeStr]
352 // console.error('TYPE', type, typeStr, 'len', length, typeof cb)
353 if (!type)
354 return cb(new Error("Bad object type " + typeStr))
355
356 var vals = []
357 var b = (type << 4) | (length & 15)
358 for (length >>= 4; length; length >>= 7) {
359 vals.push(b | 0x80)
360 b = length & 0x7f
361 }
362 vals.push(b)
363 /*
364 console.error('sending var int', vals, vals.map(function (n) {
365 return ('00000000' + Number(n).toString(2)).substr(-8)
366 }))
367 */
368 cb(null, new Buffer(vals))
369}
370
371function encodePack(opts, numObjects, readObject) {
372 if (readObject === undefined)
373 return encodePack.bind(this, numObjects)
374
375 var header = new Buffer(12)
376 header.write('PACK')
377 header.writeUInt32BE(PACK_VERSION, 4)
378 header.writeUInt32BE(numObjects, 8)
379 var checksum = createHash('sha1')
380 var readData
381
382 return cat([
383 checksum(cat([
384 pull.once(header),
385 encodeObject
386 ])),
387 checksum.readDigest
388 ])
389
390 function encodeObject(abort, cb) {
391 if (readData)
392 readData(abort, function (end, data) {
393 if (end === true)
394 readObject(abort, nextObject)
395 else
396 cb(end, data)
397 })
398 else
399 readObject(abort, nextObject)
400
401 function nextObject(end, object) {
402 if (end) return cb(end)
403 readData = deflate(object.read)
404 encodeTypedVarInt(object.type, object.length, cb)
405 }
406 }
407}
408

Built with git-ssb-web