git ssb

0+

cel / pull-git-remote-helper



Commit cc32919e93ce91c6d66985c7ca2b78da5ea7f92d

Initial commit

Charles Lehner committed on 2/8/2016, 4:36:41 AM

Files changed

LICENSEadded
README.mdadded
index.jsadded
package.jsonadded
test/git-remote-test.jsadded
test/run.jsadded
LICENSEView
@@ -1,0 +1,9 @@
1+Fair License
2+
3+Copyright (c) 2016 Charles Lehner
4+
5+Usage of the works is permitted provided that this instrument is
6+retained with the works, so that any entity that uses the works is
7+notified of this instrument.
8+
9+DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY.
README.mdView
@@ -1,0 +1,7 @@
1+# pull-git-remote-helper
2+
3+Make a [git remote helper](http://git-scm.com/docs/git-remote-helpers).
4+
5+Use [pull-stream](https://github.com/dominictarr/pull-stream)s.
6+
7+Fair License
index.jsView
@@ -1,0 +1,333 @@
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+ cb('upload pack not implemented')
93+ }
94+ // throw new Error('upload pack')
95+}
96+
97+function getRefs() {
98+ return pull.values([
99+ {
100+ hash: '78beaedba9878623cea3862cf18e098cfb901e10',
101+ name: 'refs/heads/master'
102+ },
103+ {
104+ hash: '78beaedba9878623cea3862cf18e098cfb901e10',
105+ name: 'refs/remotes/cel/master'
106+ }
107+ ])
108+ /*
109+ return function (err, cb) {
110+ if (err === true) return
111+ if (err) throw err
112+ // TODO
113+ cb(true)
114+ }
115+ */
116+}
117+
118+function packLineEncode(read) {
119+ var ended
120+ return function (end, cb) {
121+ if (ended) return cb(ended)
122+ read(end, function (end, data) {
123+ if (ended = end) {
124+ cb(end)
125+ } else {
126+ var len = data ? data.length + 5 : 0
127+ cb(end, ('000' + len.toString(16)).substr(-4) + data + '\n')
128+ }
129+ })
130+ }
131+}
132+
133+function packLineDecode(read) {
134+ var b = buffered(read)
135+ var readPrefix = b.chunks(4)
136+ var ended
137+
138+ b.packLines = function (abort, cb) {
139+ readPrefix(abort, function (end, buf) {
140+ if (ended = end) return cb(end)
141+ var len = parseInt(buf, 16)
142+ b.chunks(len)(null, function (end, buf) {
143+ if (ended = end) return cb(end)
144+ console.error('refline', buf)
145+ cb(end, buf)
146+ })
147+ })
148+ }
149+
150+ return b
151+}
152+
153+/*
154+TODO: investigate capabilities
155+report-status delete-refs side-band-64k quiet atomic ofs-delta
156+*/
157+
158+// Get a line for each ref that we have. The first line also has capabilities.
159+// Wrap with packLineEncode.
160+function receivePackHeader(capabilities) {
161+ var readRef = getRefs()
162+ var first = true
163+ var ended
164+ return function (abort, cb) {
165+ if (ended) return cb(true)
166+ readRef(abort, function (end, ref) {
167+ ended = end
168+ var name = ref && ref.name
169+ var hash = ref && ref.hash
170+ if (first) {
171+ first = false
172+ if (end) {
173+ // use placeholder data if there are no refs
174+ hash = '0000000000000000000000000000000000000000'
175+ name = 'capabilities^{}'
176+ }
177+ name += '\0' + capabilities.join(' ')
178+ } else if (end) {
179+ return cb(true)
180+ }
181+ cb(null, hash + ' ' + name)
182+ })
183+ }
184+}
185+
186+function receiveActualPack(read, objectSink, onEnd) {
187+ var objects = pushable()
188+ packCodec.decodePack(function (obj) {
189+ if (obj) pushable.push(obj)
190+ else pushable.end()
191+ })
192+ objectSink(objects)
193+}
194+
195+function receivePack(read, objectSink) {
196+ var ended
197+ var sendRefs = receivePackHeader([])
198+
199+ function receiveRefs(readLine, cb) {
200+ var refs = []
201+ readLine(null, function next(end, line) {
202+ if (end === true)
203+ cb(new Error('refs line ended early'))
204+ else if (end)
205+ cb(end)
206+ else if (line === '')
207+ cb(null, refs)
208+ else {
209+ var args = split2(line)
210+ refs.push({
211+ hash: args[0],
212+ name: args[1]
213+ })
214+ readLine(null, next)
215+ }
216+ })
217+ }
218+
219+ return packLineEncode(
220+ cat([
221+ // send our refs
222+ sendRefs,
223+ pull.once(''),
224+ function (abort, cb) {
225+ if (abort) return
226+ // receive their refs
227+ var lines = packLineDecode(read)
228+ receiveRefs(lines.packLines, function (err, refs) {
229+ if (refs)
230+ console.error('refs', refs, err)
231+ if (err) return cb(err)
232+ // receive the pack
233+ receiveActualPack(lines.passthrough, objectSink, function (err) {
234+ if (err) return cb(err)
235+ cb(true)
236+ })
237+ })
238+ },
239+ pull.once('unpack ok')
240+ ])
241+ )
242+}
243+
244+function prepend(data, read) {
245+ var done
246+ return function (end, cb) {
247+ if (done) {
248+ read(end, cb)
249+ } else {
250+ done = true
251+ cb(null, data)
252+ }
253+ }
254+}
255+
256+module.exports = function (opts) {
257+ var ended
258+ var prefix = opts.prefix
259+ var objectSink = opts.objectSink
260+
261+ function handleConnect(cmd, read) {
262+ var args = split2(cmd)
263+ switch (args[0]) {
264+ case 'git-upload-pack':
265+ return prepend('\n', uploadPack(read))
266+ case 'git-receive-pack':
267+ return prepend('\n', receivePack(read), objectSink)
268+ default:
269+ return pull.error(new Error('Unknown service ' + args[0]))
270+ }
271+ }
272+
273+ function handleCommand(line, read) {
274+ var args = split2(line)
275+ switch (args[0]) {
276+ case 'capabilities':
277+ return capabilitiesSource(prefix)
278+ case 'list':
279+ return listSource()
280+ case 'connect':
281+ return handleConnect(args[1], read)
282+ case 'option':
283+ return optionSource(args[1])
284+ default:
285+ return pull.error(new Error('Unknown command ' + args[0]))
286+ }
287+ }
288+
289+ return function (read) {
290+ var b = buffered()
291+ b(read)
292+ var command
293+
294+ function getCommand(cb) {
295+ b.lines(null, function (end, line) {
296+ if (ended = end)
297+ return cb(end)
298+
299+ if (options.verbosity > 1)
300+ console.error('command:', line)
301+
302+ var cmdSource = handleCommand(line, b.passthrough)
303+ cb(null, cmdSource)
304+ })
305+ }
306+
307+ return function next(abort, cb) {
308+ if (ended) return cb(ended)
309+
310+ if (!command) {
311+ if (abort) return
312+ getCommand(function (end, cmd) {
313+ command = cmd
314+ next(end, cb)
315+ })
316+ return
317+ }
318+
319+ command(abort, function (err, data) {
320+ if (err) {
321+ command = null
322+ if (err !== true)
323+ cb(err, data)
324+ else
325+ next(abort, cb)
326+ } else {
327+ cb(null, data)
328+ }
329+ })
330+ }
331+ }
332+}
333+
package.jsonView
@@ -1,0 +1,35 @@
1+{
2+ "name": "pull-git-remote-helper",
3+ "version": "0.0.0",
4+ "description": "use pull-streams to make gitremote-helpers(1)",
5+ "main": "index.js",
6+ "scripts": {
7+ "test": "node test/run.js"
8+ },
9+ "repository": {
10+ "type": "git",
11+ "url": "git://github.com/clehner/pull-git-remote-helper"
12+ },
13+ "keywords": [
14+ "pull-stream",
15+ "git",
16+ "git-remote-helper"
17+ ],
18+ "author": "Charles Lehner (http://celehner.com/)",
19+ "license": "ISC",
20+ "bugs": {
21+ "url": "https://github.com/clehner/pull-git-remote-helper/issues"
22+ },
23+ "dependencies": {
24+ "js-git": "^0.7.7",
25+ "pull-buffered": "^0.3.0",
26+ "pull-cat": "^1.1.8",
27+ "pull-pushable": "^2.0.0",
28+ "pull-stream": "^3.1.0",
29+ "stream-to-pull-stream": "^1.6.6"
30+ },
31+ "devDependencies": {
32+ "pull-stream": "^3.1.0",
33+ "tape": "^4.4.0"
34+ }
35+}
test/git-remote-test.jsView
@@ -1,0 +1,18 @@
1+#!/usr/bin/env node
2+
3+var toPull = require('stream-to-pull-stream')
4+var pull = require('pull-stream')
5+
6+pull(
7+ toPull(process.stdin),
8+ require('../')({
9+ prefix: 'foo',
10+ objectSink: pull.drain(function (obj) {
11+ console.error('obj', obj)
12+ })
13+ }),
14+ toPull(process.stdout, function (err) {
15+ if (err)
16+ throw err
17+ })
18+)
test/run.jsView
@@ -1,0 +1,20 @@
1+var spawn = require('child_process').spawn
2+var tape = require('tape')
3+
4+var env = Object.create(process.env)
5+env.PATH = 'test:' + env.PATH
6+var remote = 'test.js://foo'
7+
8+function git(args, cb) {
9+ spawn('git', args, {
10+ env: env,
11+ stdio: ['ignore', 'ignore', 'inherit']
12+ }).on('close', cb)
13+}
14+
15+tape('push to the remote', function (t) {
16+ git(['push', remote], function (code) {
17+ t.equals(code, 0, 'exit status')
18+ t.end()
19+ })
20+})

Built with git-ssb-web