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