git ssb

0+

cel / pull-git-remote-helper



Tree: a53fb68ffb75c9fc0ff72ebbe56746e37f323cdc

Files: a53fb68ffb75c9fc0ff72ebbe56746e37f323cdc / index.js

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

Built with git-ssb-web