git ssb

0+

cel / pull-git-remote-helper



Tree: 37179aaad737deafec53e3dee30ae7f7b281a62c

Files: 37179aaad737deafec53e3dee30ae7f7b281a62c / index.js

8394 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('./lib/pack')
6var pktLine = require('./lib/pkt-line')
7var util = require('./lib/util')
8
9function handleOption(options, name, value) {
10 switch (name) {
11 case 'verbosity':
12 options.verbosity = +value || 0
13 return true
14 case 'progress':
15 options.progress = !!value && value !== 'false'
16 return true
17 default:
18 console.error('unknown option', name + ': ' + value)
19 return false
20 }
21}
22
23function capabilitiesSource() {
24 return pull.once([
25 'option',
26 'connect',
27 ].join('\n') + '\n\n')
28}
29
30function optionSource(cmd, options) {
31 var args = util.split2(cmd)
32 var msg = handleOption(options, args[0], args[1])
33 msg = (msg === true) ? 'ok'
34 : (msg === false) ? 'unsupported'
35 : 'error ' + msg
36 return pull.once(msg + '\n')
37}
38
39// transform ref objects into lines
40function listRefs(read) {
41 var ended
42 return function (abort, cb) {
43 if (ended) return cb(ended)
44 read(abort, function (end, ref) {
45 ended = end
46 if (end === true) cb(null, '\n')
47 if (end) cb(end)
48 else cb(null,
49 [ref.value, ref.name].concat(ref.attrs || []).join(' ') + '\n')
50 })
51 }
52}
53
54// upload-pack: fetch to client
55function uploadPack(read, haveObject, getObjects, refSource, wantSink, options) {
56 /* multi_ack thin-pack side-band side-band-64k ofs-delta shallow no-progress
57 * include-tag multi_ack_detailed symref=HEAD:refs/heads/master
58 * agent=git/2.7.0 */
59 var sendRefs = receivePackHeader([
60 ], refSource, false)
61
62 var lines = pktLine.decode(read, options)
63 var readHave = lines.haves()
64 var acked
65 var commonHash
66 var sendPack
67 var earlyDisconnect
68
69 // Packfile negotiation
70 return cat([
71 pktLine.encode(cat([
72 sendRefs,
73 pull.once(''),
74 function (abort, cb) {
75 if (abort) return
76 if (acked) return cb(true)
77 // read upload request (wants list) from client
78 var readWant = lines.wants(wantsDone)
79 readWant(null, function (end, want) {
80 if (end === true) {
81 // client disconnected before sending wants
82 earlyDisconnect = true
83 cb(true)
84 } else if (end) {
85 cb(end)
86 } else {
87 wantSink(readWant)
88 }
89 })
90
91 function wantsDone(err) {
92 // console.error('wants done', err, earlyDisconnect)
93 if (err) return cb(err)
94 if (earlyDisconnect) return cb(true)
95 // Read upload haves (haves list).
96 // On first obj-id that we have, ACK
97 // If we have none, NAK.
98 // TODO: implement multi_ack_detailed
99 readHave(null, function next(end, have) {
100 if (end === true) {
101 // found no common object
102 acked = true
103 cb(null, 'NAK')
104 } else if (end)
105 cb(end)
106 else if (have.type != 'have')
107 cb(new Error('Unknown have' + JSON.stringify(have)))
108 else
109 haveObject(have.hash, function (haveIt) {
110 if (!haveIt)
111 return readHave(null, next)
112 commonHash = haveIt
113 acked = true
114 cb(null, 'ACK ' + have.hash)
115 })
116 })
117 }
118 },
119 ])),
120 function havesDone(abort, cb) {
121 // console.error("haves done", abort && typeof abort, sendPack && typeof sendPack, abort, earlyDisconnect)
122 if (abort || earlyDisconnect) return cb(abort || true)
123 // send pack file to client
124 if (!sendPack)
125 getObjects(commonHash, function (err, numObjects, readObject) {
126 sendPack = pack.encode(numObjects, readObject)
127 havesDone(abort, cb)
128 })
129 else
130 sendPack(abort, cb)
131 }
132 ])
133}
134
135// run a callback when a pipeline ends
136// TODO: find a better way to do this
137function onThroughEnd(onEnd) {
138 return function (read) {
139 return function (end, cb) {
140 read(end, function (end, data) {
141 cb(end, data)
142 if (end)
143 onEnd(end === true ? null : end)
144 })
145 }
146 }
147}
148
149/*
150TODO: investigate capabilities
151report-status delete-refs side-band-64k quiet atomic ofs-delta
152*/
153
154// Get a line for each ref that we have. The first line also has capabilities.
155// Wrap with pktLine.encode.
156function receivePackHeader(capabilities, refSource, usePlaceholder) {
157 var first = true
158 var ended
159 return function (abort, cb) {
160 if (ended) return cb(true)
161 refSource(abort, function (end, ref) {
162 ended = end
163 var name = ref && ref.name
164 var value = ref && ref.value
165 if (first && usePlaceholder) {
166 first = false
167 if (end) {
168 // use placeholder data if there are no refs
169 value = '0000000000000000000000000000000000000000'
170 name = 'capabilities^{}'
171 }
172 name += '\0' + capabilities.join(' ')
173 } else if (end) {
174 return cb(true)
175 }
176 cb(null, value + ' ' + name)
177 })
178 }
179}
180
181// receive-pack: push from client
182function receivePack(read, objectSink, refSource, updateSink, options) {
183 var ended
184 var sendRefs = receivePackHeader([
185 'delete-refs',
186 ], refSource, true)
187
188 return pktLine.encode(
189 cat([
190 // send our refs
191 sendRefs,
192 pull.once(''),
193 function (abort, cb) {
194 if (abort) return
195 // receive their refs
196 var lines = pktLine.decode(read, options)
197 pull(
198 lines.updates,
199 onThroughEnd(updatesDone),
200 updateSink
201 )
202 function updatesDone(err) {
203 if (err) return cb(err)
204 pull(
205 lines.passthrough,
206 pack.decode(cb),
207 objectSink
208 )
209 }
210 },
211 pull.once('unpack ok')
212 ])
213 )
214}
215
216function prepend(data, read) {
217 var done
218 return function (end, cb) {
219 if (done) {
220 read(end, cb)
221 } else {
222 done = true
223 cb(null, data)
224 }
225 }
226}
227
228module.exports = function (opts) {
229 var ended
230 var objectSink = opts.objectSink || pull.error('Missing object sink')
231 var haveObject = opts.haveObject || function (hash, cb) { cb(false) }
232 var getObjects = opts.getObjects || function (id, cb) {
233 cb(null, 0, pull.empty())
234 }
235 var refSource = opts.refSource || pull.empty()
236 var updateSink = opts.updateSink || pull.drain()
237 var wantSink = opts.wantSink || pull.drain()
238
239 var options = {
240 verbosity: 1,
241 progress: false
242 }
243
244 function handleConnect(cmd, read) {
245 var args = util.split2(cmd)
246 switch (args[0]) {
247 case 'git-upload-pack':
248 return prepend('\n', uploadPack(read, haveObject, getObjects, refSource,
249 wantSink, options))
250 case 'git-receive-pack':
251 return prepend('\n', receivePack(read, objectSink, refSource,
252 updateSink, options))
253 default:
254 return pull.error(new Error('Unknown service ' + args[0]))
255 }
256 }
257
258 function handleCommand(line, read) {
259 var args = util.split2(line)
260 switch (args[0]) {
261 case 'capabilities':
262 return capabilitiesSource()
263 case 'list':
264 return listRefs(refSource)
265 case 'connect':
266 return handleConnect(args[1], read)
267 case 'option':
268 return optionSource(args[1], options)
269 default:
270 return pull.error(new Error('Unknown command ' + line))
271 }
272 }
273
274 return function (read) {
275 var b = buffered()
276 b(read)
277 var command
278
279 function getCommand(cb) {
280 b.lines(null, function next(end, line) {
281 if (ended = end)
282 return cb(end)
283
284 if (line == '')
285 return b.lines(null, next)
286
287 if (options.verbosity > 1)
288 console.error('command:', line)
289
290 var cmdSource = handleCommand(line, b.passthrough)
291 cb(null, cmdSource)
292 })
293 }
294
295 return function next(abort, cb) {
296 if (ended) return cb(ended)
297
298 if (!command) {
299 if (abort) return
300 getCommand(function (end, cmd) {
301 command = cmd
302 next(end, cb)
303 })
304 return
305 }
306
307 command(abort, function (err, data) {
308 if (err) {
309 command = null
310 if (err !== true)
311 cb(err, data)
312 else
313 next(abort, cb)
314 } else {
315 cb(null, data)
316 }
317 })
318 }
319 }
320}
321

Built with git-ssb-web