Eduard Kuzmenko
3 years ago
4 changed files with 337 additions and 7 deletions
@ -0,0 +1,66 @@
@@ -0,0 +1,66 @@
|
||||
// @ts-check
|
||||
|
||||
const https = require('https'); |
||||
const http = require('http'); |
||||
const fs = require('fs'); |
||||
|
||||
const gitstatic = require("./git-serve"); |
||||
const express = require("express"); |
||||
|
||||
const repository = '.git'; |
||||
|
||||
const app = express(); |
||||
app.get(/^\/.+/, gitstatic.route().repository(repository)); |
||||
app.get(/\//, (req, res) => { |
||||
gitstatic.listAllCommits(repository, (err, commits) => { |
||||
console.log(err, commits); |
||||
|
||||
res.send( |
||||
commits.map((commit) => { |
||||
return `<a href="/${commit.sha}/public/index.html" target="_blank"><span style="font-family: monospace;">${commit.sha.slice(0, 7)} - ${commit.date.toISOString()}</span></a> - <a href="https://github.com/morethanwords/tweb/commit/${commit.sha}" target="_blank">${commit.subject}</a><br>`; |
||||
}).join('') |
||||
); |
||||
}); |
||||
}); |
||||
|
||||
const { networkInterfaces } = require('os'); |
||||
const nets = networkInterfaces(); |
||||
const results = {}; |
||||
|
||||
for(const name of Object.keys(nets)) { |
||||
for(const net of nets[name]) { |
||||
// Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses
|
||||
if(net.family === 'IPv4' && !net.internal) { |
||||
if(!results[name]) { |
||||
results[name] = []; |
||||
} |
||||
results[name].push(net.address); |
||||
} |
||||
} |
||||
} |
||||
|
||||
const useHttp = false; |
||||
const server = useHttp ? http : https; |
||||
let options = {}; |
||||
if(!useHttp) { |
||||
options.key = fs.readFileSync(__dirname + '/certs/server-key.pem'); |
||||
options.cert = fs.readFileSync(__dirname + '/certs/server-cert.pem'); |
||||
} |
||||
|
||||
const port = 3000; |
||||
const protocol = useHttp ? 'http' : 'https'; |
||||
console.log('Listening port:', port); |
||||
function createServer(host) { |
||||
server.createServer(options, app).listen(port, host, () => { |
||||
console.log('Host:', `${protocol}://${host || 'localhost'}:${port}/`); |
||||
}); |
||||
} |
||||
|
||||
for(const name in results) { |
||||
const ips = results[name]; |
||||
for(const ip of ips) { |
||||
createServer(ip); |
||||
} |
||||
} |
||||
|
||||
createServer(); |
@ -0,0 +1,246 @@
@@ -0,0 +1,246 @@
|
||||
// @ts-check
|
||||
|
||||
// Thanks to https://github.com/mbostock/git-static
|
||||
|
||||
var child = require("child_process"), |
||||
mime = require("mime"), |
||||
path = require("path"); |
||||
|
||||
var shaRe = /^[0-9a-f]{40}$/, |
||||
emailRe = /^<.*@.*>$/; |
||||
|
||||
function readBlob(repository, revision, file, callback) { |
||||
var git = child.spawn("git", ["cat-file", "blob", revision + ":" + file], {cwd: repository}), |
||||
data = [], |
||||
exit; |
||||
|
||||
git.stdout.on("data", function(chunk) { |
||||
data.push(chunk); |
||||
}); |
||||
|
||||
git.on("exit", function(code) { |
||||
exit = code; |
||||
}); |
||||
|
||||
git.on("close", function() { |
||||
if (exit > 0) return callback(error(exit)); |
||||
callback(null, Buffer.concat(data)); |
||||
}); |
||||
|
||||
git.stdin.end(); |
||||
} |
||||
|
||||
exports.readBlob = readBlob; |
||||
|
||||
exports.getBranches = function(repository, callback) { |
||||
child.exec("git branch -l", {cwd: repository}, function(error, stdout) { |
||||
if (error) return callback(error); |
||||
callback(null, stdout.split(/\n/).slice(0, -1).map(function(s) { return s.slice(2); })); |
||||
}); |
||||
}; |
||||
|
||||
exports.getSha = function(repository, revision, callback) { |
||||
child.exec("git rev-parse '" + revision.replace(/'/g, "'\''") + "'", {cwd: repository}, function(error, stdout) { |
||||
if (error) return callback(error); |
||||
callback(null, stdout.trim()); |
||||
}); |
||||
}; |
||||
|
||||
exports.getBranchCommits = function(repository, callback) { |
||||
child.exec("git for-each-ref refs/heads/ --sort=-authordate --format='%(objectname)\t%(refname:short)\t%(authordate:iso8601)\t%(authoremail)'", {cwd: repository}, function(error, stdout) { |
||||
if (error) return callback(error); |
||||
callback(null, stdout.split("\n").map(function(line) { |
||||
var fields = line.split("\t"), |
||||
sha = fields[0], |
||||
ref = fields[1], |
||||
date = new Date(fields[2]), |
||||
author = fields[3]; |
||||
if (!shaRe.test(sha) || !date || !emailRe.test(author)) return; |
||||
return { |
||||
sha: sha, |
||||
ref: ref, |
||||
date: date, |
||||
author: author.substring(1, author.length - 1) |
||||
}; |
||||
}).filter(function(commit) { |
||||
return commit; |
||||
})); |
||||
}); |
||||
}; |
||||
|
||||
exports.getCommit = function(repository, revision, callback) { |
||||
if (arguments.length < 3) callback = revision, revision = null; |
||||
child.exec(shaRe.test(revision) |
||||
? "git log -1 --date=iso " + revision + " --format='%H\n%ad'" |
||||
: "git for-each-ref --count 1 --sort=-authordate 'refs/heads/" + (revision ? revision.replace(/'/g, "'\''") : "") + "' --format='%(objectname)\n%(authordate:iso8601)'", {cwd: repository}, function(error, stdout) { |
||||
if (error) return callback(error); |
||||
var lines = stdout.split("\n"), |
||||
sha = lines[0], |
||||
date = new Date(lines[1]); |
||||
if (!shaRe.test(sha) || !date) return void callback(new Error("unable to get commit")); |
||||
callback(null, { |
||||
sha: sha, |
||||
date: date |
||||
}); |
||||
}); |
||||
}; |
||||
|
||||
exports.getRelatedCommits = function(repository, branch, sha, callback) { |
||||
if (!shaRe.test(sha)) return callback(new Error("invalid SHA: " + sha)); |
||||
child.exec("git log --format='%H' '" + branch.replace(/'/g, "'\''") + "' | grep -C1 " + sha, {cwd: repository}, function(error, stdout) { |
||||
if (error) return callback(error); |
||||
var shas = stdout.split(/\n/), |
||||
i = shas.indexOf(sha); |
||||
|
||||
callback(null, { |
||||
previous: shas[i + 1], |
||||
next: shas[i - 1] |
||||
}); |
||||
}); |
||||
}; |
||||
|
||||
exports.listCommits = function(repository, sha1, sha2, callback) { |
||||
if (!shaRe.test(sha1)) return callback(new Error("invalid SHA: " + sha1)); |
||||
if (!shaRe.test(sha2)) return callback(new Error("invalid SHA: " + sha2)); |
||||
child.exec("git log --format='%H\t%ad' " + sha1 + ".." + sha2, {cwd: repository}, function(error, stdout) { |
||||
if (error) return callback(error); |
||||
callback(null, stdout.split(/\n/).slice(0, -1).map(function(commit) { |
||||
var fields = commit.split(/\t/); |
||||
return { |
||||
sha: fields[0], |
||||
date: new Date(fields[1]) |
||||
}; |
||||
})); |
||||
}); |
||||
}; |
||||
|
||||
/** @type {(repository: string, callback: (err: Error, commits?: {sha: string, date: Date, author: string, subject: string}[]) => void) => void} */ |
||||
exports.listAllCommits = function(repository, callback) { |
||||
child.exec("git log --branches --format='%H\t%ad\t%an\t%s'", {cwd: repository}, function(error, stdout) { |
||||
if (error) return callback(error); |
||||
callback(null, stdout.split(/\n/).slice(0, -1).map(function(commit) { |
||||
var fields = commit.split(/\t/); |
||||
return { |
||||
sha: fields[0], |
||||
date: new Date(fields[1]), |
||||
author: fields[2], |
||||
subject: fields[3] |
||||
}; |
||||
})); |
||||
}); |
||||
}; |
||||
|
||||
exports.listTree = function(repository, revision, callback) { |
||||
child.exec("git ls-tree -r " + revision, {cwd: repository}, function(error, stdout) { |
||||
if (error) return callback(error); |
||||
callback(null, stdout.split(/\n/).slice(0, -1).map(function(commit) { |
||||
var fields = commit.split(/\t/); |
||||
return { |
||||
sha: fields[0].split(/\s/)[2], |
||||
name: fields[1] |
||||
}; |
||||
})); |
||||
}); |
||||
}; |
||||
|
||||
exports.route = function() { |
||||
var repository = defaultRepository, |
||||
revision = defaultRevision, |
||||
file = defaultFile, |
||||
type = defaultType; |
||||
|
||||
function route(request, response) { |
||||
var repository_, |
||||
revision_, |
||||
file_; |
||||
|
||||
// @ts-ignore
|
||||
if ((repository_ = repository(request.url)) == null |
||||
|| (revision_ = revision(request.url)) == null |
||||
|| (file_ = file(request.url)) == null) return serveNotFound(); |
||||
|
||||
readBlob(repository_, revision_, file_, function(error, data) { |
||||
if (error) return error.code === 128 ? serveNotFound() : serveError(error); |
||||
response.writeHead(200, { |
||||
"Content-Type": type(file_), |
||||
"Cache-Control": "public, max-age=300" |
||||
}); |
||||
response.end(data); |
||||
}); |
||||
|
||||
function serveError(error) { |
||||
response.writeHead(500, {"Content-Type": "text/plain"}); |
||||
response.end(error + ""); |
||||
} |
||||
|
||||
function serveNotFound() { |
||||
response.writeHead(404, {"Content-Type": "text/plain"}); |
||||
response.end("File not found."); |
||||
} |
||||
} |
||||
|
||||
route.repository = function(_) { |
||||
if (!arguments.length) return repository; |
||||
repository = functor(_); |
||||
return route; |
||||
}; |
||||
|
||||
route.sha = // sha is deprecated; use revision instead
|
||||
route.revision = function(_) { |
||||
if (!arguments.length) return revision; |
||||
revision = functor(_); |
||||
return route; |
||||
}; |
||||
|
||||
route.file = function(_) { |
||||
if (!arguments.length) return file; |
||||
file = functor(_); |
||||
return route; |
||||
}; |
||||
|
||||
route.type = function(_) { |
||||
if (!arguments.length) return type; |
||||
type = functor(_); |
||||
return route; |
||||
}; |
||||
|
||||
return route; |
||||
}; |
||||
|
||||
function functor(_) { |
||||
return typeof _ === "function" ? _ : function() { return _; }; |
||||
} |
||||
|
||||
function defaultRepository() { |
||||
return path.join(__dirname, "repository"); |
||||
} |
||||
|
||||
function defaultRevision(url) { |
||||
return decodeURIComponent(url.substring(1, url.indexOf("/", 1))); |
||||
} |
||||
|
||||
function defaultFile(url) { |
||||
url = url.substring(url.indexOf("/", 1) + 1); |
||||
const pathIdx = url.indexOf('?'); |
||||
if(pathIdx !== -1) { |
||||
url = url.slice(0, pathIdx); |
||||
} |
||||
|
||||
return decodeURIComponent(url); |
||||
} |
||||
|
||||
function defaultType(file) { |
||||
var type = mime.getType(file) || "text/plain"; |
||||
return text(type) ? type + "; charset=utf-8" : type; |
||||
} |
||||
|
||||
function text(type) { |
||||
return /^(text\/)|(application\/(javascript|json)|image\/svg$)/.test(type); |
||||
} |
||||
|
||||
function error(code) { |
||||
var e = new Error; |
||||
// @ts-ignore
|
||||
e.code = code; |
||||
return e; |
||||
} |
Loading…
Reference in new issue