index.jsView |
---|
15 | 15 … | var asyncMemo = require('asyncmemo') |
16 | 16 … | var multicb = require('multicb') |
17 | 17 … | var schemas = require('ssb-msg-schemas') |
18 | 18 … | var Issues = require('ssb-issues') |
| 19 … | +var PullRequests = require('ssb-pull-requests') |
19 | 20 … | var paramap = require('pull-paramap') |
20 | 21 … | var gitPack = require('pull-git-pack') |
21 | 22 … | var Mentions = require('ssb-mentions') |
22 | 23 … | var Highlight = require('highlight.js') |
23 | 24 … | var JsDiff = require('diff') |
| 25 … | +var many = require('pull-many') |
24 | 26 … | |
25 | 27 … | var hlCssPath = path.resolve(require.resolve('highlight.js'), '../../styles') |
26 | 28 … | |
27 | 29 … | |
211 | 213 … | '<div class="preview-text tab2" id="preview-tab"></div>' + |
212 | 214 … | '<script>' + issueCommentScript + '</script>' |
213 | 215 … | } |
214 | 216 … | |
| 217 … | +function hiddenInputs(values) { |
| 218 … | + return Object.keys(values).map(function (key) { |
| 219 … | + return '<input type="hidden"' + |
| 220 … | + ' name="' + escapeHTML(key) + '"' + |
| 221 … | + ' value="' + escapeHTML(values[key]) + '"/>' |
| 222 … | + }).join('') |
| 223 … | +} |
| 224 … | + |
215 | 225 … | function readNext(fn) { |
216 | 226 … | var next |
217 | 227 … | return function (end, cb) { |
218 | 228 … | if (next) return next(end, cb) |
343 | 353 … | } |
344 | 354 … | }, cb) |
345 | 355 … | } |
346 | 356 … | |
| 357 … | +function getRepoFullName(about, author, repoId, cb) { |
| 358 … | + var done = multicb({ pluck: 1, spread: true }) |
| 359 … | + getRepoName(about, author, repoId, done()) |
| 360 … | + about.getName(author, done()) |
| 361 … | + done(cb) |
| 362 … | +} |
| 363 … | + |
347 | 364 … | function addAuthorName(about) { |
348 | 365 … | return paramap(function (msg, cb) { |
349 | | - about.getName(msg.value.author, function (err, authorName) { |
| 366 … | + var author = msg && msg.value && msg.value.author |
| 367 … | + if (!author) return cb(null, msg) |
| 368 … | + about.getName(author, function (err, authorName) { |
350 | 369 … | msg.authorName = authorName |
351 | 370 … | cb(err, msg) |
352 | 371 … | }) |
353 | 372 … | }, 8) |
409 | 428 … | |
410 | 429 … | var msgTypes = { |
411 | 430 … | 'git-repo': true, |
412 | 431 … | 'git-update': true, |
413 | | - 'issue': true |
| 432 … | + 'issue': true, |
| 433 … | + 'pull-request': true |
414 | 434 … | } |
415 | 435 … | |
416 | 436 … | var imgMimes = { |
417 | 437 … | png: 'image/png', |
451 | 471 … | }) |
452 | 472 … | getVotes = ssbVotes(ssb) |
453 | 473 … | getMsg = asyncMemo(ssb.get) |
454 | 474 … | issues = Issues.init(ssb) |
| 475 … | + pullReqs = PullRequests.init(ssb) |
455 | 476 … | }) |
456 | 477 … | } |
457 | 478 … | } |
458 | 479 … | |
572 | 593 … | if (err) return cb(null, serveError(err)) |
573 | 594 … | cb(null, serveRedirect(encodeLink(msg.key))) |
574 | 595 … | }) |
575 | 596 … | |
| 597 … | + case 'new-pull': |
| 598 … | + var msg = PullRequests.schemas.new(dir, data.branch, |
| 599 … | + data.head_repo, data.head_branch, data.title, data.text) |
| 600 … | + var mentions = Mentions(data.text) |
| 601 … | + if (mentions.length) |
| 602 … | + msg.mentions = mentions |
| 603 … | + return ssb.publish(msg, function (err, msg) { |
| 604 … | + if (err) return cb(null, serveError(err)) |
| 605 … | + cb(null, serveRedirect(encodeLink(msg.key))) |
| 606 … | + }) |
| 607 … | + |
576 | 608 … | case 'markdown': |
577 | 609 … | return cb(null, serveMarkdown(data.text, {id: data.repo})) |
578 | 610 … | |
579 | 611 … | default: |
817 | 849 … | cb(null, '<section class="collapse">' + msgLink + '<br>' + |
818 | 850 … | authorLink + ' pushed to ' + repoLink + '</section>') |
819 | 851 … | }) |
820 | 852 … | case 'issue': |
| 853 … | + case 'pull-request': |
821 | 854 … | var issueLink = link([msg.key], c.title) |
822 | 855 … | return getRepoName(about, author, c.project, function (err, repoName) { |
823 | 856 … | if (err) return cb(err) |
824 | 857 … | var repoLink = link([c.project], repoName) |
825 | 858 … | cb(null, '<section class="collapse">' + msgLink + '<br>' + |
826 | | - authorLink + ' opened issue ' + issueLink + |
| 859 … | + authorLink + ' opened ' + c.type + ' ' + issueLink + |
827 | 860 … | ' on ' + repoLink + '</section>') |
828 | 861 … | }) |
829 | 862 … | } |
830 | 863 … | } |
912 | 945 … | if (err) return cb(null, serveError(err)) |
913 | 946 … | cb(null, serveRepoIssue(req, Repo(repo), issue, path)) |
914 | 947 … | }) |
915 | 948 … | }) |
| 949 … | + case 'pull-request': |
| 950 … | + return getRepo(c.repo, function (err, repo) { |
| 951 … | + if (err) return cb(null, serveRepoNotFound(c.project, err)) |
| 952 … | + pullReqs.get(id, function (err, pr) { |
| 953 … | + if (err) return cb(null, serveError(err)) |
| 954 … | + cb(null, serveRepoPullReq(req, Repo(repo), pr, path)) |
| 955 … | + }) |
| 956 … | + }) |
| 957 … | + case 'issue-edit': |
| 958 … | + if (ref.isMsgId(c.issue)) { |
| 959 … | + return pullReqs.get(c.issue, function (err, issue) { |
| 960 … | + if (err) return cb(err) |
| 961 … | + var serve = issue.msg.value.content.type == 'pull-request' ? |
| 962 … | + serveRepoPullReq : serveRepoIssue |
| 963 … | + getRepo(issue.project, function (err, repo) { |
| 964 … | + if (err) { |
| 965 … | + if (!repo) return cb(null, serveRepoNotFound(c.repo, err)) |
| 966 … | + return cb(null, serveError(err)) |
| 967 … | + } |
| 968 … | + cb(null, serve(req, Repo(repo), issue, path, id)) |
| 969 … | + }) |
| 970 … | + }) |
| 971 … | + } |
| 972 … | + |
916 | 973 … | case 'post': |
917 | 974 … | if (ref.isMsgId(c.issue) && ref.isMsgId(c.repo)) { |
918 | 975 … | var done = multicb({ pluck: 1, spread: true }) |
919 | 976 … | getRepo(c.repo, done()) |
920 | | - issues.get(c.issue, done()) |
| 977 … | + pullReqs.get(c.issue, done()) |
921 | 978 … | return done(function (err, repo, issue) { |
922 | 979 … | if (err) { |
923 | 980 … | if (!repo) return cb(null, serveRepoNotFound(c.repo, err)) |
924 | 981 … | return cb(null, serveError(err)) |
925 | 982 … | } |
926 | | - cb(null, serveRepoIssue(req, Repo(repo), issue, path, id)) |
| 983 … | + var serve = issue.msg.value.content.type == 'pull-request' ? |
| 984 … | + serveRepoPullReq : serveRepoIssue |
| 985 … | + cb(null, serve(req, Repo(repo), issue, path, id)) |
927 | 986 … | }) |
928 | 987 … | } |
929 | 988 … | |
930 | 989 … | default: |
1008 | 1067 … | if (filePath.length == 0) |
1009 | 1068 … | return serveRepoNewIssue(repo) |
1010 | 1069 … | break |
1011 | 1070 … | default: |
1012 | | - return serveRepoIssues(req, repo, branch, filePath) |
| 1071 … | + return serveRepoIssues(req, repo, filePath) |
1013 | 1072 … | } |
| 1073 … | + case 'pulls': |
| 1074 … | + return serveRepoPullReqs(req, repo) |
| 1075 … | + case 'compare': |
| 1076 … | + return serveRepoCompare(req, repo) |
| 1077 … | + case 'comparing': |
| 1078 … | + return serveRepoComparing(req, repo) |
1014 | 1079 … | default: |
1015 | 1080 … | return serve404(req) |
1016 | 1081 … | } |
1017 | 1082 … | } |
1026 | 1091 … | |
1027 | 1092 … | function renderRepoPage(repo, page, branch, body) { |
1028 | 1093 … | var gitUrl = 'ssb://' + repo.id |
1029 | 1094 … | var gitLink = '<input class="clone-url" readonly="readonly" ' + |
1030 | | - 'value="' + gitUrl + '" size="61" ' + |
| 1095 … | + 'value="' + gitUrl + '" size="45" ' + |
1031 | 1096 … | 'onclick="this.select()"/>' |
1032 | 1097 … | var digsPath = [repo.id, 'digs'] |
1033 | 1098 … | |
1034 | 1099 … | var done = multicb({ pluck: 1, spread: true }) |
1069 | 1134 … | link([repo.id, 'forks'], '+', false, ' title="Forks"') + |
1070 | 1135 … | '</form>' + |
1071 | 1136 … | renderNameForm(!isPublic, repo.id, repoName, 'repo-name', null, |
1072 | 1137 … | 'Rename the repo', |
1073 | | - '<h2>' + link([repo.feed], authorName) + ' / ' + |
| 1138 … | + '<h2 class="bgslash">' + link([repo.feed], authorName) + ' / ' + |
1074 | 1139 … | link([repo.id], repoName) + '</h2>') + |
1075 | 1140 … | '</div>' + |
1076 | 1141 … | (repo.upstream ? |
1077 | 1142 … | '<small>forked from ' + |
1081 | 1146 … | nav([ |
1082 | 1147 … | [[repo.id], 'Code', 'code'], |
1083 | 1148 … | [[repo.id, 'activity'], 'Activity', 'activity'], |
1084 | 1149 … | [[repo.id, 'commits', branch || ''], 'Commits', 'commits'], |
1085 | | - [[repo.id, 'issues'], 'Issues', 'issues'] |
| 1150 … | + [[repo.id, 'issues'], 'Issues', 'issues'], |
| 1151 … | + [[repo.id, 'pulls'], 'Pull Requests', 'pulls'] |
1086 | 1152 … | ], page, gitLink)), |
1087 | 1153 … | body |
1088 | 1154 … | ]))) |
1089 | 1155 … | }) |
1136 | 1202 … | return renderRepoPage(repo, 'activity', branch, cat([ |
1137 | 1203 … | pull.once('<h3>Activity</h3>'), |
1138 | 1204 … | pull( |
1139 | 1205 … | ssb.links({ |
1140 | | - type: 'git-update', |
1141 | 1206 … | dest: repo.id, |
1142 | 1207 … | source: repo.feed, |
1143 | 1208 … | rel: 'repo', |
1144 | 1209 … | values: true, |
1164 | 1229 … | |
1165 | 1230 … | function renderRepoUpdate(repo, msg, full) { |
1166 | 1231 … | var c = msg.value.content |
1167 | 1232 … | |
| 1233 … | + if (c.type != 'git-update') { |
| 1234 … | + return '' |
| 1235 … | + |
| 1236 … | + |
| 1237 … | + } |
| 1238 … | + |
1168 | 1239 … | var refs = c.refs ? Object.keys(c.refs).map(function (ref) { |
1169 | 1240 … | return {name: ref, value: c.refs[ref]} |
1170 | 1241 … | }) : [] |
1171 | 1242 … | var numObjects = c.objects ? Object.keys(c.objects).length : 0 |
1222 | 1293 … | (commit.separateAuthor ? '<br>' + escapeHTML(commit.committer.name) + ' committed on ' + commit.committer.date.toLocaleString() : "") + |
1223 | 1294 … | '</section>' |
1224 | 1295 … | } |
1225 | 1296 … | |
1226 | | - |
| 1297 … | + |
1227 | 1298 … | |
| 1299 … | + function formatRevOptions(currentName) { |
| 1300 … | + return function (name) { |
| 1301 … | + var htmlName = escapeHTML(name) |
| 1302 … | + return '<option value="' + htmlName + '"' + |
| 1303 … | + (name == currentName ? ' selected="selected"' : '') + |
| 1304 … | + '>' + htmlName + '</option>' |
| 1305 … | + } |
| 1306 … | + } |
| 1307 … | + |
1228 | 1308 … | function revMenu(repo, currentName) { |
1229 | 1309 … | return readOnce(function (cb) { |
1230 | 1310 … | repo.getRefNames(true, function (err, refs) { |
1231 | 1311 … | if (err) return cb(err) |
1232 | 1312 … | cb(null, '<select name="rev" onchange="this.form.submit()">' + |
1233 | 1313 … | Object.keys(refs).map(function (group) { |
1234 | 1314 … | return '<optgroup label="' + group + '">' + |
1235 | | - refs[group].map(function (name) { |
1236 | | - var htmlName = escapeHTML(name) |
1237 | | - return '<option value="' + htmlName + '"' + |
1238 | | - (name == currentName ? ' selected="selected"' : '') + |
1239 | | - '>' + htmlName + '</option>' |
1240 | | - }).join('') + '</optgroup>' |
| 1315 … | + refs[group].map(formatRevOptions(currentName)).join('') + |
| 1316 … | + '</optgroup>' |
1241 | 1317 … | }).join('') + |
1242 | 1318 … | '</select><noscript> <input type="submit" value="Go"/></noscript>') |
1243 | 1319 … | }) |
1244 | 1320 … | }) |
1245 | 1321 … | } |
1246 | 1322 … | |
| 1323 … | + function branchMenu(repo, name, currentName) { |
| 1324 … | + return cat([ |
| 1325 … | + pull.once('<select name="' + name + '">'), |
| 1326 … | + pull( |
| 1327 … | + repo.refs(), |
| 1328 … | + pull.map(function (ref) { |
| 1329 … | + var m = ref.name.match(/^refs\/([^\/]*)\/(.*)$/) || [,, ref.name] |
| 1330 … | + return m[1] == 'heads' && m[2] |
| 1331 … | + }), |
| 1332 … | + pull.filter(Boolean), |
| 1333 … | + pullSort(), |
| 1334 … | + pull.map(formatRevOptions(currentName)) |
| 1335 … | + ), |
| 1336 … | + pull.once('</select>') |
| 1337 … | + ]) |
| 1338 … | + } |
| 1339 … | + |
| 1340 … | + |
| 1341 … | + |
1247 | 1342 … | function renderRepoLatest(repo, rev) { |
1248 | 1343 … | return readOnce(function (cb) { |
1249 | 1344 … | repo.getCommitParsed(rev, function (err, commit) { |
1250 | 1345 … | if (err) return cb(err) |
1345 | 1440 … | commit.committer.date.toLocaleString() + '<br/>' + |
1346 | 1441 … | commit.parents.map(function (id) { |
1347 | 1442 … | return 'Parent: ' + link([repo.id, 'commit', id], id) |
1348 | 1443 … | }).join('<br>') + |
1349 | | - '</section>'), |
1350 | | - renderDiffStat(repo, commit.id, commit.parents) |
| 1444 … | + '</section>' + |
| 1445 … | + '<section><h3>Files changed</h3>'), |
| 1446 … | + |
| 1447 … | + renderDiffStat([repo, repo], [commit.parents[0], commit.id]), |
| 1448 … | + pull.once('</section>') |
1351 | 1449 … | ])) |
1352 | 1450 … | }) |
1353 | 1451 … | }) |
1354 | 1452 … | ])) |
1355 | 1453 … | } |
1356 | 1454 … | |
1357 | 1455 … | |
1358 | 1456 … | |
1359 | | - function renderDiffStat(repo, id, parentIds) { |
1360 | | - if (parentIds.length == 0) parentIds = [null] |
1361 | | - var lastI = parentIds.length |
1362 | | - var oldTree = parentIds[0] |
| 1457 … | + function renderDiffStat(repos, treeIds) { |
| 1458 … | + if (treeIds.length == 0) treeIds = [null] |
| 1459 … | + var id = treeIds[0] |
| 1460 … | + var lastI = treeIds.length - 1 |
| 1461 … | + var oldTree = treeIds[0] |
1363 | 1462 … | var changedFiles = [] |
1364 | 1463 … | return cat([ |
1365 | | - pull.once('<section><h3>Files changed</h3>'), |
1366 | 1464 … | pull( |
1367 | | - repo.diffTrees(parentIds.concat(id), true), |
| 1465 … | + Repo.diffTrees(repos, treeIds, true), |
1368 | 1466 … | pull.map(function (item) { |
1369 | 1467 … | var filename = item.filename = escapeHTML(item.path.join('/')) |
1370 | 1468 … | var oldId = item.id && item.id[0] |
1371 | 1469 … | var newId = item.id && item.id[lastI] |
1381 | 1479 … | if (item.id) |
1382 | 1480 … | changedFiles.push(item) |
1383 | 1481 … | var fileHref = item.id ? |
1384 | 1482 … | '#' + encodeURIComponent(item.path.join('/')) : |
1385 | | - encodeLink([repo.id, 'blob', id].concat(item.path)) |
| 1483 … | + encodeLink([repos[0].id, 'blob', id].concat(item.path)) |
1386 | 1484 … | return ['<a href="' + fileHref + '">' + filename + '</a>', action] |
1387 | 1485 … | }), |
1388 | 1486 … | table() |
1389 | 1487 … | ), |
1390 | 1488 … | pull( |
1391 | 1489 … | pull.values(changedFiles), |
1392 | 1490 … | paramap(function (item, cb) { |
1393 | 1491 … | var done = multicb({ pluck: 1, spread: true }) |
1394 | | - getRepoObjectString(repo, item.id[0], done()) |
1395 | | - getRepoObjectString(repo, item.id[lastI], done()) |
| 1492 … | + getRepoObjectString(repos[0], item.id[0], done()) |
| 1493 … | + getRepoObjectString(repos[1], item.id[lastI], done()) |
1396 | 1494 … | done(function (err, strOld, strNew) { |
1397 | 1495 … | if (err) return cb(err) |
1398 | | - var commitId = item.id[lastI] ? id : parentIds.filter(Boolean)[0] |
| 1496 … | + var commitId = item.id[lastI] ? id : treeIds.filter(Boolean)[0] |
1399 | 1497 … | cb(null, htmlLineDiff(item.filename, item.filename, |
1400 | 1498 … | strOld, strNew, |
1401 | | - encodeLink([repo.id, 'blob', commitId].concat(item.path)))) |
| 1499 … | + encodeLink([repos[0].id, 'blob', commitId].concat(item.path)))) |
1402 | 1500 … | }) |
1403 | 1501 … | }, 4) |
1404 | | - ), |
1405 | | - pull.once('</section>'), |
| 1502 … | + ) |
1406 | 1503 … | ]) |
1407 | 1504 … | } |
1408 | 1505 … | |
1409 | 1506 … | function htmlLineDiff(filename, anchor, oldStr, newStr, blobHref, rawHref) { |
1643 | 1740 … | } |
1644 | 1741 … | |
1645 | 1742 … | |
1646 | 1743 … | |
| 1744 … | + function getForks(repo, includeSelf) { |
| 1745 … | + return pull( |
| 1746 … | + cat([ |
| 1747 … | + includeSelf && readOnce(function (cb) { |
| 1748 … | + getMsg(repo.id, function (err, value) { |
| 1749 … | + cb(err, value && {key: repo.id, value: value}) |
| 1750 … | + }) |
| 1751 … | + }), |
| 1752 … | + ssb.links({ |
| 1753 … | + dest: repo.id, |
| 1754 … | + values: true, |
| 1755 … | + rel: 'upstream' |
| 1756 … | + }) |
| 1757 … | + ]), |
| 1758 … | + pull.filter(function (msg) { |
| 1759 … | + return msg.value.content && msg.value.content.type == 'git-repo' |
| 1760 … | + }), |
| 1761 … | + paramap(function (msg, cb) { |
| 1762 … | + getRepoFullName(about, msg.value.author, msg.key, |
| 1763 … | + function (err, repoName, authorName) { |
| 1764 … | + if (err) return cb(err) |
| 1765 … | + cb(null, { |
| 1766 … | + key: msg.key, |
| 1767 … | + value: msg.value, |
| 1768 … | + repoName: repoName, |
| 1769 … | + authorName: authorName |
| 1770 … | + }) |
| 1771 … | + }) |
| 1772 … | + }, 8) |
| 1773 … | + ) |
| 1774 … | + } |
| 1775 … | + |
1647 | 1776 … | function serveRepoForks(repo) { |
1648 | 1777 … | var hasForks |
1649 | 1778 … | return renderRepoPage(repo, null, null, cat([ |
1650 | 1779 … | pull.once('<h3>Forks</h3>'), |
1651 | 1780 … | pull( |
1652 | | - ssb.links({ |
1653 | | - dest: repo.id, |
1654 | | - values: true, |
1655 | | - rel: 'upstream' |
1656 | | - }), |
1657 | | - pull.filter(function (msg) { |
1658 | | - var c = msg.value.content |
1659 | | - return (c && c.type == 'git-repo') |
1660 | | - }), |
1661 | | - paramap(function (msg, cb) { |
| 1781 … | + getForks(repo), |
| 1782 … | + pull.map(function (msg) { |
1662 | 1783 … | hasForks = true |
1663 | | - var author = msg.value.author |
1664 | | - var done = multicb({ pluck: 1, spread: true }) |
1665 | | - getRepoName(about, author, msg.key, done()) |
1666 | | - about.getName(author, done()) |
1667 | | - done(function (err, repoName, authorName) { |
1668 | | - if (err) return cb(err) |
1669 | | - var authorLink = link([author], authorName) |
1670 | | - var repoLink = link([msg.key], repoName) |
1671 | | - cb(null, '<section class="collapse">' + |
1672 | | - authorLink + ' / ' + repoLink + |
1673 | | - '<span class="right-bar">' + |
1674 | | - timestamp(msg.value.timestamp) + |
1675 | | - '</span></section>') |
1676 | | - }) |
1677 | | - }, 8) |
| 1784 … | + return '<section class="collapse">' + |
| 1785 … | + link([msg.value.author], msg.authorName) + ' / ' + |
| 1786 … | + link([msg.key], msg.repoName) + |
| 1787 … | + '<span class="right-bar">' + |
| 1788 … | + timestamp(msg.value.timestamp) + |
| 1789 … | + '</span></section>' |
| 1790 … | + }) |
1678 | 1791 … | ), |
1679 | 1792 … | readOnce(function (cb) { |
1680 | 1793 … | cb(null, hasForks ? '' : 'No forks') |
1681 | 1794 … | }) |
1683 | 1796 … | } |
1684 | 1797 … | |
1685 | 1798 … | |
1686 | 1799 … | |
1687 | | - function serveRepoIssues(req, repo, issueId, path) { |
| 1800 … | + function serveRepoIssues(req, repo, path) { |
1688 | 1801 … | var numIssues = 0 |
1689 | 1802 … | var state = req._u.query.state || 'open' |
1690 | 1803 … | return renderRepoPage(repo, 'issues', null, cat([ |
1691 | 1804 … | pull.once( |
1724 | 1837 … | }) |
1725 | 1838 … | ])) |
1726 | 1839 … | } |
1727 | 1840 … | |
| 1841 … | + |
| 1842 … | + |
| 1843 … | + function serveRepoPullReqs(req, repo) { |
| 1844 … | + var count = 0 |
| 1845 … | + var state = req._u.query.state || 'open' |
| 1846 … | + return renderRepoPage(repo, 'pulls', null, cat([ |
| 1847 … | + pull.once( |
| 1848 … | + (isPublic ? '' : |
| 1849 … | + '<div class="right-bar">' + link([repo.id, 'compare'], |
| 1850 … | + '<button class="btn">+ New Pull Request</button>', true) + |
| 1851 … | + '</div>') + |
| 1852 … | + '<h3>Pull Requests</h3>' + |
| 1853 … | + nav([ |
| 1854 … | + ['?', 'Open', 'open'], |
| 1855 … | + ['?state=closed', 'Closed', 'closed'], |
| 1856 … | + ['?state=all', 'All', 'all'] |
| 1857 … | + ], state)), |
| 1858 … | + pull( |
| 1859 … | + pullReqs.list({ |
| 1860 … | + repo: repo.id, |
| 1861 … | + open: {open: true, closed: false}[state] |
| 1862 … | + }), |
| 1863 … | + pull.map(function (issue) { |
| 1864 … | + count++ |
| 1865 … | + var state = (issue.open ? 'open' : 'closed') |
| 1866 … | + return '<section class="collapse">' + |
| 1867 … | + '<i class="issue-state issue-state-' + state + '"' + |
| 1868 … | + ' title="' + ucfirst(state) + '">◼</i> ' + |
| 1869 … | + '<a href="' + encodeLink(issue.id) + '">' + |
| 1870 … | + escapeHTML(issue.title) + |
| 1871 … | + '<span class="right-bar">' + |
| 1872 … | + new Date(issue.created_at).toLocaleString() + |
| 1873 … | + '</span>' + |
| 1874 … | + '</a>' + |
| 1875 … | + '</section>' |
| 1876 … | + }) |
| 1877 … | + ), |
| 1878 … | + readOnce(function (cb) { |
| 1879 … | + cb(null, count > 0 ? '' : '<p>No pull requests</p>') |
| 1880 … | + }) |
| 1881 … | + ])) |
| 1882 … | + } |
| 1883 … | + |
1728 | 1884 … | |
1729 | 1885 … | |
1730 | 1886 … | function serveRepoNewIssue(repo, issueId, path) { |
1731 | 1887 … | return renderRepoPage(repo, 'issues', null, pull.once( |
1756 | 1912 … | readOnce(function (cb) { |
1757 | 1913 … | about.getName(issue.author, function (err, authorName) { |
1758 | 1914 … | if (err) return cb(err) |
1759 | 1915 … | var authorLink = link([issue.author], authorName) |
1760 | | - cb(null, |
1761 | | - authorLink + ' opened this issue on ' + timestamp(issue.created_at) + |
1762 | | - '<hr/>' + |
1763 | | - markdown(issue.text, repo) + |
1764 | | - '</section>') |
| 1916 … | + cb(null, authorLink + ' opened this issue on ' + |
| 1917 … | + timestamp(issue.created_at)) |
1765 | 1918 … | }) |
1766 | 1919 … | }), |
| 1920 … | + pull.once('<hr/>' + markdown(issue.text, repo) + '</section>'), |
1767 | 1921 … | |
1768 | 1922 … | pull( |
1769 | 1923 … | ssb.links({ |
1770 | 1924 … | dest: issue.id, |
1772 | 1926 … | }), |
1773 | 1927 … | pull.unique('key'), |
1774 | 1928 … | addAuthorName(about), |
1775 | 1929 … | sortMsgs(), |
1776 | | - pull.map(function (msg) { |
1777 | | - var authorLink = link([msg.value.author], msg.authorName) |
1778 | | - var msgTimeLink = link([msg.key], |
1779 | | - new Date(msg.value.timestamp).toLocaleString(), false, |
1780 | | - 'name="' + escapeHTML(msg.key) + '"') |
1781 | | - var c = msg.value.content |
| 1930 … | + pull.through(function (msg) { |
1782 | 1931 … | if (msg.value.timestamp > newestMsg.value.timestamp) |
1783 | 1932 … | newestMsg = msg |
1784 | | - switch (c.type) { |
1785 | | - case 'post': |
1786 | | - if (c.root == issue.id) { |
1787 | | - var changed = issues.isStatusChanged(msg, issue) |
1788 | | - return '<section class="collapse">' + |
1789 | | - (msg.key == postId ? '<div class="highlight">' : '') + |
1790 | | - authorLink + |
1791 | | - (changed == null ? '' : ' ' + ( |
1792 | | - changed ? 'reopened this issue' : 'closed this issue')) + |
1793 | | - ' · ' + msgTimeLink + |
1794 | | - (msg.key == postId ? '</div>' : '') + |
1795 | | - markdown(c.text, repo) + |
1796 | | - '</section>' |
1797 | | - } else { |
1798 | | - var text = c.text || (c.type + ' ' + msg.key) |
1799 | | - return '<section class="collapse mention-preview">' + |
1800 | | - authorLink + ' mentioned this issue in ' + |
1801 | | - '<a href="/' + msg.key + '#' + msg.key + '">' + |
1802 | | - String(text).substr(0, 140) + '</a>' + |
1803 | | - '</section>' |
1804 | | - } |
1805 | | - case 'issue': |
1806 | | - return '<section class="collapse mention-preview">' + |
1807 | | - authorLink + ' mentioned this issue in ' + |
1808 | | - link([msg.key], String(c.title || msg.key).substr(0, 140)) + |
1809 | | - '</section>' |
1810 | | - case 'issue-edit': |
1811 | | - return '<section class="collapse">' + |
1812 | | - (c.title == null ? '' : |
1813 | | - authorLink + ' renamed this issue to <q>' + |
1814 | | - escapeHTML(c.title) + '</q>') + |
1815 | | - ' · ' + msgTimeLink + |
1816 | | - '</section>' |
1817 | | - case 'git-update': |
1818 | | - var mention = issues.getMention(msg, issue) |
1819 | | - if (mention) { |
1820 | | - var commitLink = link([repo.id, 'commit', mention.object], |
1821 | | - mention.label || mention.object) |
1822 | | - return '<section class="collapse">' + |
1823 | | - authorLink + ' ' + |
1824 | | - (mention.open ? 'reopened this issue' : |
1825 | | - 'closed this issue') + |
1826 | | - ' · ' + msgTimeLink + '<br/>' + |
1827 | | - commitLink + |
1828 | | - '</section>' |
1829 | | - } else if ((mention = getMention(msg, issue.id))) { |
1830 | | - var commitLink = link(mention.object ? |
1831 | | - [repo.id, 'commit', mention.object] : [msg.key], |
1832 | | - mention.label || mention.object || msg.key) |
1833 | | - return '<section class="collapse">' + |
1834 | | - authorLink + ' mentioned this issue' + |
1835 | | - ' · ' + msgTimeLink + '<br/>' + |
1836 | | - commitLink + |
1837 | | - '</section>' |
1838 | | - } else { |
1839 | | - |
1840 | | - } |
| 1933 … | + }), |
| 1934 … | + pull.map(renderIssueActivityMsg.bind(null, repo, issue, |
| 1935 … | + 'issue', postId)) |
| 1936 … | + ), |
| 1937 … | + isPublic ? pull.empty() : readOnce(function (cb) { |
| 1938 … | + cb(null, renderIssueCommentForm(issue, repo, newestMsg.key, isAuthor, |
| 1939 … | + 'issue')) |
| 1940 … | + }) |
| 1941 … | + ])) |
| 1942 … | + } |
1841 | 1943 … | |
1842 | | - default: |
1843 | | - return '<section class="collapse">' + |
1844 | | - authorLink + |
1845 | | - ' · ' + msgTimeLink + |
1846 | | - json(c) + |
1847 | | - '</section>' |
1848 | | - } |
| 1944 … | + function renderIssueActivityMsg(repo, issue, type, postId, msg) { |
| 1945 … | + var authorLink = link([msg.value.author], msg.authorName) |
| 1946 … | + var msgTimeLink = link([msg.key], |
| 1947 … | + new Date(msg.value.timestamp).toLocaleString(), false, |
| 1948 … | + 'name="' + escapeHTML(msg.key) + '"') |
| 1949 … | + var c = msg.value.content |
| 1950 … | + switch (c.type) { |
| 1951 … | + case 'post': |
| 1952 … | + if (c.root == issue.id) { |
| 1953 … | + var changed = issues.isStatusChanged(msg, issue) |
| 1954 … | + return '<section class="collapse">' + |
| 1955 … | + (msg.key == postId ? '<div class="highlight">' : '') + |
| 1956 … | + authorLink + |
| 1957 … | + (changed == null ? '' : ' ' + ( |
| 1958 … | + changed ? 'reopened this ' : 'closed this ') + type) + |
| 1959 … | + ' · ' + msgTimeLink + |
| 1960 … | + (msg.key == postId ? '</div>' : '') + |
| 1961 … | + markdown(c.text, repo) + |
| 1962 … | + '</section>' |
| 1963 … | + } else { |
| 1964 … | + var text = c.text || (c.type + ' ' + msg.key) |
| 1965 … | + return '<section class="collapse mention-preview">' + |
| 1966 … | + authorLink + ' mentioned this issue in ' + |
| 1967 … | + '<a href="/' + msg.key + '#' + msg.key + '">' + |
| 1968 … | + String(text).substr(0, 140) + '</a>' + |
| 1969 … | + '</section>' |
| 1970 … | + } |
| 1971 … | + case 'issue': |
| 1972 … | + case 'pull-request': |
| 1973 … | + return '<section class="collapse mention-preview">' + |
| 1974 … | + authorLink + ' mentioned this ' + type + ' in ' + |
| 1975 … | + link([msg.key], String(c.title || msg.key).substr(0, 140)) + |
| 1976 … | + '</section>' |
| 1977 … | + case 'issue-edit': |
| 1978 … | + return '<section class="collapse">' + |
| 1979 … | + (msg.key == postId ? '<div class="highlight">' : '') + |
| 1980 … | + (c.title == null ? '' : |
| 1981 … | + authorLink + ' renamed this ' + type + ' to <q>' + |
| 1982 … | + escapeHTML(c.title) + '</q>') + |
| 1983 … | + ' · ' + msgTimeLink + |
| 1984 … | + (msg.key == postId ? '</div>' : '') + |
| 1985 … | + '</section>' |
| 1986 … | + case 'git-update': |
| 1987 … | + var mention = issues.getMention(msg, issue) |
| 1988 … | + if (mention) { |
| 1989 … | + var commitLink = link([repo.id, 'commit', mention.object], |
| 1990 … | + mention.label || mention.object) |
| 1991 … | + return '<section class="collapse">' + |
| 1992 … | + authorLink + ' ' + |
| 1993 … | + (mention.open ? 'reopened this ' : |
| 1994 … | + 'closed this ') + type + |
| 1995 … | + ' · ' + msgTimeLink + '<br/>' + |
| 1996 … | + commitLink + |
| 1997 … | + '</section>' |
| 1998 … | + } else if ((mention = getMention(msg, issue.id))) { |
| 1999 … | + var commitLink = link(mention.object ? |
| 2000 … | + [repo.id, 'commit', mention.object] : [msg.key], |
| 2001 … | + mention.label || mention.object || msg.key) |
| 2002 … | + return '<section class="collapse">' + |
| 2003 … | + authorLink + ' mentioned this ' + type + |
| 2004 … | + ' · ' + msgTimeLink + '<br/>' + |
| 2005 … | + commitLink + |
| 2006 … | + '</section>' |
| 2007 … | + } else { |
| 2008 … | + |
| 2009 … | + } |
| 2010 … | + |
| 2011 … | + default: |
| 2012 … | + return '<section class="collapse">' + |
| 2013 … | + authorLink + |
| 2014 … | + ' · ' + msgTimeLink + |
| 2015 … | + json(c) + |
| 2016 … | + '</section>' |
| 2017 … | + } |
| 2018 … | + } |
| 2019 … | + |
| 2020 … | + function renderIssueCommentForm(issue, repo, branch, isAuthor, type) { |
| 2021 … | + return '<section><form action="" method="post">' + |
| 2022 … | + '<input type="hidden" name="action" value="comment">' + |
| 2023 … | + '<input type="hidden" name="id" value="' + issue.id + '">' + |
| 2024 … | + '<input type="hidden" name="issue" value="' + issue.id + '">' + |
| 2025 … | + '<input type="hidden" name="repo" value="' + repo.id + '">' + |
| 2026 … | + '<input type="hidden" name="branch" value="' + branch + '">' + |
| 2027 … | + renderPostForm(repo) + |
| 2028 … | + '<input type="submit" class="btn open" value="Comment" />' + |
| 2029 … | + (isAuthor ? |
| 2030 … | + '<input type="submit" class="btn"' + |
| 2031 … | + ' name="' + (issue.open ? 'close' : 'open') + '"' + |
| 2032 … | + ' value="' + (issue.open ? 'Close ' : 'Reopen ') + type + '"' + |
| 2033 … | + '/>' : '') + |
| 2034 … | + '</form></section>' |
| 2035 … | + } |
| 2036 … | + |
| 2037 … | + |
| 2038 … | + |
| 2039 … | + function serveRepoPullReq(req, repo, pr, path, postId) { |
| 2040 … | + var headRepo, authorLink |
| 2041 … | + var page = path[0] || 'activity' |
| 2042 … | + return renderRepoPage(repo, 'pulls', null, cat([ |
| 2043 … | + pull.once('<div class="pull-request">' + |
| 2044 … | + renderNameForm(!isPublic, pr.id, pr.title, 'issue-title', null, |
| 2045 … | + 'Rename the pull request', |
| 2046 … | + '<h3>' + link([pr.id], pr.title) + '</h3>') + |
| 2047 … | + '<code>' + pr.id + '</code>'), |
| 2048 … | + readOnce(function (cb) { |
| 2049 … | + var done = multicb({ pluck: 1, spread: true }) |
| 2050 … | + about.getName(pr.author, done()) |
| 2051 … | + var sameRepo = (pr.headRepo == pr.baseRepo) |
| 2052 … | + getRepo(pr.headRepo, function (err, headRepo) { |
| 2053 … | + if (err) return cb(err) |
| 2054 … | + done()(null, headRepo) |
| 2055 … | + getRepoName(about, headRepo.feed, headRepo.id, done()) |
| 2056 … | + about.getName(headRepo.feed, done()) |
1849 | 2057 … | }) |
| 2058 … | + |
| 2059 … | + done(function (err, issueAuthorName, _headRepo, |
| 2060 … | + headRepoName, headRepoAuthorName) { |
| 2061 … | + if (err) return cb(err) |
| 2062 … | + headRepo = _headRepo |
| 2063 … | + authorLink = link([pr.author], issueAuthorName) |
| 2064 … | + var repoLink = link([pr.headRepo], headRepoName) |
| 2065 … | + var headRepoAuthorLink = link([headRepo.feed], headRepoAuthorName) |
| 2066 … | + var headRepoLink = link([headRepo.id], headRepoName) |
| 2067 … | + var headBranchLink = link([headRepo.id, 'tree', pr.headBranch]) |
| 2068 … | + var baseBranchLink = link([repo.id, 'tree', pr.baseBranch]) |
| 2069 … | + cb(null, '<section class="collapse">' + |
| 2070 … | + (pr.open |
| 2071 … | + ? '<strong class="issue-status open">Open</strong>' |
| 2072 … | + : '<strong class="issue-status closed">Closed</strong>') + |
| 2073 … | + authorLink + ' wants to merge commits into ' + |
| 2074 … | + '<code>' + baseBranchLink + '</code> from ' + |
| 2075 … | + (sameRepo ? '<code>' + headBranchLink + '</code>' : |
| 2076 … | + '<code class="bgslash">' + |
| 2077 … | + headRepoAuthorLink + ' / ' + |
| 2078 … | + headRepoLink + ' / ' + |
| 2079 … | + headBranchLink + '</code>') + |
| 2080 … | + '</section>') |
| 2081 … | + }) |
| 2082 … | + }), |
| 2083 … | + pull.once( |
| 2084 … | + nav([ |
| 2085 … | + [[pr.id], 'Discussion', 'activity'], |
| 2086 … | + [[pr.id, 'commits'], 'Commits', 'commits'], |
| 2087 … | + [[pr.id, 'files'], 'Files', 'files'] |
| 2088 … | + ], page)), |
| 2089 … | + readNext(function (cb) { |
| 2090 … | + if (page == 'commits') cb(null, |
| 2091 … | + renderPullReqCommits(pr, repo, headRepo)) |
| 2092 … | + else if (page == 'files') cb(null, |
| 2093 … | + renderPullReqFiles(pr, repo, headRepo)) |
| 2094 … | + else cb(null, |
| 2095 … | + renderPullReqActivity(pr, repo, headRepo, authorLink, postId)) |
| 2096 … | + }) |
| 2097 … | + ])) |
| 2098 … | + } |
| 2099 … | + |
| 2100 … | + function renderPullReqCommits(pr, baseRepo, headRepo) { |
| 2101 … | + return cat([ |
| 2102 … | + pull.once('<section>'), |
| 2103 … | + renderCommitLog(baseRepo, pr.baseBranch, headRepo, pr.headBranch), |
| 2104 … | + pull.once('</section>') |
| 2105 … | + ]) |
| 2106 … | + } |
| 2107 … | + |
| 2108 … | + function renderPullReqFiles(pr, baseRepo, headRepo) { |
| 2109 … | + return cat([ |
| 2110 … | + pull.once('<section>'), |
| 2111 … | + renderDiffStat([baseRepo, headRepo], [pr.baseBranch, pr.headBranch]), |
| 2112 … | + pull.once('</section>') |
| 2113 … | + ]) |
| 2114 … | + } |
| 2115 … | + |
| 2116 … | + function renderPullReqActivity(pr, repo, headRepo, authorLink, postId) { |
| 2117 … | + var msgTimeLink = link([pr.id], new Date(pr.created_at).toLocaleString()) |
| 2118 … | + var newestMsg = {key: pr.id, value: {timestamp: pr.created_at}} |
| 2119 … | + var isAuthor = (myId == pr.author) || (myId == repo.feed) |
| 2120 … | + return cat([ |
| 2121 … | + readOnce(function (cb) { |
| 2122 … | + cb(null, |
| 2123 … | + '<section class="collapse">' + |
| 2124 … | + authorLink + ' · ' + msgTimeLink + |
| 2125 … | + markdown(pr.text, repo) + '</section>') |
| 2126 … | + }), |
| 2127 … | + |
| 2128 … | + pull( |
| 2129 … | + many([ |
| 2130 … | + pull( |
| 2131 … | + ssb.links({ |
| 2132 … | + dest: pr.id, |
| 2133 … | + values: true |
| 2134 … | + }), |
| 2135 … | + pull.unique('key') |
| 2136 … | + ), |
| 2137 … | + readNext(function (cb) { |
| 2138 … | + cb(null, pull( |
| 2139 … | + ssb.links({ |
| 2140 … | + dest: headRepo.id, |
| 2141 … | + source: headRepo.feed, |
| 2142 … | + rel: 'repo', |
| 2143 … | + values: true, |
| 2144 … | + reverse: true |
| 2145 … | + }), |
| 2146 … | + pull.take(function (link) { |
| 2147 … | + return link.value.timestamp > pr.created_at |
| 2148 … | + }), |
| 2149 … | + pull.filter(function (link) { |
| 2150 … | + return link.value.content.type == 'git-update' |
| 2151 … | + && ('refs/heads/' + pr.headBranch) in link.value.content.refs |
| 2152 … | + }) |
| 2153 … | + )) |
| 2154 … | + }) |
| 2155 … | + ]), |
| 2156 … | + addAuthorName(about), |
| 2157 … | + pull.through(function (msg) { |
| 2158 … | + if (msg.value.timestamp > newestMsg.value.timestamp) |
| 2159 … | + newestMsg = msg |
| 2160 … | + }), |
| 2161 … | + sortMsgs(), |
| 2162 … | + pull.map(function (item) { |
| 2163 … | + if (item.value.content.type == 'git-update') |
| 2164 … | + return renderBranchUpdate(pr, item) |
| 2165 … | + return renderIssueActivityMsg(repo, pr, |
| 2166 … | + 'pull request', postId, item) |
| 2167 … | + }) |
1850 | 2168 … | ), |
1851 | | - isPublic ? pull.empty() : readOnce(renderCommentForm) |
| 2169 … | + isPublic ? pull.empty() : readOnce(function (cb) { |
| 2170 … | + cb(null, renderIssueCommentForm(pr, repo, newestMsg.key, isAuthor, |
| 2171 … | + 'pull request')) |
| 2172 … | + }) |
| 2173 … | + ]) |
| 2174 … | + } |
| 2175 … | + |
| 2176 … | + function renderBranchUpdate(pr, msg) { |
| 2177 … | + var authorLink = link([msg.value.author], msg.authorName) |
| 2178 … | + var msgLink = link([msg.key], |
| 2179 … | + new Date(msg.value.timestamp).toLocaleString()) |
| 2180 … | + var rev = msg.value.content.refs['refs/heads/' + pr.headBranch] |
| 2181 … | + if (!rev) |
| 2182 … | + return '<section class="collapse">' + |
| 2183 … | + authorLink + ' deleted the <code>' + pr.headBranch + '</code> branch' + |
| 2184 … | + ' · ' + msgLink + |
| 2185 … | + '</section>' |
| 2186 … | + |
| 2187 … | + var revLink = link([pr.headRepo, 'commit', rev], rev.substr(0, 8)) |
| 2188 … | + return '<section class="collapse">' + |
| 2189 … | + authorLink + ' updated the branch to <code>' + revLink + '</code>' + |
| 2190 … | + ' · ' + msgLink + |
| 2191 … | + '</section>' |
| 2192 … | + } |
| 2193 … | + |
| 2194 … | + |
| 2195 … | + |
| 2196 … | + function serveRepoCompare(req, repo) { |
| 2197 … | + var query = req._u.query |
| 2198 … | + var base |
| 2199 … | + var count = 0 |
| 2200 … | + |
| 2201 … | + return renderRepoPage(repo, 'pulls', null, cat([ |
| 2202 … | + pull.once('<h3>Compare changes</h3>' + |
| 2203 … | + '<form action="' + encodeLink(repo.id) + '/comparing" method="get">' + |
| 2204 … | + '<section>'), |
| 2205 … | + pull.once('Base branch: '), |
| 2206 … | + readNext(function (cb) { |
| 2207 … | + if (query.base) gotBase(null, query.base) |
| 2208 … | + else repo.getSymRef('HEAD', true, gotBase) |
| 2209 … | + function gotBase(err, ref) { |
| 2210 … | + if (err) return cb(err) |
| 2211 … | + cb(null, branchMenu(repo, 'base', base = ref || 'HEAD')) |
| 2212 … | + } |
| 2213 … | + }), |
| 2214 … | + pull.once('<br/>Comparison repo/branch:'), |
| 2215 … | + pull( |
| 2216 … | + getForks(repo, true), |
| 2217 … | + pull.asyncMap(function (msg, cb) { |
| 2218 … | + getRepo(msg.key, function (err, repo) { |
| 2219 … | + if (err) return cb(err) |
| 2220 … | + cb(null, { |
| 2221 … | + msg: msg, |
| 2222 … | + repo: repo |
| 2223 … | + }) |
| 2224 … | + }) |
| 2225 … | + }), |
| 2226 … | + pull.map(renderFork), |
| 2227 … | + pull.flatten() |
| 2228 … | + ), |
| 2229 … | + pull.once('</section>'), |
| 2230 … | + readOnce(function (cb) { |
| 2231 … | + cb(null, count == 0 ? 'No branches to compare!' : |
| 2232 … | + '<button type="submit" class="btn">Compare</button>') |
| 2233 … | + }), |
| 2234 … | + pull.once('</form>') |
1852 | 2235 … | ])) |
1853 | 2236 … | |
1854 | | - function renderCommentForm(cb) { |
1855 | | - cb(null, '<section><form action="" method="post">' + |
1856 | | - '<input type="hidden" name="action" value="comment">' + |
1857 | | - '<input type="hidden" name="id" value="' + issue.id + '">' + |
1858 | | - '<input type="hidden" name="issue" value="' + issue.id + '">' + |
1859 | | - '<input type="hidden" name="repo" value="' + repo.id + '">' + |
1860 | | - '<input type="hidden" name="branch" value="' + newestMsg.key + '">' + |
1861 | | - renderPostForm(repo) + |
1862 | | - '<input type="submit" class="btn open" value="Comment" />' + |
1863 | | - (isAuthor ? |
1864 | | - '<input type="submit" class="btn"' + |
1865 | | - ' name="' + (issue.open ? 'close' : 'open') + '"' + |
1866 | | - ' value="' + (issue.open ? 'Close issue' : 'Reopen issue') + '"' + |
1867 | | - '/>' : '') + |
1868 | | - '</form></section>') |
| 2237 … | + function renderFork(fork) { |
| 2238 … | + return pull( |
| 2239 … | + fork.repo.refs(), |
| 2240 … | + pull.map(function (ref) { |
| 2241 … | + var m = /^refs\/([^\/]*)\/(.*)$/.exec(ref.name) || [,ref.name] |
| 2242 … | + return { |
| 2243 … | + type: m[1], |
| 2244 … | + name: m[2], |
| 2245 … | + value: ref.value |
| 2246 … | + } |
| 2247 … | + }), |
| 2248 … | + pull.filter(function (ref) { |
| 2249 … | + return ref.type == 'heads' |
| 2250 … | + && !(ref.name == base && fork.msg.key == repo.id) |
| 2251 … | + }), |
| 2252 … | + pull.map(function (ref) { |
| 2253 … | + var branchLink = link([fork.msg.key, 'tree', ref.name], ref.name) |
| 2254 … | + var authorLink = link([fork.msg.value.author], fork.msg.authorName) |
| 2255 … | + var repoLink = link([fork.msg.key], fork.msg.repoName) |
| 2256 … | + var value = fork.msg.key + ':' + ref.name |
| 2257 … | + count++ |
| 2258 … | + return '<div class="bgslash">' + |
| 2259 … | + '<input type="radio" name="head"' + |
| 2260 … | + ' value="' + escapeHTML(value) + '"' + |
| 2261 … | + (query.head == value ? ' checked="checked"' : '') + '> ' + |
| 2262 … | + authorLink + ' / ' + repoLink + ' / ' + branchLink + '</div>' |
| 2263 … | + }) |
| 2264 … | + ) |
1869 | 2265 … | } |
1870 | 2266 … | } |
1871 | 2267 … | |
| 2268 … | + function serveRepoComparing(req, repo) { |
| 2269 … | + var query = req._u.query |
| 2270 … | + var baseBranch = query.base |
| 2271 … | + var s = (query.head || '').split(':') |
| 2272 … | + |
| 2273 … | + if (!s || !baseBranch) |
| 2274 … | + return serveRedirect(encodeLink([repo.id, 'compare'])) |
| 2275 … | + |
| 2276 … | + var headRepoId = s[0] |
| 2277 … | + var headBranch = s[1] |
| 2278 … | + var baseLink = link([repo.id, 'tree', baseBranch]) |
| 2279 … | + var headBranchLink = link([headRepoId, 'tree', headBranch]) |
| 2280 … | + var backHref = encodeLink([repo.id, 'compare']) + req._u.search |
| 2281 … | + |
| 2282 … | + return renderRepoPage(repo, 'pulls', null, cat([ |
| 2283 … | + pull.once('<h3>' + |
| 2284 … | + (query.expand ? 'Open a pull request' : 'Comparing changes') + |
| 2285 … | + '</h3>'), |
| 2286 … | + readNext(function (cb) { |
| 2287 … | + getRepo(headRepoId, function (err, headRepo) { |
| 2288 … | + if (err) return cb(err) |
| 2289 … | + getRepoFullName(about, headRepo.feed, headRepo.id, |
| 2290 … | + function (err, repoName, authorName) { |
| 2291 … | + if (err) return cb(err) |
| 2292 … | + cb(null, renderRepoInfo(Repo(headRepo), repoName, authorName)) |
| 2293 … | + } |
| 2294 … | + ) |
| 2295 … | + }) |
| 2296 … | + }) |
| 2297 … | + ])) |
| 2298 … | + |
| 2299 … | + function renderRepoInfo(headRepo, headRepoName, headRepoAuthorName) { |
| 2300 … | + var authorLink = link([headRepo.feed], headRepoAuthorName) |
| 2301 … | + var repoLink = link([headRepoId], headRepoName) |
| 2302 … | + return cat([ |
| 2303 … | + pull.once('<section>' + |
| 2304 … | + 'Base: ' + baseLink + '<br/>' + |
| 2305 … | + 'Head: <span class="bgslash">' + authorLink + ' / ' + repoLink + |
| 2306 … | + ' / ' + headBranchLink + '</span>' + |
| 2307 … | + '</section>' + |
| 2308 … | + (query.expand ? '<section><form method="post" action="">' + |
| 2309 … | + hiddenInputs({ |
| 2310 … | + action: 'new-pull', |
| 2311 … | + branch: baseBranch, |
| 2312 … | + head_repo: headRepoId, |
| 2313 … | + head_branch: headBranch |
| 2314 … | + }) + |
| 2315 … | + '<input class="wide-input" name="title"' + |
| 2316 … | + ' placeholder="Title" size="77"/>' + |
| 2317 … | + renderPostForm(repo, 'Description', 8) + |
| 2318 … | + '<button type="submit" class="btn open">Create</button>' + |
| 2319 … | + '</form></section>' |
| 2320 … | + : '<section><form method="get" action="">' + |
| 2321 … | + hiddenInputs({ |
| 2322 … | + base: baseBranch, |
| 2323 … | + head: query.head |
| 2324 … | + }) + |
| 2325 … | + '<button class="btn open" type="submit" name="expand" value="1">' + |
| 2326 … | + '<i>⎇</i> Create pull request</button> ' + |
| 2327 … | + '<a href="' + backHref + '">Back</a>' + |
| 2328 … | + '</form></section>') + |
| 2329 … | + '<div id="commits"></div>' + |
| 2330 … | + '<div class="tab-links">' + |
| 2331 … | + '<a href="#" id="files-link">Files changed</a> ' + |
| 2332 … | + '<a href="#commits" id="commits-link">Commits</a>' + |
| 2333 … | + '</div>' + |
| 2334 … | + '<section id="files-tab">'), |
| 2335 … | + renderDiffStat([repo, headRepo], [baseBranch, headBranch]), |
| 2336 … | + pull.once('</section>' + |
| 2337 … | + '<section id="commits-tab">'), |
| 2338 … | + renderCommitLog(repo, baseBranch, headRepo, headBranch), |
| 2339 … | + pull.once('</section>') |
| 2340 … | + ]) |
| 2341 … | + } |
| 2342 … | + } |
| 2343 … | + |
| 2344 … | + function renderCommitLog(baseRepo, baseBranch, headRepo, headBranch) { |
| 2345 … | + return cat([ |
| 2346 … | + pull.once('<table class="compare-commits">'), |
| 2347 … | + readNext(function (cb) { |
| 2348 … | + baseRepo.resolveRef(baseBranch, function (err, baseBranchRev) { |
| 2349 … | + if (err) return cb(err) |
| 2350 … | + var currentDay |
| 2351 … | + return cb(null, pull( |
| 2352 … | + headRepo.readLog(headBranch), |
| 2353 … | + pull.take(function (rev) { return rev != baseBranchRev }), |
| 2354 … | + |
| 2355 … | + pullReverse(), |
| 2356 … | + paramap(headRepo.getCommitParsed.bind(headRepo), 8), |
| 2357 … | + pull.map(function (commit) { |
| 2358 … | + var commitPath = [baseRepo.id, 'commit', commit.id] |
| 2359 … | + var commitIdShort = '<tt>' + commit.id.substr(0, 8) + '</tt>' |
| 2360 … | + var day = Math.floor(commit.author.date / 86400000) |
| 2361 … | + var dateRow = day == currentDay ? '' : |
| 2362 … | + '<tr><th colspan=3 class="date-info">' + |
| 2363 … | + commit.author.date.toLocaleDateString() + |
| 2364 … | + '</th><tr>' |
| 2365 … | + currentDay = day |
| 2366 … | + return dateRow + '<tr>' + |
| 2367 … | + '<td>' + escapeHTML(commit.author.name) + '</td>' + |
| 2368 … | + '<td>' + link(commitPath, commit.title) + '</td>' + |
| 2369 … | + '<td>' + link(commitPath, commitIdShort, true) + '</td>' + |
| 2370 … | + '</tr>' |
| 2371 … | + }) |
| 2372 … | + )) |
| 2373 … | + }) |
| 2374 … | + }), |
| 2375 … | + pull.once('</table>') |
| 2376 … | + ]) |
| 2377 … | + } |
1872 | 2378 … | } |