git ssb

0+

cel / pull-git-remote-helper



Tree: 9c5fc252c08c46239564a18250cc3d09354a620b

Files: 9c5fc252c08c46239564a18250cc3d09354a620b / index.js

8110 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, hasObject, 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 hasObject(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/*
136TODO: investigate capabilities
137report-status delete-refs side-band-64k quiet atomic ofs-delta
138*/
139
140// Get a line for each ref that we have. The first line also has capabilities.
141// Wrap with pktLine.encode.
142function receivePackHeader(capabilities, refSource, usePlaceholder) {
143 var first = true
144 var ended
145 return function (abort, cb) {
146 if (ended) return cb(true)
147 refSource(abort, function (end, ref) {
148 ended = end
149 var name = ref && ref.name
150 var value = ref && ref.value
151 if (first && usePlaceholder) {
152 first = false
153 if (end) {
154 // use placeholder data if there are no refs
155 value = '0000000000000000000000000000000000000000'
156 name = 'capabilities^{}'
157 }
158 name += '\0' + capabilities.join(' ')
159 } else if (end) {
160 return cb(true)
161 }
162 cb(null, value + ' ' + name)
163 })
164 }
165}
166
167// receive-pack: push from client
168function receivePack(read, objectSink, refSource, updateSink, options) {
169 var ended
170 var sendRefs = receivePackHeader([
171 'delete-refs',
172 ], refSource, true)
173
174 return pktLine.encode(
175 cat([
176 // send our refs
177 sendRefs,
178 pull.once(''),
179 function (abort, cb) {
180 if (abort) return
181 // receive their refs
182 var lines = pktLine.decode(read, options)
183 pull(
184 lines.updates,
185 pull.through(null, updatesDone),
186 updateSink
187 )
188 function updatesDone(err) {
189 if (err) return cb(err)
190 pull(
191 lines.passthrough,
192 pack.decode(cb),
193 objectSink
194 )
195 }
196 },
197 pull.once('unpack ok')
198 ])
199 )
200}
201
202function prepend(data, read) {
203 var done
204 return function (end, cb) {
205 if (done) {
206 read(end, cb)
207 } else {
208 done = true
209 cb(null, data)
210 }
211 }
212}
213
214module.exports = function (opts) {
215 var ended
216 var objectSink = opts.objectSink ||
217 function () { throw new Error('Missing object sink') }
218 var hasObject = opts.hasObject || function (hash, cb) { cb(false) }
219 var getObjects = opts.getObjects || function (id, cb) {
220 cb(null, 0, pull.empty())
221 }
222 var refSource = opts.refSource || pull.empty()
223 var updateSink = opts.updateSink || pull.drain()
224 var wantSink = opts.wantSink || pull.drain()
225
226 var options = {
227 verbosity: 1,
228 progress: false
229 }
230
231 function handleConnect(cmd, read) {
232 var args = util.split2(cmd)
233 switch (args[0]) {
234 case 'git-upload-pack':
235 return prepend('\n', uploadPack(read, hasObject, getObjects, refSource,
236 wantSink, options))
237 case 'git-receive-pack':
238 return prepend('\n', receivePack(read, objectSink, refSource,
239 updateSink, options))
240 default:
241 return pull.error(new Error('Unknown service ' + args[0]))
242 }
243 }
244
245 function handleCommand(line, read) {
246 var args = util.split2(line)
247 switch (args[0]) {
248 case 'capabilities':
249 return capabilitiesSource()
250 case 'list':
251 return listRefs(refSource)
252 case 'connect':
253 return handleConnect(args[1], read)
254 case 'option':
255 return optionSource(args[1], options)
256 default:
257 return pull.error(new Error('Unknown command ' + line))
258 }
259 }
260
261 return function (read) {
262 var b = buffered()
263 b(read)
264 var command
265
266 function getCommand(cb) {
267 b.lines(null, function next(end, line) {
268 if (ended = end)
269 return cb(end)
270
271 if (line == '')
272 return b.lines(null, next)
273
274 if (options.verbosity > 1)
275 console.error('command:', line)
276
277 var cmdSource = handleCommand(line, b.passthrough)
278 cb(null, cmdSource)
279 })
280 }
281
282 return function next(abort, cb) {
283 if (ended) return cb(ended)
284
285 if (!command) {
286 if (abort) return
287 getCommand(function (end, cmd) {
288 command = cmd
289 next(end, cb)
290 })
291 return
292 }
293
294 command(abort, function (err, data) {
295 if (err) {
296 command = null
297 if (err !== true)
298 cb(err, data)
299 else
300 next(abort, cb)
301 } else {
302 cb(null, data)
303 }
304 })
305 }
306 }
307}
308

Built with git-ssb-web