#!/usr/bin/env node var Chalk = require('chalk') var DHT = require('bittorrent-dht') var exec = require('child_process').exec var fs = require('fs') var hat = require('hat') var magnet = require('magnet-uri') var prettyjson = require('prettyjson') var spawn = require('child_process').spawn var Swarm = require('bittorrent-swarm') var ut_gittorrent = require('ut_gittorrent') var WebTorrent = require('webtorrent') var zeroFill = require('zero-fill') // BitTorrent client version string (used in peer ID). // Generated from package.json major and minor version. For example: // '0.16.1' -> '0016' // '1.2.5' -> '0102' // var VERSION = require('./package.json').version .match(/([0-9]+)/g).slice(0, 2).map(zeroFill(2)).join('') 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 bootstrap = ['dht.gittorrent.org:6881', 'core.gittorrent.org:6881'] var dht = new DHT({ bootstrap: bootstrap }) 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' + prettyjson.render(repos, {keysColor: 'yellow'})) 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 peerId = new Buffer('-WW' + VERSION + '-' + hat(48), 'utf8') var swarm = new Swarm(parsed.infoHash, peerId) 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: bootstrap }, tracker: false }) client.download(infoHash, function (torrent) { console.warn('Downloading git pack with infohash: ' + chalk.green(infoHash) + '\n') torrent.on('done', function (done) { var stream = torrent.files[0].createReadStream() var unpack = spawn('git', ['index-pack', '--stdin', '-v', '--fix-thin']) 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() }) }) }) }) }) }) }