From edf024a982457bc40862a66e4eda910ad4fff5b3 Mon Sep 17 00:00:00 2001 From: Chris Ball Date: Wed, 27 May 2015 16:54:10 -0400 Subject: Rename! gitswarm->gittorrent --- CONTRIBUTING.md | 2 +- README.md | 36 ++++----- git-remote-gitswarm | 132 --------------------------------- git-remote-gittorrent | 132 +++++++++++++++++++++++++++++++++ gitswarmd | 198 -------------------------------------------------- gittorrentd | 198 ++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 16 ++-- 7 files changed, 357 insertions(+), 357 deletions(-) delete mode 100755 git-remote-gitswarm create mode 100755 git-remote-gittorrent delete mode 100755 gitswarmd create mode 100755 gittorrentd diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d03620f..3c57709 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ -# GitSwarm is an OPEN Open Source Project +# GitTorrent is an OPEN Open Source Project Individuals making significant and valuable contributions are given commit-access to the project to contribute as they see fit. This project is more like an open wiki than a standard guarded open source project. diff --git a/README.md b/README.md index dee8beb..ec19272 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,26 @@ -# ![GitSwarm](http://gitswarm.org) +# ![GitTorrent](http://gittorrent.org) ### The Decentralization of GitHub -**GitSwarm** is a peer-to-peer network of Git repositories being shared over BitTorrent. +**GitTorrent** is a peer-to-peer network of Git repositories being shared over BitTorrent. To get started: ``` -npm install gitswarm +npm install gittorrent ``` After that, you can clone a repo with: ``` -git clone gitswarm://github.com/someuser/somerepo +git clone gittorrent://github.com/someuser/somerepo ``` Or serve your own repos with: ``` touch somerepo/.git/git-daemon-export-ok -gitswarmd +gittorrentd ``` # Design -The design of GitSwarm has five components: +The design of GitTorrent has five components: 1. A "git transport helper" that knows how to download and unpack git objects, and can be used by Git itself to perform a fetch/clone/push. 1. A distributed hash table that advertises which git commits a node is willing to serve. 1. A BitTorrent protocol extension that negotiates sending a packfile with needed objects to a peer @@ -31,28 +31,28 @@ The design of GitSwarm has five components: When Git is asked to perform a network operation with a URL that starts with e.g. `someprotocol://`, it calls `git-remote-someprotocol` and passes the URL as an argument. The remote helper binary is responsible for telling Git what capabilities it has, receiving commands from Git, and downloading objects into the `.git/` directory. -In GitSwarm's case, we could be asked for three styles of URL: +In GitTorrent's case, we could be asked for three styles of URL: * `gittorrent://some.git.hosting.site/somerepo` -- we connect over `git://` to find out what the latest commit is, then perform the download using that commit's sha1. This is kind of like a [CDN](CDN) for a git server; the actual download of objects happens via peers, but the lookup of which objects to downloads happens in the normal Git way. -* `gitswarm:///reponame` -- the sha1 corresponds to a gitswarm user's "mutable key" (hash of their public key) on our DHT -- we look up the key, receive JSON describing the user's repositories, and then perform the download using that commit's sha1. This doesn't use any resources outside of GitSwarm's network. -* `gitswarm://` -- the username is converted into a mutable key sha1 as above. The mapping from usernames to happens on Bitcoin's blockchain in OP_RETURN transaction. +* `gittorrent:///reponame` -- the sha1 corresponds to a gittorrent user's "mutable key" (hash of their public key) on our DHT -- we look up the key, receive JSON describing the user's repositories, and then perform the download using that commit's sha1. This doesn't use any resources outside of GitTorrent's network. +* `gittorrent://` -- the username is converted into a mutable key sha1 as above. The mapping from usernames to happens on Bitcoin's blockchain in OP_RETURN transaction. ## 2. Distributed hash table -The bootstrap server for this DHT runs at `core.gitswarm.org:6881`. It is a bittorrent mainline DHT. Git SHA1s are announced by nodes who can create packfiles for them. The clients on this DHT support dht-store (BEP 44) and use it to store mutable keys. +The bootstrap server for this DHT runs at `core.gittorrent.org:6881`. It is a bittorrent mainline DHT. Git SHA1s are announced by nodes who can create packfiles for them. The clients on this DHT support dht-store (BEP 44) and use it to store mutable keys. ## 3. Protocol extension Once a client has connected to another node, it sends a request for the SHA1 it's looking for as bencoded JSON: ``` -{gitswarm: ask: "sha1"} +{gittorrent: ask: "sha1"} ``` The node providing the packfile returns: ``` -{gitswarm: sendTorrent: "infoHash"} +{gittorrent: sendTorrent: "infoHash"} ``` ## 4. Key/value store -BEP 44 adds support for *mutable* and *immutable* keys. Immutable keys are addressed by the hash of their content, but mutable keys are addressed by the hash of a crypto keypair's public key. The owner of that keypair publishes signed updates to their public key's hash, with a sequence number to ensure the latest value is always propagated by peers. The hash of the public key here is a GitSwarm user ID, and the value associated with that key is a JSON object describing the user's repositories in a User Profile. +BEP 44 adds support for *mutable* and *immutable* keys. Immutable keys are addressed by the hash of their content, but mutable keys are addressed by the hash of a crypto keypair's public key. The owner of that keypair publishes signed updates to their public key's hash, with a sequence number to ensure the latest value is always propagated by peers. The hash of the public key here is a GitTorrent user ID, and the value associated with that key is a JSON object describing the user's repositories in a User Profile. ### User Profile JSON format * name (string) @@ -79,22 +79,22 @@ The idea of using OP_RETURN comes from telehash's blockname project, but while b ``` e.g. ``` -@gitswarm!cjb!81e24205d4bac8496d3e13282c90ead5045f09ea +@gittorrent!cjb!81e24205d4bac8496d3e13282c90ead5045f09ea ``` -Note that OP_RETURN transactions are limited to 80 bytes, which limits usernames in this scheme to 29 bytes. +Note that OP_RETURN transactions are limited to 80 bytes, which limits usernames in this scheme to 27 bytes. -As a convenience, this repository will include a database of registered usernames that is updated regularly. This doesn't make GitSwarm any more centralized -- you can run the same scripts yourself on a downloaded blockchain to make sure that this repository does not lie. This is just to save everyone from downloading tens of gigabytes of blockchain to process. +As a convenience, this repository will include a database of registered usernames that is updated regularly. This doesn't make GitTorrent any more centralized -- you can run the same scripts yourself on a downloaded blockchain to make sure that this repository does not lie. This is just to save everyone from downloading tens of gigabytes of blockchain to process. By the way, storing full Bitcoin history is not necessary. We just need to scan every transaction once, and can discard each transaction after we've scanned it once and determined whether it contained a valid username registration that we record. We just need to scan through all unprocessed blockchain transactions once, and record where we got up to so that we don't have to look at them again after that. ## Contributing -Please send pull requests! Even changes to the design of GitSwarm are welcome and encouraged; nothing is set in stone. +Please send pull requests! Even changes to the design of GitTorrent are welcome and encouraged; nothing is set in stone. #### JavaScript Standard Style -GitSwarm uses [JavaScript Standard Style](https://github.com/feross/standard). +GitTorrent uses [JavaScript Standard Style](https://github.com/feross/standard). [![js-standard-style](https://raw.githubusercontent.com/feross/standard/master/badge.png)](https://github.com/feross/standard) diff --git a/git-remote-gitswarm b/git-remote-gitswarm deleted file mode 100755 index d39c567..0000000 --- a/git-remote-gitswarm +++ /dev/null @@ -1,132 +0,0 @@ -#!/usr/bin/env node - -var Chalk = require('chalk') -var DHT = require('bittorrent-dht') -var exec = require('child_process').exec -var fs = require('fs') -var magnet = require('magnet-uri') -var spawn = require('child_process').spawn -var Swarm = require('bittorrent-swarm') -var ut_gitswarm = require('ut_gitswarm') -var WebTorrent = require('webtorrent') - -function die (error) { - console.error(error) - process.exit(1) -} - -// Gotta enable color manually because stdout isn't a tty. -var chalk = new Chalk.constructor({enabled: true}); - -var dht = new DHT({ - bootstrap: ['three.printf.net:6882'] -}) - -var url = process.argv[3] -var matches = url.match(/gitswarm:\/\/([a-f0-9]{40})\/(.*)/) -if (matches) { - var key = matches[1] - var reponame = matches[2] - dht.on('ready', function () { - var val = new Buffer(key, 'hex') - dht.get(val, function (err, res) { - if (err) { - return console.error(err) - } - var json = res.v.toString() - var repos = JSON.parse(json) - console.warn('\nMutable key ' + chalk.green(key) + ' returned:\n' + chalk.yellow(json)) - get_infohash(repos.repositories[reponame].master) - }) - }) -} else { - url = url.replace(/^gitswarm:/i, 'git:') - exec('git ls-remote ' + url + ' HEAD', function (err, stdout, stderr) { - if (err !== null) { - die(err) - } - var lines = stdout.split('\n') - if (lines.length !== 2) { - die("Didn't get back a single HEAD ref: " + lines) - } - var line = lines[0].split('\t') - var ref = line[0] - var head = line[1] - if (head !== 'HEAD') { - die("Couldn't parse the ref line: " + ref, head) - } - if (ref.length !== 40) { - die('Was expecting a 40-byte sha: ' + ref) - } - dht.on('ready', function () { - get_infohash(ref) - }) - }) -} - -function get_infohash (ref) { - // We use console.warn (stderr) because git ignores our writes to stdout. - console.warn('\nOkay, we want to get: ' + chalk.green(ref) + '\n') - - process.stdin.setEncoding('utf8') - process.stdin.on('readable', function () { - var chunk = process.stdin.read() - if (chunk === 'capabilities\n') { - process.stdout.write('fetch\n\n') - } - if (chunk === 'list\n') { - process.stdout.write(ref + ' refs/heads/master\n\n') - } - }) - process.stdout.on('error', function () { - // stdout was closed - }) - - var magnetUri = 'magnet:?xt=urn:btih:' + ref - var parsed = magnet(magnetUri) - dht.lookup(parsed.infoHash) - - dht.on('peer', function (addr, hash, from) { - swarm.addPeer(addr) - }) - - var swarm = new Swarm(parsed.infoHash, 'cafebabecafebabecafecafebabecafebabecafe') - swarm.on('wire', function (wire, addr) { - console.warn('Adding swarm peer: ' + chalk.green(addr) + '\n') - wire.use(ut_gitswarm()) - wire.ut_gitswarm.on('handshake', function () { - wire.ut_gitswarm.ask(parsed.infoHash) - }) - wire.ut_gitswarm.on('receivedTorrent', function (infoHash) { - var client = new WebTorrent({ - dht: { - bootstrap: ['three.printf.net:6882'] - }, - tracker: false - }) - client.download(infoHash, function (torrent) { - console.warn('Downloading git pack with infohash: ' + chalk.green(infoHash) + '\n') - torrent.on('done', function (done) { - var filename = torrent.storage.path + '/' + torrent.files[0].path - var unpack = spawn('git', ['index-pack', '--stdin', '-v', '--fix-thin']) - var stream = fs.createReadStream(filename) - stream.on('open', function () { - stream.pipe(unpack.stdin) - }) - unpack.stderr.pipe(process.stderr) - unpack.on('exit', function (code) { - var targetdir = process.env['GIT_DIR'] - var stream = fs.createWriteStream(targetdir + '/refs/heads/master') - stream.once('open', function (fd) { - stream.write(ref + '\n') - stream.end() - // These writes are actually necessary for git to finish checkout. - process.stdout.write('\n\n') - process.exit() - }) - }) - }) - }) - }) - }) -} diff --git a/git-remote-gittorrent b/git-remote-gittorrent new file mode 100755 index 0000000..a8bcb9d --- /dev/null +++ b/git-remote-gittorrent @@ -0,0 +1,132 @@ +#!/usr/bin/env node + +var Chalk = require('chalk') +var DHT = require('bittorrent-dht') +var exec = require('child_process').exec +var fs = require('fs') +var magnet = require('magnet-uri') +var spawn = require('child_process').spawn +var Swarm = require('bittorrent-swarm') +var ut_gittorrent = require('ut_gittorrent') +var WebTorrent = require('webtorrent') + +function die (error) { + console.error(error) + process.exit(1) +} + +// Gotta enable color manually because stdout isn't a tty. +var chalk = new Chalk.constructor({enabled: true}); + +var dht = new DHT({ + bootstrap: ['three.printf.net:6882'] +}) + +var url = process.argv[3] +var matches = url.match(/gittorrent:\/\/([a-f0-9]{40})\/(.*)/) +if (matches) { + var key = matches[1] + var reponame = matches[2] + dht.on('ready', function () { + var val = new Buffer(key, 'hex') + dht.get(val, function (err, res) { + if (err) { + return console.error(err) + } + var json = res.v.toString() + var repos = JSON.parse(json) + console.warn('\nMutable key ' + chalk.green(key) + ' returned:\n' + chalk.yellow(json)) + get_infohash(repos.repositories[reponame].master) + }) + }) +} else { + url = url.replace(/^gittorrent:/i, 'git:') + exec('git ls-remote ' + url + ' HEAD', function (err, stdout, stderr) { + if (err !== null) { + die(err) + } + var lines = stdout.split('\n') + if (lines.length !== 2) { + die("Didn't get back a single HEAD ref: " + lines) + } + var line = lines[0].split('\t') + var ref = line[0] + var head = line[1] + if (head !== 'HEAD') { + die("Couldn't parse the ref line: " + ref, head) + } + if (ref.length !== 40) { + die('Was expecting a 40-byte sha: ' + ref) + } + dht.on('ready', function () { + get_infohash(ref) + }) + }) +} + +function get_infohash (ref) { + // We use console.warn (stderr) because git ignores our writes to stdout. + console.warn('\nOkay, we want to get: ' + chalk.green(ref) + '\n') + + process.stdin.setEncoding('utf8') + process.stdin.on('readable', function () { + var chunk = process.stdin.read() + if (chunk === 'capabilities\n') { + process.stdout.write('fetch\n\n') + } + if (chunk === 'list\n') { + process.stdout.write(ref + ' refs/heads/master\n\n') + } + }) + process.stdout.on('error', function () { + // stdout was closed + }) + + var magnetUri = 'magnet:?xt=urn:btih:' + ref + var parsed = magnet(magnetUri) + dht.lookup(parsed.infoHash) + + dht.on('peer', function (addr, hash, from) { + swarm.addPeer(addr) + }) + + var swarm = new Swarm(parsed.infoHash, 'cafebabecafebabecafecafebabecafebabecafe') + swarm.on('wire', function (wire, addr) { + console.warn('Adding swarm peer: ' + chalk.green(addr) + '\n') + wire.use(ut_gittorrent()) + wire.ut_gittorrent.on('handshake', function () { + wire.ut_gittorrent.ask(parsed.infoHash) + }) + wire.ut_gittorrent.on('receivedTorrent', function (infoHash) { + var client = new WebTorrent({ + dht: { + bootstrap: ['three.printf.net:6882'] + }, + tracker: false + }) + client.download(infoHash, function (torrent) { + console.warn('Downloading git pack with infohash: ' + chalk.green(infoHash) + '\n') + torrent.on('done', function (done) { + var filename = torrent.storage.path + '/' + torrent.files[0].path + var unpack = spawn('git', ['index-pack', '--stdin', '-v', '--fix-thin']) + var stream = fs.createReadStream(filename) + stream.on('open', function () { + stream.pipe(unpack.stdin) + }) + unpack.stderr.pipe(process.stderr) + unpack.on('exit', function (code) { + var targetdir = process.env['GIT_DIR'] + var stream = fs.createWriteStream(targetdir + '/refs/heads/master') + stream.once('open', function (fd) { + stream.write(ref + '\n') + stream.end() + // These writes are actually necessary for git to finish checkout. + process.stdout.write('\n\n') + process.exit() + }) + }) + }) + }) + }) + }) +} diff --git a/gitswarmd b/gitswarmd deleted file mode 100755 index e7a6046..0000000 --- a/gitswarmd +++ /dev/null @@ -1,198 +0,0 @@ -#!/usr/bin/env node - -var DHT = require('bittorrent-dht') -var EC = require('elliptic').ec -var ed25519 = new EC('ed25519') -var exec = require('child_process').exec -var glob = require('glob') -var fs = require('fs') -var net = require('net') -var Protocol = require('bittorrent-protocol') -var spawn = require('child_process').spawn -var ut_gitswarm = require('ut_gitswarm') -var ut_metadata = require('ut_metadata') -var WebTorrent = require('webtorrent') - -function die (error) { - console.error(error) - process.exit(1) -} - -var dht = new DHT({ - bootstrap: ['three.printf.net:6881'] -}) -dht.listen(6882) - -var announcedRefs = { - master: {} -} -var userProfile = { - name: '', - email: '', - repositories: {} -} - -// These would be better as execSync, but node didn't get that until 0.12. -exec('git config user.name', function (error, stdout, stderr) { - userProfile.name = stdout.trim() - if (error !== null) { - die("Couldn't get git user name: " + error) - } -}) -exec('git config user.email', function (error, stdout, stderr) { - userProfile.email = stdout.trim() - if (error !== null) { - die("Couldn't get git user email: " + error) - } -}) -var key = create_or_read_keyfile() - -function create_or_read_keyfile () { - var filename = 'ed25519.key' - if (!fs.existsSync(filename)) { - var keypair = new EC('ed25519').genKeyPair() - fs.writeFileSync(filename, JSON.stringify({ - pub: keypair.getPublic('hex'), - priv: keypair.getPrivate('hex') - })) - } - - // Okay, now the file exists, whether created here or not. - var key = JSON.parse(fs.readFileSync('ed25519.key').toString()) - return ed25519.keyPair({ - priv: key.priv, - privEnc: 'hex', - pub: key.pub, - pubEnc: 'hex' - }) -} - -function bpad (n, buf) { - if (buf.length === n) return buf - if (buf.length < n) { - var b = new Buffer(n) - buf.copy(b, n - buf.length) - for (var i = 0; i < n - buf.length; i++) b[i] = 0 - return b - } -} - -dht.on('ready', function () { - // Spider all */.git dirs and announce all refs. - var repos = glob.sync('*/.git/git-daemon-export-ok') - var count = repos.length - repos.forEach(function (repo) { - console.log('in repo ' + repo) - repo = repo.replace(/git-daemon-export-ok$/, '') - console.log(repo) - var upload = spawn('git-upload-pack', ['--strict', repo]) - upload.stdout.on('data', function (line) { - var lines = line.toString().split('\n') - lines.forEach(function (line) { - var arr = line.toString().split(' ') - if (arr.length === 2) { - var sha = arr[0].toString() - // First four chars are git-upload-pack's length-of-line metadata. - sha = sha.substring(4) - var ref = arr[1].toString() - if (ref.search(/^refs\/heads\//) !== -1 || ref.search(/^refs\/remotes\//) !== -1) { - if (!announcedRefs.master[sha]) { - console.log('Announcing ' + sha + ' for ref ' + ref + ' on repo ' + repo) - announcedRefs.master[sha] = repo - var reponame = repo.replace(/\/.git\/$/, '') - userProfile.repositories[reponame] = {} - userProfile.repositories[reponame].master = sha - // Callback counting for repos - count-- - if (count <= 0) { - publish_mutable_key() - } - dht.announce(sha, 30000, function (err) { - if (err !== null) { - console.log('Announced ' + sha) - } - }) - } - } - } - }) - }) - upload.stdout.on('end', function () { - console.log('end') - }) - upload.on('exit', function (code) { - if (code !== 0) { - die('Failed: ' + code) - } - }) - }) - - function publish_mutable_key () { - var json = JSON.stringify(userProfile) - if (json.length > 950) { - console.error("Can't publish mutable key: doesn't fit in 950 bytes.") - return false - } - var value = new Buffer(json.length) - value.write(json) - var sig = key.sign(value) - var opts = { - k: bpad(32, Buffer(key.getPublic().x.toArray())), - seq: 0, - v: value, - sig: Buffer.concat([ - bpad(32, Buffer(sig.r.toArray())), - bpad(32, Buffer(sig.s.toArray())) - ])} - console.log(json) - dht.put(opts, function (errors, hash) { - console.error('errors=', errors) - console.log('hash=', hash.toString('hex')) - }) - } - - net.createServer(function (socket) { - var wire = new Protocol() - wire.use(ut_gitswarm()) - wire.use(ut_metadata()) - socket.pipe(wire).pipe(socket) - wire.on('handshake', function (infoHash, peerId) { - console.log('Received handshake for ' + infoHash) - wire.handshake(new Buffer(infoHash), new Buffer(peerId)) - }) - wire.ut_gitswarm.on('generatePack', function (sha) { - console.error('calling git pack-objects') - var filename = sha + '.pack' - var stream = fs.createWriteStream(filename) - if (!announcedRefs.master[sha]) { - console.error('Asked for an unknown sha!') - } - var directory = announcedRefs.master[sha] - var pack = spawn('git', ['pack-objects', '--revs', '--thin', '--stdout', '--delta-base-offset'], {cwd: directory}) - pack.on('close', function (code) { - if (code !== 0) { - console.error('git pack-objects process exited with code ' + code) - } else { - console.error('Finished writing ' + filename) - var webtorrent = new WebTorrent({ - dht: {bootstrap: ['three.printf.net:6882']}, - tracker: false - }) - webtorrent.seed(filename, function onTorrent (torrent) { - console.error(torrent.infoHash) - wire.ut_gitswarm.sendTorrent(torrent.infoHash) - }) - } - }) - pack.stdout.pipe(stream) - pack.stderr.on('data', function (data) { - console.error(data.toString()) - }) - pack.on('exit', function () { - console.log('exited') - }) - pack.stdin.write(sha + '\n') - pack.stdin.write('--not\n\n') - }) - }).listen(30000) -}) diff --git a/gittorrentd b/gittorrentd new file mode 100755 index 0000000..1e2352a --- /dev/null +++ b/gittorrentd @@ -0,0 +1,198 @@ +#!/usr/bin/env node + +var DHT = require('bittorrent-dht') +var EC = require('elliptic').ec +var ed25519 = new EC('ed25519') +var exec = require('child_process').exec +var glob = require('glob') +var fs = require('fs') +var net = require('net') +var Protocol = require('bittorrent-protocol') +var spawn = require('child_process').spawn +var ut_gittorrent = require('ut_gittorrent') +var ut_metadata = require('ut_metadata') +var WebTorrent = require('webtorrent') + +function die (error) { + console.error(error) + process.exit(1) +} + +var dht = new DHT({ + bootstrap: ['three.printf.net:6881'] +}) +dht.listen(6882) + +var announcedRefs = { + master: {} +} +var userProfile = { + name: '', + email: '', + repositories: {} +} + +// These would be better as execSync, but node didn't get that until 0.12. +exec('git config user.name', function (error, stdout, stderr) { + userProfile.name = stdout.trim() + if (error !== null) { + die("Couldn't get git user name: " + error) + } +}) +exec('git config user.email', function (error, stdout, stderr) { + userProfile.email = stdout.trim() + if (error !== null) { + die("Couldn't get git user email: " + error) + } +}) +var key = create_or_read_keyfile() + +function create_or_read_keyfile () { + var filename = 'ed25519.key' + if (!fs.existsSync(filename)) { + var keypair = new EC('ed25519').genKeyPair() + fs.writeFileSync(filename, JSON.stringify({ + pub: keypair.getPublic('hex'), + priv: keypair.getPrivate('hex') + })) + } + + // Okay, now the file exists, whether created here or not. + var key = JSON.parse(fs.readFileSync('ed25519.key').toString()) + return ed25519.keyPair({ + priv: key.priv, + privEnc: 'hex', + pub: key.pub, + pubEnc: 'hex' + }) +} + +function bpad (n, buf) { + if (buf.length === n) return buf + if (buf.length < n) { + var b = new Buffer(n) + buf.copy(b, n - buf.length) + for (var i = 0; i < n - buf.length; i++) b[i] = 0 + return b + } +} + +dht.on('ready', function () { + // Spider all */.git dirs and announce all refs. + var repos = glob.sync('*/.git/git-daemon-export-ok') + var count = repos.length + repos.forEach(function (repo) { + console.log('in repo ' + repo) + repo = repo.replace(/git-daemon-export-ok$/, '') + console.log(repo) + var upload = spawn('git-upload-pack', ['--strict', repo]) + upload.stdout.on('data', function (line) { + var lines = line.toString().split('\n') + lines.forEach(function (line) { + var arr = line.toString().split(' ') + if (arr.length === 2) { + var sha = arr[0].toString() + // First four chars are git-upload-pack's length-of-line metadata. + sha = sha.substring(4) + var ref = arr[1].toString() + if (ref.search(/^refs\/heads\//) !== -1 || ref.search(/^refs\/remotes\//) !== -1) { + if (!announcedRefs.master[sha]) { + console.log('Announcing ' + sha + ' for ref ' + ref + ' on repo ' + repo) + announcedRefs.master[sha] = repo + var reponame = repo.replace(/\/.git\/$/, '') + userProfile.repositories[reponame] = {} + userProfile.repositories[reponame].master = sha + // Callback counting for repos + count-- + if (count <= 0) { + publish_mutable_key() + } + dht.announce(sha, 30000, function (err) { + if (err !== null) { + console.log('Announced ' + sha) + } + }) + } + } + } + }) + }) + upload.stdout.on('end', function () { + console.log('end') + }) + upload.on('exit', function (code) { + if (code !== 0) { + die('Failed: ' + code) + } + }) + }) + + function publish_mutable_key () { + var json = JSON.stringify(userProfile) + if (json.length > 950) { + console.error("Can't publish mutable key: doesn't fit in 950 bytes.") + return false + } + var value = new Buffer(json.length) + value.write(json) + var sig = key.sign(value) + var opts = { + k: bpad(32, Buffer(key.getPublic().x.toArray())), + seq: 0, + v: value, + sig: Buffer.concat([ + bpad(32, Buffer(sig.r.toArray())), + bpad(32, Buffer(sig.s.toArray())) + ])} + console.log(json) + dht.put(opts, function (errors, hash) { + console.error('errors=', errors) + console.log('hash=', hash.toString('hex')) + }) + } + + net.createServer(function (socket) { + var wire = new Protocol() + wire.use(ut_gittorrent()) + wire.use(ut_metadata()) + socket.pipe(wire).pipe(socket) + wire.on('handshake', function (infoHash, peerId) { + console.log('Received handshake for ' + infoHash) + wire.handshake(new Buffer(infoHash), new Buffer(peerId)) + }) + wire.ut_gittorrent.on('generatePack', function (sha) { + console.error('calling git pack-objects') + var filename = sha + '.pack' + var stream = fs.createWriteStream(filename) + if (!announcedRefs.master[sha]) { + console.error('Asked for an unknown sha!') + } + var directory = announcedRefs.master[sha] + var pack = spawn('git', ['pack-objects', '--revs', '--thin', '--stdout', '--delta-base-offset'], {cwd: directory}) + pack.on('close', function (code) { + if (code !== 0) { + console.error('git pack-objects process exited with code ' + code) + } else { + console.error('Finished writing ' + filename) + var webtorrent = new WebTorrent({ + dht: {bootstrap: ['three.printf.net:6882']}, + tracker: false + }) + webtorrent.seed(filename, function onTorrent (torrent) { + console.error(torrent.infoHash) + wire.ut_gittorrent.sendTorrent(torrent.infoHash) + }) + } + }) + pack.stdout.pipe(stream) + pack.stderr.on('data', function (data) { + console.error(data.toString()) + }) + pack.on('exit', function () { + console.log('exited') + }) + pack.stdin.write(sha + '\n') + pack.stdin.write('--not\n\n') + }) + }).listen(30000) +}) diff --git a/package.json b/package.json index 0fb06e9..1e6b484 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "gitswarm", + "name": "gittorrent", "description": "Using BitTorrent to share git repositories", "version": "0.1.0", "author": { @@ -8,11 +8,11 @@ "url": "http://printf.net/" }, "bin": { - "git-remote-gitswarm": "./git-remote-gitswarm", - "gitswarmd": "./gitswarmd" + "git-remote-gittorrent": "./git-remote-gittorrent", + "gittorrentd": "./gittorrentd" }, "bugs": { - "url": "https://github.com/cjb/gitswarm/issues" + "url": "https://github.com/cjb/gittorrent/issues" }, "dependencies": { "bencode": "^0.7.0", @@ -24,7 +24,7 @@ "glob": "^5.0.6", "inherits": "^2.0.1", "magnet-uri": "^4.0.0", - "ut_gitswarm": "^0.1.0", + "ut_gittorrent": "^0.1.0", "ut_metadata": "^2.7.3", "webtorrent": "^0.32.0" }, @@ -38,13 +38,13 @@ "bittorrent", "bittorrent client", "git", - "gitswarm", + "gittorrent", "mad science" ], "license": "MIT", - "main": "git-remote-gitswarm", + "main": "git-remote-gittorrent", "repository": { "type": "git", - "url": "git://github.com/cjb/gitswarm.git" + "url": "git://github.com/cjb/gittorrent.git" } } -- cgit v1.2.3