git ssb

0+

cel / pull-git-remote-helper



Tree: b1ed4631de0942640e4c7591c2be9dbdf8e98240

Files: b1ed4631de0942640e4c7591c2be9dbdf8e98240 / index.js

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

Built with git-ssb-web