git ssb

0+

cel / pull-git-remote-helper



Tree: 16cb3742dd9d961d752709c70a7d7e0d0139c0f8

Files: 16cb3742dd9d961d752709c70a7d7e0d0139c0f8 / index.js

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

Built with git-ssb-web