git ssb

0+

cel / pull-git-remote-helper



Tree: 17c29f2cc34026a1af2ec44ea384e15716514a8f

Files: 17c29f2cc34026a1af2ec44ea384e15716514a8f / index.js

8452 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 capabilitiesCmd(read) {
22 read(null, function next(end, data) {
23 if(end === true) return
24 if(end) throw end
25
26 console.log(data)
27 read(null, next)
28 })
29}
30
31// return a source that delivers some data and then ends
32// TODO: use pull.once and abortCb for this
33function endSource(data) {
34 var done
35 return function (end, cb) {
36 if (done) return cb(true)
37 done = true
38 cb(null, data)
39 }
40}
41
42function capabilitiesSource(prefix) {
43 return endSource([
44 'option',
45 'connect',
46 'refspec refs/heads/*:refs/' + prefix + '/heads/*',
47 'refspec refs/tags/*:refs/' + prefix + '/tags/*',
48 ].join('\n') + '\n\n')
49}
50
51function split2(str, delim) {
52 var i = str.indexOf(delim || ' ')
53 return (i === -1) ? [str, ''] : [
54 str.substr(0, i),
55 str.substr(i + 1)
56 ]
57}
58
59function split3(str) {
60 var args = split2(str)
61 return [args[0]].concat(split2(args[1]))
62}
63
64function optionSource(cmd, options) {
65 var args = split2(cmd)
66 var msg = handleOption(options, args[0], args[1])
67 msg = (msg === true) ? 'ok'
68 : (msg === false) ? 'unsupported'
69 : 'error ' + msg
70 return endSource(msg + '\n')
71}
72
73function listSource() {
74 return endSource([
75 /* TODO */
76 ].join('\n') + '\n\n')
77}
78
79function createHash() {
80 var hash = crypto.createHash('sha256')
81 var hasher = pull.through(function (data) {
82 hash.update(data)
83 }, function () {
84 hasher.digest = '&'+hash.digest('base64')+'.sha256'
85 })
86 return hasher
87}
88
89function uploadPack(read, objectSource, refSource) {
90 /* multi_ack thin-pack side-band side-band-64k ofs-delta shallow no-progress
91 * include-tag multi_ack_detailed symref=HEAD:refs/heads/master
92 * agent=git/2.7.0 */
93 var sendRefs = receivePackHeader([
94 ], refSource, false)
95
96 return packLineEncode(
97 cat([
98 sendRefs,
99 pull.once('')
100 ])
101 )
102}
103
104function getRefs() {
105 return pull.values([
106 {
107 hash: '78beaedba9878623cea3862cf18e098cfb901e10',
108 name: 'refs/heads/master'
109 },
110 {
111 hash: '78beaedba9878623cea3862cf18e098cfb901e10',
112 name: 'refs/remotes/cel/master'
113 }
114 ])
115 /*
116 return function (err, cb) {
117 if (err === true) return
118 if (err) throw err
119 // TODO
120 cb(true)
121 }
122 */
123}
124
125function packLineEncode(read) {
126 var ended
127 return function (end, cb) {
128 if (ended) return cb(ended)
129 read(end, function (end, data) {
130 if (ended = end) {
131 cb(end)
132 } else {
133 var len = data ? data.length + 5 : 0
134 cb(end, ('000' + len.toString(16)).substr(-4) + data + '\n')
135 }
136 })
137 }
138}
139
140function rev(str) {
141 return str === '0000000000000000000000000000000000000000' ? null : str
142}
143
144function packLineDecode(read, options) {
145 var b = buffered(read)
146 var readPrefix = b.chunks(4)
147 var ended
148
149 function readPackLine(abort, cb) {
150 readPrefix(abort, function (end, buf) {
151 if (ended = end) return cb(end)
152 var len = parseInt(buf, 16)
153 if (!len)
154 return cb(null, new Buffer(''))
155 // TODO: figure out this -4 thing
156 b.chunks(len - 4)(null, function (end, buf) {
157 if (ended = end) return cb(end)
158 cb(end, buf)
159 })
160 })
161 }
162
163 function readUpdate(abort, cb) {
164 readPackLine(abort, function (end, line) {
165 if (end) return cb(end)
166 if (options.verbosity >= 2)
167 console.error('line', line.toString('ascii'))
168 if (!line.length) return cb(true)
169 var args = split3(line.toString('ascii'))
170 var args2 = split2(args[2], '\0')
171 var caps = args2[1]
172 if (caps && options.verbosity >= 2)
173 console.error('update capabilities:', caps)
174 cb(null, {
175 old: rev(args[0]),
176 new: rev(args[1]),
177 name: args2[0]
178 })
179 })
180 }
181
182 b.packLines = readPackLine
183 b.updates = readUpdate
184
185 return b
186}
187
188// run a callback when a pipeline ends
189// TODO: find a better way to do this
190function onThroughEnd(onEnd) {
191 return function (read) {
192 return function (end, cb) {
193 read(end, function (end, data) {
194 cb(end, data)
195 if (end)
196 onEnd(end === true ? null : end)
197 })
198 }
199 }
200}
201
202/*
203TODO: investigate capabilities
204report-status delete-refs side-band-64k quiet atomic ofs-delta
205*/
206
207// Get a line for each ref that we have. The first line also has capabilities.
208// Wrap with packLineEncode.
209function receivePackHeader(capabilities, refSource, usePlaceholder) {
210 var first = true
211 var ended
212 return function (abort, cb) {
213 if (ended) return cb(true)
214 refSource(abort, function (end, ref) {
215 ended = end
216 var name = ref && ref.name
217 var hash = ref && ref.hash
218 if (first && usePlaceholder) {
219 first = false
220 if (end) {
221 // use placeholder data if there are no refs
222 hash = '0000000000000000000000000000000000000000'
223 name = 'capabilities^{}'
224 }
225 name += '\0' + capabilities.join(' ')
226 } else if (end) {
227 return cb(true)
228 }
229 cb(null, hash + ' ' + name)
230 })
231 }
232}
233
234function receivePack(read, objectSink, refSource, refSink, options) {
235 var ended
236 var sendRefs = receivePackHeader([], refSource, true)
237
238 return packLineEncode(
239 cat([
240 // send our refs
241 sendRefs,
242 pull.once(''),
243 function (abort, cb) {
244 if (abort) return
245 // receive their refs
246 var lines = packLineDecode(read, options)
247 pull(
248 lines.updates,
249 onThroughEnd(refsDone),
250 refSink
251 )
252 function refsDone(err) {
253 if (err) return cb(err)
254 pull(
255 lines.passthrough,
256 pack.decode(cb),
257 objectSink
258 )
259 }
260 },
261 pull.once('unpack ok')
262 ])
263 )
264}
265
266function prepend(data, read) {
267 var done
268 return function (end, cb) {
269 if (done) {
270 read(end, cb)
271 } else {
272 done = true
273 cb(null, data)
274 }
275 }
276}
277
278module.exports = function (opts) {
279 var ended
280 var prefix = opts.prefix
281 var objectSink = opts.objectSink
282 var objectSource = opts.objectSource || pull.empty()
283 var refSource = opts.refSource || pull.empty()
284 var refSink = opts.refSink
285
286 var options = {
287 verbosity: 1,
288 progress: false
289 }
290
291 function handleConnect(cmd, read) {
292 var args = split2(cmd)
293 switch (args[0]) {
294 case 'git-upload-pack':
295 return prepend('\n', uploadPack(read, objectSource, refSource))
296 case 'git-receive-pack':
297 return prepend('\n', receivePack(read, objectSink, refSource,
298 refSink, options))
299 default:
300 return pull.error(new Error('Unknown service ' + args[0]))
301 }
302 }
303
304 function handleCommand(line, read) {
305 var args = split2(line)
306 switch (args[0]) {
307 case 'capabilities':
308 return capabilitiesSource(prefix)
309 case 'list':
310 return listSource()
311 case 'connect':
312 return handleConnect(args[1], read)
313 case 'option':
314 return optionSource(args[1], options)
315 default:
316 return pull.error(new Error('Unknown command ' + line))
317 }
318 }
319
320 return function (read) {
321 var b = buffered()
322 b(read)
323 var command
324
325 function getCommand(cb) {
326 b.lines(null, function next(end, line) {
327 if (ended = end)
328 return cb(end)
329
330 if (line == '')
331 return b.lines(null, next)
332
333 if (options.verbosity > 1)
334 console.error('command:', line)
335
336 var cmdSource = handleCommand(line, b.passthrough)
337 cb(null, cmdSource)
338 })
339 }
340
341 return function next(abort, cb) {
342 if (ended) return cb(ended)
343
344 if (!command) {
345 if (abort) return
346 getCommand(function (end, cmd) {
347 command = cmd
348 next(end, cb)
349 })
350 return
351 }
352
353 command(abort, function (err, data) {
354 if (err) {
355 command = null
356 if (err !== true)
357 cb(err, data)
358 else
359 next(abort, cb)
360 } else {
361 cb(null, data)
362 }
363 })
364 }
365 }
366}
367
368

Built with git-ssb-web