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