git ssb

0+

cel / pull-git-remote-helper



Tree: f60f57ddecf31964b98f5147c7ee02faa6c871fc

Files: f60f57ddecf31964b98f5147c7ee02faa6c871fc / index.js

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

Built with git-ssb-web