git ssb

0+

cel / pull-git-remote-helper



Tree: b2cf98073f3c52efa61e1a9d9a2a6b4af4944906

Files: b2cf98073f3c52efa61e1a9d9a2a6b4af4944906 / index.js

11385 bytesRaw
1var packCodec = require('js-git/lib/pack-codec')
2var pull = require('pull-stream')
3var cat = require('pull-cat')
4var buffered = require('pull-buffered')
5var pack = require('./pack')
6
7function handleOption(options, name, value) {
8 switch (name) {
9 case 'verbosity':
10 options.verbosity = +value || 0
11 return true
12 case 'progress':
13 options.progress = !!value && value !== 'false'
14 return true
15 default:
16 console.error('unknown option', name + ': ' + value)
17 return false
18 }
19}
20
21function capabilitiesSource() {
22 return pull.once([
23 'option',
24 'connect',
25 ].join('\n') + '\n\n')
26}
27
28function split2(str, delim) {
29 var i = str.indexOf(delim || ' ')
30 return (i === -1) ? [str, ''] : [
31 str.substr(0, i),
32 str.substr(i + 1)
33 ]
34}
35
36function split3(str) {
37 var args = split2(str)
38 return [args[0]].concat(split2(args[1]))
39}
40
41function optionSource(cmd, options) {
42 var args = split2(cmd)
43 var msg = handleOption(options, args[0], args[1])
44 msg = (msg === true) ? 'ok'
45 : (msg === false) ? 'unsupported'
46 : 'error ' + msg
47 return pull.once(msg + '\n')
48}
49
50// transform ref objects into lines
51function listRefs(read) {
52 var ended
53 return function (abort, cb) {
54 if (ended) return cb(ended)
55 read(abort, function (end, ref) {
56 ended = end
57 if (end === true) cb(null, '\n')
58 if (end) cb(end)
59 else cb(null,
60 [ref.value, ref.name].concat(ref.attrs || []).join(' ') + '\n')
61 })
62 }
63}
64
65// upload-pack: fetch to client
66function uploadPack(read, haveObject, getObjects, refSource, wantSink, options) {
67 /* multi_ack thin-pack side-band side-band-64k ofs-delta shallow no-progress
68 * include-tag multi_ack_detailed symref=HEAD:refs/heads/master
69 * agent=git/2.7.0 */
70 var sendRefs = receivePackHeader([
71 ], refSource, false)
72
73 var lines = packLineDecode(read, options)
74 var readHave = lines.haves()
75 var acked
76 var commonHash
77 var sendPack
78 var earlyDisconnect
79
80 // Packfile negotiation
81 return cat([
82 packLineEncode(cat([
83 sendRefs,
84 pull.once(''),
85 function (abort, cb) {
86 if (abort) return
87 if (acked) return cb(true)
88 // read upload request (wants list) from client
89 var readWant = lines.wants(wantsDone)
90 readWant(null, function (end, want) {
91 if (end === true) {
92 // client disconnected before sending wants
93 earlyDisconnect = true
94 cb(true)
95 } else if (end) {
96 cb(end)
97 } else {
98 wantSink(readWant)
99 }
100 })
101
102 function wantsDone(err) {
103 // console.error('wants done', err, earlyDisconnect)
104 if (err) return cb(err)
105 if (earlyDisconnect) return cb(true)
106 // Read upload haves (haves list).
107 // On first obj-id that we have, ACK
108 // If we have none, NAK.
109 // TODO: implement multi_ack_detailed
110 readHave(null, function next(end, have) {
111 if (end === true) {
112 // found no common object
113 acked = true
114 cb(null, 'NAK')
115 } else if (end)
116 cb(end)
117 else if (have.type != 'have')
118 cb(new Error('Unknown have' + JSON.stringify(have)))
119 else
120 haveObject(have.hash, function (haveIt) {
121 if (!haveIt)
122 return readHave(null, next)
123 commonHash = haveIt
124 acked = true
125 cb(null, 'ACK ' + have.hash)
126 })
127 })
128 }
129 },
130 ])),
131 function havesDone(abort, cb) {
132 // console.error("haves done", abort && typeof abort, sendPack && typeof sendPack, abort, earlyDisconnect)
133 if (abort || earlyDisconnect) return cb(abort || true)
134 // send pack file to client
135 if (!sendPack)
136 getObjects(commonHash, function (err, numObjects, readObject) {
137 sendPack = pack.encode(numObjects, readObject)
138 havesDone(abort, cb)
139 })
140 else
141 sendPack(abort, cb)
142 }
143 ])
144}
145
146function packLineEncode(read) {
147 var ended
148 return function (end, cb) {
149 if (ended) return cb(ended)
150 read(end, function (end, data) {
151 if (ended = end) {
152 cb(end)
153 } else {
154 if (data)
155 data += '\n'
156 else
157 data = ''
158 var len = data ? data.length + 4 : 0
159 var hexLen = ('000' + len.toString(16)).substr(-4)
160 var pkt = hexLen + data
161 // console.error('>', JSON.stringify(pkt))
162 cb(end, pkt)
163 }
164 })
165 }
166}
167
168function rev(str) {
169 return str === '0000000000000000000000000000000000000000' ? null : str
170}
171
172/* pull-stream/source.js */
173function abortCb(cb, abort, onAbort) {
174 cb(abort)
175 onAbort && onAbort(abort === true ? null: abort)
176 return
177}
178
179function packLineDecode(read, options) {
180 var b = buffered(read)
181 var readPrefix = b.chunks(4)
182 var ended
183
184 function readPackLine(abort, cb) {
185 if (ended) return cb(ended)
186 readPrefix(abort, function (end, buf) {
187 if (ended = end) return cb(end)
188 var len = parseInt(buf, 16)
189 if (!len)
190 return cb(null, new Buffer(''))
191 // TODO: figure out this -4 thing
192 b.chunks(len - 4)(null, function (end, buf) {
193 if (ended = end) return cb(end)
194 cb(end, buf)
195 })
196 })
197 }
198
199 function readPackLineStr(abort, cb) {
200 if (ended) return cb(ended)
201 readPackLine(abort, function (end, buf) {
202 if (ended = end) return cb(end)
203 // trim newline
204 var len = buf.length
205 if (buf[len - 1] == 0xa)
206 len--
207 var line = buf.toString('ascii', 0, len)
208 cb(null, line)
209 })
210 }
211
212 function readUpdate(abort, cb) {
213 readPackLine(abort, function (end, line) {
214 if (end) return cb(end)
215 if (options.verbosity >= 2)
216 console.error('line', line.toString('ascii'))
217 if (!line.length) return cb(true)
218 var args = split3(line.toString('ascii'))
219 var args2 = split2(args[2], '\0')
220 var caps = args2[1]
221 if (caps && options.verbosity >= 2)
222 console.error('update capabilities:', caps)
223 cb(null, {
224 old: rev(args[0]),
225 new: rev(args[1]),
226 name: args2[0]
227 })
228 })
229 }
230
231 function havesWants(onEnd) {
232 return function readWant(abort, cb) {
233 readPackLineStr(abort, function (end, line) {
234 if (end) return abortCb(cb, end, onEnd)
235 if (options.verbosity >= 2)
236 console.error('line', line)
237 // if (!line.length) return cb(true)
238 if (!line.length || line == 'done')
239 return abortCb(cb, true, onEnd)
240 var args = split3(line)
241 var caps = args[2]
242 if (caps && options.verbosity >= 2)
243 console.error('want capabilities:', caps)
244 cb(null, {
245 type: args[0],
246 hash: args[1],
247 })
248 })
249 }
250 }
251
252 b.packLines = readPackLine
253 b.updates = readUpdate
254 b.wants = b.haves = havesWants
255
256 return b
257}
258
259// run a callback when a pipeline ends
260// TODO: find a better way to do this
261function onThroughEnd(onEnd) {
262 return function (read) {
263 return function (end, cb) {
264 read(end, function (end, data) {
265 cb(end, data)
266 if (end)
267 onEnd(end === true ? null : end)
268 })
269 }
270 }
271}
272
273/*
274TODO: investigate capabilities
275report-status delete-refs side-band-64k quiet atomic ofs-delta
276*/
277
278// Get a line for each ref that we have. The first line also has capabilities.
279// Wrap with packLineEncode.
280function receivePackHeader(capabilities, refSource, usePlaceholder) {
281 var first = true
282 var ended
283 return function (abort, cb) {
284 if (ended) return cb(true)
285 refSource(abort, function (end, ref) {
286 ended = end
287 var name = ref && ref.name
288 var value = ref && ref.value
289 if (first && usePlaceholder) {
290 first = false
291 if (end) {
292 // use placeholder data if there are no refs
293 value = '0000000000000000000000000000000000000000'
294 name = 'capabilities^{}'
295 }
296 name += '\0' + capabilities.join(' ')
297 } else if (end) {
298 return cb(true)
299 }
300 cb(null, value + ' ' + name)
301 })
302 }
303}
304
305// receive-pack: push from client
306function receivePack(read, objectSink, refSource, refSink, options) {
307 var ended
308 var sendRefs = receivePackHeader([
309 'delete-refs',
310 ], refSource, true)
311
312 return packLineEncode(
313 cat([
314 // send our refs
315 sendRefs,
316 pull.once(''),
317 function (abort, cb) {
318 if (abort) return
319 // receive their refs
320 var lines = packLineDecode(read, options)
321 pull(
322 lines.updates,
323 onThroughEnd(refsDone),
324 refSink
325 )
326 function refsDone(err) {
327 if (err) return cb(err)
328 pull(
329 lines.passthrough,
330 pack.decode(cb),
331 objectSink
332 )
333 }
334 },
335 pull.once('unpack ok')
336 ])
337 )
338}
339
340function prepend(data, read) {
341 var done
342 return function (end, cb) {
343 if (done) {
344 read(end, cb)
345 } else {
346 done = true
347 cb(null, data)
348 }
349 }
350}
351
352module.exports = function (opts) {
353 var ended
354 var objectSink = opts.objectSink
355 var haveObject = opts.haveObject || function (hash, cb) { cb(false) }
356 var getObjects = opts.getObjects || function (id, cb) {
357 cb(null, 0, pull.empty())
358 }
359 var refSource = opts.refSource || pull.empty()
360 var refSink = opts.refSink || pull.drain()
361 var wantSink = opts.wantSink || pull.drain()
362
363 var options = {
364 verbosity: 1,
365 progress: false
366 }
367
368 function handleConnect(cmd, read) {
369 var args = split2(cmd)
370 switch (args[0]) {
371 case 'git-upload-pack':
372 return prepend('\n', uploadPack(read, haveObject, getObjects, refSource,
373 wantSink, options))
374 case 'git-receive-pack':
375 return prepend('\n', receivePack(read, objectSink, refSource,
376 refSink, options))
377 default:
378 return pull.error(new Error('Unknown service ' + args[0]))
379 }
380 }
381
382 function handleCommand(line, read) {
383 var args = split2(line)
384 switch (args[0]) {
385 case 'capabilities':
386 return capabilitiesSource()
387 case 'list':
388 return listRefs(refSource)
389 case 'connect':
390 return handleConnect(args[1], read)
391 case 'option':
392 return optionSource(args[1], options)
393 default:
394 return pull.error(new Error('Unknown command ' + line))
395 }
396 }
397
398 return function (read) {
399 var b = buffered()
400 b(read)
401 var command
402
403 function getCommand(cb) {
404 b.lines(null, function next(end, line) {
405 if (ended = end)
406 return cb(end)
407
408 if (line == '')
409 return b.lines(null, next)
410
411 if (options.verbosity > 1)
412 console.error('command:', line)
413
414 var cmdSource = handleCommand(line, b.passthrough)
415 cb(null, cmdSource)
416 })
417 }
418
419 return function next(abort, cb) {
420 if (ended) return cb(ended)
421
422 if (!command) {
423 if (abort) return
424 getCommand(function (end, cmd) {
425 command = cmd
426 next(end, cb)
427 })
428 return
429 }
430
431 command(abort, function (err, data) {
432 if (err) {
433 command = null
434 if (err !== true)
435 cb(err, data)
436 else
437 next(abort, cb)
438 } else {
439 cb(null, data)
440 }
441 })
442 }
443 }
444}
445

Built with git-ssb-web