var fs = require('fs') var http = require('http') var path = require('path') var url = require('url') var qs = require('querystring') var util = require('util') var ref = require('ssb-ref') var pull = require('pull-stream') var ssbGit = require('ssb-git-repo') var toPull = require('stream-to-pull-stream') var cat = require('pull-cat') var Repo = require('pull-git-repo') var ssbAbout = require('./about') var ssbVotes = require('./votes') var i18n = require('./i18n') var marked = require('ssb-marked') var asyncMemo = require('asyncmemo') var multicb = require('multicb') var schemas = require('ssb-msg-schemas') var Issues = require('ssb-issues') var PullRequests = require('ssb-pull-requests') var paramap = require('pull-paramap') var gitPack = require('pull-git-pack') var Mentions = require('ssb-mentions') var Highlight = require('highlight.js') var JsDiff = require('diff') var many = require('pull-many') var hlCssPath = path.resolve(require.resolve('highlight.js'), '../../styles') // render links to git objects and ssb objects var blockRenderer = new marked.Renderer() blockRenderer.urltransform = function (url) { if (ref.isLink(url)) return encodeLink(url) if (/^[0-9a-f]{40}$/.test(url) && this.options.repo) return encodeLink([this.options.repo.id, 'commit', url]) return url } blockRenderer.image = function (href, title, text) { href = href.replace(/^&/, '&') var url if (ref.isBlobId(href)) url = encodeLink(href) else if (this.options.repo && this.options.rev && this.options.path) url = path.join('/', encodeURIComponent(this.options.repo.id), 'raw', this.options.rev, this.options.path.join('/'), href) else return text return '' } function getExtension(filename) { return (/\.([^.]+)$/.exec(filename) || [,filename])[1] } function highlight(code, lang) { try { return lang ? Highlight.highlight(lang, code).value : Highlight.highlightAuto(code).value } catch(e) { if (/^Unknown language/.test(e.message)) return escapeHTML(code) throw e } } marked.setOptions({ gfm: true, mentions: true, tables: true, breaks: true, pedantic: false, sanitize: true, smartLists: true, smartypants: false, highlight: highlight, renderer: blockRenderer }) // hack to make git link mentions work var mdRules = new marked.InlineLexer(1, marked.defaults).rules mdRules.mention = /^(\s)?([@%&][A-Za-z0-9\._\-+=\/]*[A-Za-z0-9_\-+=\/]|[0-9a-f]{40})/ mdRules.text = /^[\s\S]+?(?=[\\' + text + '' } function linkify(text) { // regex is from ssb-ref return text.replace(/(@|%|&|&)[A-Za-z0-9\/+]{43}=\.[\w\d]+/g, function (str) { return '' + str + '' }) } function timestamp(time, req) { time = Number(time) var d = new Date(time) return '' + d.toLocaleString(req._locale) + '' } function pre(text) { return '
' + escapeHTML(text) + '' } function json(obj) { return linkify(pre(JSON.stringify(obj, null, 2))) } function escapeHTML(str) { return String(str) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') } function table(props) { return function (read) { return cat([ pull.once('
' + cell + ' | ' }).join('') + '
' + escapeHTML(err.stack) + '' } function renderTry(read) { var ended return function (end, cb) { if (ended) return cb(ended) read(end, function (err, data) { if (err === true) cb(true) else if (err) { ended = true cb(null, renderError(err, 'h3')) } else cb(null, data) }) } } function serveTemplate(req, title, code, read) { if (read === undefined) return serveTemplate.bind(this, req, title, code) var q = req._u.query.q && escapeHTML(req._u.query.q) || '' var app = 'git ssb' if (req._t) app = req._t(app) return cat([ pull.values([ [code || 200, { 'Content-Type': 'text/html' }], '', '
' + '' + i + ' | ' + '' + line + ' |
' + feedId + '
' + req._t('error.RepoNameNotFound') + '
', '' + escapeHTML(err.stack) + '' ])) } function renderRepoPage(req, repo, page, branch, body) { var gitUrl = 'ssb://' + repo.id var gitLink = '' var digsPath = [repo.id, 'digs'] var done = multicb({ pluck: 1, spread: true }) getRepoName(about, repo.feed, repo.id, done()) about.getName(repo.feed, done()) getVotes(repo.id, done()) if (repo.upstream) { getRepoName(about, repo.upstream.feed, repo.upstream.id, done()) about.getName(repo.upstream.feed, done()) } return readNext(function (cb) { done(function (err, repoName, authorName, votes, upstreamName, upstreamAuthorName) { if (err) return cb(null, serveError(req, err)) var upvoted = votes.upvoters[myId] > 0 var upstreamLink = !repo.upstream ? '' : link([repo.upstream]) cb(null, serveTemplate(req, repo.id)(cat([ pull.once( '
' + 'touch ' + req._t('initRepo.README') + '.md\n' + 'git init\n' + 'git add ' + req._t('initRepo.README') + '.md\n' + 'git commit -m "' + req._t('initRepo.InitialCommit') + '"\n' + 'git remote add origin ' + gitUrl + '\n' + 'git push -u origin master\n' + '
git remote add origin ' + gitUrl + '\n' + 'git push -u origin master' + '
' + req._t('NoReadme') + '
')) repo.getObjectFromAny(file.id, function (err, obj) { if (err) return cb(err) cb(null, cat([ pull.once('' + filename + ' |
---|
' + filename + ' |
---|
' + req._t('error.BlobNotFoundInRepo', { repo: link([repoId]) }) + '
' + '' + escapeHTML(err.stack) + '' )) } /* Raw blob */ function serveRepoRaw(req, repo, branch, path) { return readNext(function (cb) { repo.getFile(branch, path, function (err, object) { if (err) return cb(null, serveBuffer(404, req._t('error.BlobNotFound'))) var extension = getExtension(path[path.length-1]) var contentType = imgMimes[extension] cb(null, pull(object.read, serveRaw(object.length, contentType))) }) }) } function serveRaw(length, contentType) { var headers = { 'Content-Type': contentType || 'text/plain; charset=utf-8', 'Cache-Control': 'max-age=31536000' } if (length != null) headers['Content-Length'] = length return function (read) { return cat([pull.once([200, headers]), read]) } } function getBlob(req, key, cb) { ssb.blobs.want(key, function (err, got) { if (err) cb(err) else if (!got) cb(new Error(req._t('error.MissingBlob', {key: key}))) else cb(null, ssb.blobs.get(key)) }) } function serveBlob(req, key) { return readNext(function (cb) { getBlob(req, key, function (err, read) { if (err) cb(null, serveError(req, err)) else if (!read) cb(null, serve404(req)) else cb(null, serveRaw()(read)) }) }) } /* Digs */ function serveRepoDigs(req, repo) { return readNext(function (cb) { getVotes(repo.id, function (err, votes) { cb(null, renderRepoPage(req, repo, null, null, cat([ pull.once('
' + req._t('NoIssues') + '
') }) ])) } /* Pull Requests */ function serveRepoPullReqs(req, repo) { var count = 0 var state = req._u.query.state || 'open' return renderRepoPage(req, repo, 'pulls', null, cat([ pull.once( (isPublic ? '' : '') + '' + req._t('NoPullRequests') + '
') }) ])) } /* New Issue */ function serveRepoNewIssue(req, repo, issueId, path) { return renderRepoPage(req, repo, 'issues', null, pull.once( '' + issue.id + '
' +
'' + escapeHTML(c.title) + '
'
})) + ' · ' + msgTimeLink +
(msg.key == postId ? '' + pr.id + '
'),
readOnce(function (cb) {
var done = multicb({ pluck: 1, spread: true })
var gotHeadRepo = done()
about.getName(pr.author, done())
var sameRepo = (pr.headRepo == pr.baseRepo)
getRepo(pr.headRepo, function (err, headRepo) {
if (err) return cb(err)
getRepoName(about, headRepo.feed, headRepo.id, done())
about.getName(headRepo.feed, done())
gotHeadRepo(null, Repo(headRepo))
})
done(function (err, _headRepo, issueAuthorName,
headRepoName, headRepoAuthorName) {
if (err) return cb(err)
headRepo = _headRepo
authorLink = link([pr.author], issueAuthorName)
var repoLink = link([pr.headRepo], headRepoName)
var headRepoAuthorLink = link([headRepo.feed], headRepoAuthorName)
var headRepoLink = link([headRepo.id], headRepoName)
var headBranchLink = link([headRepo.id, 'tree', pr.headBranch])
var baseBranchLink = link([repo.id, 'tree', pr.baseBranch])
cb(null, '' + baseBranchLink + '
',
head: (sameRepo ?
'' + headBranchLink + '
' :
'' +
headRepoAuthorLink + ' / ' +
headRepoLink + ' / ' +
headBranchLink + '
')
}) + '' + req._t('mergeInstructions.CheckOut') + '
' + '' + 'git fetch ssb://' + escapeHTML(pr.headRepo) + ' ' + escapeHTML(pr.headBranch) + '\n' + 'git checkout -b ' + escapeHTML(pr.headBranch) + ' FETCH_HEAD' + '' + '
' + req._t('mergeInstructions.MergeAndPush') + '
' + '' + 'git checkout ' + escapeHTML(pr.baseBranch) + '\n' + 'git merge ' + escapeHTML(pr.headBranch) + '\n' + 'git push ssb ' + escapeHTML(pr.baseBranch) + '' + '
' + pr.headBranch + '
'
}) + ' · ' + msgLink +
'' + revLink + '
'
}) + ' · ' + msgLink +
'' + commit.author.date.toLocaleDateString(req._locale) + ' | ||
---|---|---|
' + escapeHTML(commit.author.name) + ' | ' + '' + link(commitPath, commit.title) + ' | ' + '' + link(commitPath, commitIdShort, true) + ' | ' + '