index.jsView |
---|
380 | 380 | return str |
381 | 381 | } |
382 | 382 | } |
383 | 383 | |
384 | | -function getRepoName(about, ownerId, repoId, cb) { |
385 | | - about.getName({ |
386 | | - owner: ownerId, |
387 | | - target: repoId, |
388 | | - toString: function () { |
389 | | - |
390 | | - return ownerId + '/' + repoId |
391 | | - } |
392 | | - }, cb) |
393 | | -} |
394 | | - |
395 | | -function getRepoFullName(about, author, repoId, cb) { |
396 | | - var done = multicb({ pluck: 1, spread: true }) |
397 | | - getRepoName(about, author, repoId, done()) |
398 | | - about.getName(author, done()) |
399 | | - done(cb) |
400 | | -} |
401 | | - |
402 | | -function addAuthorName(about) { |
403 | | - return paramap(function (msg, cb) { |
404 | | - var author = msg && msg.value && msg.value.author |
405 | | - if (!author) return cb(null, msg) |
406 | | - about.getName(author, function (err, authorName) { |
407 | | - msg.authorName = authorName |
408 | | - cb(err, msg) |
409 | | - }) |
410 | | - }, 8) |
411 | | -} |
412 | | - |
413 | 384 | function getMention(msg, id) { |
414 | 385 | if (msg.key == id) return msg |
415 | 386 | var mentions = msg.value.content.mentions |
416 | 387 | if (mentions) for (var i = 0; i < mentions.length; i++) { |
480 | 451 | svg: 'image/svg+xml', |
481 | 452 | bmp: 'image/bmp' |
482 | 453 | } |
483 | 454 | |
484 | | -module.exports = function (opts, cb) { |
485 | | - var ssb, reconnect, myId, getRepo, getVotes, getMsg, issues |
486 | | - var about = function (id, cb) { cb(null, {name: id}) } |
487 | | - var reqQueue = [] |
488 | | - var isPublic = opts.public |
489 | | - var ssbAppname = opts.appname || 'ssb' |
| 455 | +var _httpServer |
490 | 456 | |
491 | | - var addr = parseAddr(opts.listenAddr, {host: 'localhost', port: 7718}) |
492 | | - http.createServer(onRequest).listen(addr.port, addr.host, onListening) |
| 457 | +module.exports = { |
| 458 | + name: 'git-ssb-web', |
| 459 | + version: require('./package').version, |
| 460 | + manifest: {}, |
| 461 | + init: function (ssb, config, reconnect) { |
| 462 | + |
| 463 | + |
| 464 | + if (_httpServer) |
| 465 | + _httpServer.close() |
493 | 466 | |
494 | | - var server = { |
495 | | - setSSB: function (_ssb, _reconnect) { |
496 | | - _ssb.whoami(function (err, feed) { |
497 | | - if (err) throw err |
498 | | - ssb = _ssb |
499 | | - reconnect = _reconnect |
500 | | - myId = feed.id |
501 | | - about = ssbAbout(ssb, myId) |
502 | | - while (reqQueue.length) |
503 | | - onRequest.apply(this, reqQueue.shift()) |
504 | | - getRepo = asyncMemo(function (id, cb) { |
505 | | - getMsg(id, function (err, msg) { |
506 | | - if (err) return cb(err) |
507 | | - ssbGit.getRepo(ssb, {key: id, value: msg}, {live: true}, cb) |
508 | | - }) |
509 | | - }) |
510 | | - getVotes = ssbVotes(ssb) |
511 | | - getMsg = asyncMemo(ssb.get) |
512 | | - issues = Issues.init(ssb) |
513 | | - pullReqs = PullRequests.init(ssb) |
514 | | - }) |
515 | | - } |
516 | | - } |
| 467 | + var web = new GitSSBWeb(ssb, config, reconnect) |
| 468 | + _httpSserver = web.httpServer |
517 | 469 | |
518 | | - function onListening() { |
519 | | - var host = ~addr.host.indexOf(':') ? '[' + addr.host + ']' : addr.host |
520 | | - console.log('Listening on http://' + host + ':' + addr.port + '/') |
521 | | - cb(null, server) |
| 470 | + return {} |
522 | 471 | } |
| 472 | +} |
523 | 473 | |
524 | | - |
| 474 | +function GitSSBWeb(ssb, config, reconnect) { |
| 475 | + this.ssb = ssb |
| 476 | + this.config = config |
| 477 | + this.reconnect = reconnect |
525 | 478 | |
526 | | - function onRequest(req, res) { |
527 | | - console.log(req.method, req.url) |
528 | | - if (!ssb) return reqQueue.push(arguments) |
529 | | - req._u = url.parse(req.url, true) |
530 | | - var locale = req._u.query.locale || |
531 | | - (/locale=([^;]*)/.exec(req.headers.cookie) || [])[1] |
532 | | - var reqLocales = req.headers['accept-language'] |
533 | | - i18n.pickCatalog(reqLocales, locale, function (err, t) { |
534 | | - if (err) return pull(serveError(req, err, 500), serve(req, res)) |
535 | | - req._t = t |
536 | | - req._locale = t.locale |
537 | | - pull(handleRequest(req), serve(req, res)) |
| 479 | + this.ssbAppname = config.appname || 'ssb' |
| 480 | + this.isPublic = config.public |
| 481 | + this.getVotes = ssbVotes(ssb) |
| 482 | + this.getMsg = asyncMemo(ssb.get) |
| 483 | + this.issues = Issues.init(ssb) |
| 484 | + this.pullReqs = PullRequests.init(ssb) |
| 485 | + this.getRepo = asyncMemo(function (id, cb) { |
| 486 | + this.getMsg(id, function (err, msg) { |
| 487 | + if (err) return cb(err) |
| 488 | + ssbGit.getRepo(ssb, {key: id, value: msg}, {live: true}, cb) |
538 | 489 | }) |
539 | | - } |
| 490 | + }) |
540 | 491 | |
541 | | - function serve(req, res) { |
542 | | - return pull( |
543 | | - pull.filter(function (data) { |
544 | | - if (Array.isArray(data)) { |
545 | | - res.writeHead.apply(res, data) |
546 | | - return false |
547 | | - } |
548 | | - return true |
549 | | - }), |
550 | | - toPull(res) |
551 | | - ) |
552 | | - } |
| 492 | + this.about = function (id, cb) { cb(null, {name: id}) } |
| 493 | + ssb.whoami(function (err, feed) { |
| 494 | + this.myId = feed.id |
| 495 | + this.about = ssbAbout(ssb, this.myId) |
| 496 | + }.bind(this)) |
553 | 497 | |
554 | | - function handleRequest(req) { |
555 | | - var path = req._u.pathname.slice(1) |
556 | | - var dirs = ref.isLink(path) ? [path] : |
557 | | - path.split(/\/+/).map(tryDecodeURIComponent) |
558 | | - var dir = dirs[0] |
| 498 | + var webConfig = config['git-ssb-web'] || {} |
| 499 | + var addr = parseAddr(config.listenAddr, { |
| 500 | + host: webConfig.host || 'localhost', |
| 501 | + port: webConfig.port || 7718 |
| 502 | + }) |
| 503 | + this.listen(addr.host, addr.port) |
| 504 | +} |
559 | 505 | |
560 | | - if (req.method == 'POST') { |
561 | | - if (isPublic) |
562 | | - return serveBuffer(405, req._t('error.POSTNotAllowed')) |
563 | | - return readNext(function (cb) { |
564 | | - readReqForm(req, function (err, data) { |
565 | | - if (err) return cb(null, serveError(req, err, 400)) |
566 | | - if (!data) return cb(null, serveError(req, |
567 | | - new ParamError(req._t('error.MissingData')), 400)) |
| 506 | +var G = GitSSBWeb.prototype |
568 | 507 | |
569 | | - switch (data.action) { |
570 | | - case 'fork-prompt': |
571 | | - return cb(null, serveRedirect(req, |
572 | | - encodeLink([data.id, 'fork']))) |
| 508 | +G.listen = function (host, port) { |
| 509 | + this.httpServer = http.createServer(G_onRequest.bind(this)) |
| 510 | + this.httpServer.listen(port, host, function () { |
| 511 | + var hostName = ~host.indexOf(':') ? '[' + host + ']' : host |
| 512 | + console.log('Listening on http://' + hostName + ':' + port + '/') |
| 513 | + }) |
| 514 | +} |
573 | 515 | |
574 | | - case 'fork': |
575 | | - if (!data.id) |
576 | | - return cb(null, serveError(req, |
577 | | - new ParamError(req._t('error.MissingId')), 400)) |
578 | | - return ssbGit.createRepo(ssb, {upstream: data.id}, |
579 | | - function (err, repo) { |
580 | | - if (err) return cb(null, serveError(req, err)) |
581 | | - cb(null, serveRedirect(req, encodeLink(repo.id))) |
582 | | - }) |
| 516 | +G.getRepoName = function (ownerId, repoId, cb) { |
| 517 | + this.about.getName({ |
| 518 | + owner: ownerId, |
| 519 | + target: repoId, |
| 520 | + toString: function () { |
| 521 | + |
| 522 | + return ownerId + '/' + repoId |
| 523 | + } |
| 524 | + }, cb) |
| 525 | +} |
583 | 526 | |
584 | | - case 'vote': |
585 | | - var voteValue = +data.value || 0 |
586 | | - if (!data.id) |
587 | | - return cb(null, serveError(req, |
588 | | - new ParamError(req._t('error.MissingId')), 400)) |
589 | | - var msg = schemas.vote(data.id, voteValue) |
590 | | - return ssb.publish(msg, function (err) { |
591 | | - if (err) return cb(null, serveError(req, err)) |
592 | | - cb(null, serveRedirect(req, req.url)) |
593 | | - }) |
| 527 | +G.getRepoFullName = function (author, repoId, cb) { |
| 528 | + var done = multicb({ pluck: 1, spread: true }) |
| 529 | + this.getRepoName(author, repoId, done()) |
| 530 | + this.about.getName(author, done()) |
| 531 | + done(cb) |
| 532 | +} |
594 | 533 | |
595 | | - case 'repo-name': |
596 | | - if (!data.id) |
597 | | - return cb(null, serveError(req, |
598 | | - new ParamError(req._t('error.MissingId')), 400)) |
599 | | - if (!data.name) |
600 | | - return cb(null, serveError(req, |
601 | | - new ParamError(req._t('error.MissingName')), 400)) |
602 | | - var msg = schemas.name(data.id, data.name) |
603 | | - return ssb.publish(msg, function (err) { |
604 | | - if (err) return cb(null, serveError(req, err)) |
605 | | - cb(null, serveRedirect(req, req.url)) |
606 | | - }) |
| 534 | +G.addAuthorName = function () { |
| 535 | + var about = this.about |
| 536 | + return paramap(function (msg, cb) { |
| 537 | + var author = msg && msg.value && msg.value.author |
| 538 | + if (!author) return cb(null, msg) |
| 539 | + about.getName(author, function (err, authorName) { |
| 540 | + msg.authorName = authorName |
| 541 | + cb(err, msg) |
| 542 | + }) |
| 543 | + }, 8) |
| 544 | +} |
607 | 545 | |
608 | | - case 'issue-title': |
609 | | - if (!data.id) |
610 | | - return cb(null, serveError(req, |
611 | | - new ParamError(req._t('error.MissingId')), 400)) |
612 | | - if (!data.name) |
613 | | - return cb(null, serveError(req, |
614 | | - new ParamError(req._t('error.MissingName')), 400)) |
615 | | - var msg = Issues.schemas.edit(data.id, {title: data.name}) |
616 | | - return ssb.publish(msg, function (err) { |
617 | | - if (err) return cb(null, serveError(req, err)) |
618 | | - cb(null, serveRedirect(req, req.url)) |
619 | | - }) |
| 546 | + |
620 | 547 | |
621 | | - case 'comment': |
622 | | - if (!data.id) |
623 | | - return cb(null, serveError(req, |
624 | | - new ParamError(req._t('error.MissingId')), 400)) |
625 | | - var msg = schemas.post(data.text, data.id, data.branch || data.id) |
626 | | - msg.issue = data.issue |
627 | | - msg.repo = data.repo |
628 | | - if (data.open != null) |
629 | | - Issues.schemas.opens(msg, data.id) |
630 | | - if (data.close != null) |
631 | | - Issues.schemas.closes(msg, data.id) |
632 | | - var mentions = Mentions(data.text) |
633 | | - if (mentions.length) |
634 | | - msg.mentions = mentions |
635 | | - return ssb.publish(msg, function (err) { |
636 | | - if (err) return cb(null, serveError(req, err)) |
637 | | - cb(null, serveRedirect(req, req.url)) |
638 | | - }) |
| 548 | +function serve(req, res) { |
| 549 | + return pull( |
| 550 | + pull.filter(function (data) { |
| 551 | + if (Array.isArray(data)) { |
| 552 | + res.writeHead.apply(res, data) |
| 553 | + return false |
| 554 | + } |
| 555 | + return true |
| 556 | + }), |
| 557 | + toPull(res) |
| 558 | + ) |
| 559 | +} |
639 | 560 | |
640 | | - case 'new-issue': |
641 | | - var msg = Issues.schemas.new(dir, data.title, data.text) |
642 | | - var mentions = Mentions(data.text) |
643 | | - if (mentions.length) |
644 | | - msg.mentions = mentions |
645 | | - return ssb.publish(msg, function (err, msg) { |
646 | | - if (err) return cb(null, serveError(req, err)) |
647 | | - cb(null, serveRedirect(req, encodeLink(msg.key))) |
648 | | - }) |
| 561 | +function G_onRequest(req, res) { |
| 562 | + console.log(req.method, req.url) |
| 563 | + req._u = url.parse(req.url, true) |
| 564 | + var locale = req._u.query.locale || |
| 565 | + (/locale=([^;]*)/.exec(req.headers.cookie) || [])[1] |
| 566 | + var reqLocales = req.headers['accept-language'] |
| 567 | + i18n.pickCatalog(reqLocales, locale, function (err, t) { |
| 568 | + if (err) return pull(this.serveError(req, err, 500), serve(req, res)) |
| 569 | + req._t = t |
| 570 | + req._locale = t.locale |
| 571 | + pull(this.handleRequest(req), serve(req, res)) |
| 572 | + }.bind(this)) |
| 573 | +} |
649 | 574 | |
650 | | - case 'new-pull': |
651 | | - var msg = PullRequests.schemas.new(dir, data.branch, |
652 | | - data.head_repo, data.head_branch, data.title, data.text) |
653 | | - var mentions = Mentions(data.text) |
654 | | - if (mentions.length) |
655 | | - msg.mentions = mentions |
656 | | - return ssb.publish(msg, function (err, msg) { |
657 | | - if (err) return cb(null, serveError(req, err)) |
658 | | - cb(null, serveRedirect(req, encodeLink(msg.key))) |
| 575 | +G.handleRequest = function (req) { |
| 576 | + var path = req._u.pathname.slice(1) |
| 577 | + var dirs = ref.isLink(path) ? [path] : |
| 578 | + path.split(/\/+/).map(tryDecodeURIComponent) |
| 579 | + var dir = dirs[0] |
| 580 | + |
| 581 | + if (req.method == 'POST') |
| 582 | + return this.handlePOST(req, dir) |
| 583 | + |
| 584 | + if (dir == '') |
| 585 | + return this.serveIndex(req) |
| 586 | + else if (dir == 'search') |
| 587 | + return this.serveSearch(req) |
| 588 | + else if (ref.isBlobId(dir)) |
| 589 | + return this.serveBlob(req, dir) |
| 590 | + else if (ref.isMsgId(dir)) |
| 591 | + return this.serveMessage(req, dir, dirs.slice(1)) |
| 592 | + else if (ref.isFeedId(dir)) |
| 593 | + return this.serveUserPage(req, dir, dirs.slice(1)) |
| 594 | + else if (dir == 'static') |
| 595 | + return this.serveFile(req, dirs) |
| 596 | + else if (dir == 'highlight') |
| 597 | + return this.serveFile(req, [hlCssPath].concat(dirs.slice(1)), true) |
| 598 | + else |
| 599 | + return this.serve404(req) |
| 600 | +} |
| 601 | + |
| 602 | +G.handlePOST = function (req, dir) { |
| 603 | + var self = this |
| 604 | + if (self.isPublic) |
| 605 | + return self.serveBuffer(405, req._t('error.POSTNotAllowed')) |
| 606 | + return readNext(function (cb) { |
| 607 | + readReqForm(req, function (err, data) { |
| 608 | + if (err) return cb(null, self.serveError(req, err, 400)) |
| 609 | + if (!data) return cb(null, self.serveError(req, |
| 610 | + new ParamError(req._t('error.MissingData')), 400)) |
| 611 | + |
| 612 | + switch (data.action) { |
| 613 | + case 'fork-prompt': |
| 614 | + return cb(null, self.serveRedirect(req, |
| 615 | + encodeLink([data.id, 'fork']))) |
| 616 | + |
| 617 | + case 'fork': |
| 618 | + if (!data.id) |
| 619 | + return cb(null, self.serveError(req, |
| 620 | + new ParamError(req._t('error.MissingId')), 400)) |
| 621 | + return ssbGit.createRepo(self.ssb, {upstream: data.id}, |
| 622 | + function (err, repo) { |
| 623 | + if (err) return cb(null, self.serveError(req, err)) |
| 624 | + cb(null, self.serveRedirect(req, encodeLink(repo.id))) |
659 | 625 | }) |
660 | 626 | |
661 | | - case 'markdown': |
662 | | - return cb(null, serveMarkdown(data.text, {id: data.repo})) |
| 627 | + case 'vote': |
| 628 | + var voteValue = +data.value || 0 |
| 629 | + if (!data.id) |
| 630 | + return cb(null, self.serveError(req, |
| 631 | + new ParamError(req._t('error.MissingId')), 400)) |
| 632 | + var msg = schemas.vote(data.id, voteValue) |
| 633 | + return self.ssb.publish(msg, function (err) { |
| 634 | + if (err) return cb(null, self.serveError(req, err)) |
| 635 | + cb(null, self.serveRedirect(req, req.url)) |
| 636 | + }) |
663 | 637 | |
664 | | - default: |
665 | | - cb(null, serveBuffer(400, req._t('error.UnknownAction', data))) |
666 | | - } |
| 638 | + case 'repo-name': |
| 639 | + if (!data.id) |
| 640 | + return cb(null, self.serveError(req, |
| 641 | + new ParamError(req._t('error.MissingId')), 400)) |
| 642 | + if (!data.name) |
| 643 | + return cb(null, self.serveError(req, |
| 644 | + new ParamError(req._t('error.MissingName')), 400)) |
| 645 | + var msg = schemas.name(data.id, data.name) |
| 646 | + return self.ssb.publish(msg, function (err) { |
| 647 | + if (err) return cb(null, self.serveError(req, err)) |
| 648 | + cb(null, self.serveRedirect(req, req.url)) |
667 | 649 | }) |
668 | | - }) |
669 | | - } |
670 | 650 | |
671 | | - if (dir == '') |
672 | | - return serveIndex(req) |
673 | | - else if (dir == 'search') |
674 | | - return serveSearch(req) |
675 | | - else if (ref.isBlobId(dir)) |
676 | | - return serveBlob(req, dir) |
677 | | - else if (ref.isMsgId(dir)) |
678 | | - return serveMessage(req, dir, dirs.slice(1)) |
679 | | - else if (ref.isFeedId(dir)) |
680 | | - return serveUserPage(req, dir, dirs.slice(1)) |
681 | | - else if (dir == 'static') |
682 | | - return serveFile(req, dirs) |
683 | | - else if (dir == 'highlight') |
684 | | - return serveFile(req, [hlCssPath].concat(dirs.slice(1)), true) |
685 | | - else |
686 | | - return serve404(req) |
687 | | - } |
| 651 | + case 'issue-title': |
| 652 | + if (!data.id) |
| 653 | + return cb(null, self.serveError(req, |
| 654 | + new ParamError(req._t('error.MissingId')), 400)) |
| 655 | + if (!data.name) |
| 656 | + return cb(null, self.serveError(req, |
| 657 | + new ParamError(req._t('error.MissingName')), 400)) |
| 658 | + var msg = Issues.schemas.edit(data.id, {title: data.name}) |
| 659 | + return self.ssb.publish(msg, function (err) { |
| 660 | + if (err) return cb(null, self.serveError(req, err)) |
| 661 | + cb(null, self.serveRedirect(req, req.url)) |
| 662 | + }) |
688 | 663 | |
689 | | - function serveFile(req, dirs, outside) { |
690 | | - var filename = path.resolve.apply(path, [__dirname].concat(dirs)) |
691 | | - |
692 | | - if (!outside && filename.indexOf('../') === 0) |
693 | | - return serveBuffer(403, req._t("error.403Forbidden")) |
| 664 | + case 'comment': |
| 665 | + if (!data.id) |
| 666 | + return cb(null, self.serveError(req, |
| 667 | + new ParamError(req._t('error.MissingId')), 400)) |
| 668 | + var msg = schemas.post(data.text, data.id, data.branch || data.id) |
| 669 | + msg.issue = data.issue |
| 670 | + msg.repo = data.repo |
| 671 | + if (data.open != null) |
| 672 | + Issues.schemas.reopens(msg, data.id) |
| 673 | + if (data.close != null) |
| 674 | + Issues.schemas.closes(msg, data.id) |
| 675 | + var mentions = Mentions(data.text) |
| 676 | + if (mentions.length) |
| 677 | + msg.mentions = mentions |
| 678 | + return self.ssb.publish(msg, function (err) { |
| 679 | + if (err) return cb(null, self.serveError(req, err)) |
| 680 | + cb(null, self.serveRedirect(req, req.url)) |
| 681 | + }) |
694 | 682 | |
695 | | - return readNext(function (cb) { |
696 | | - fs.stat(filename, function (err, stats) { |
697 | | - cb(null, err ? |
698 | | - err.code == 'ENOENT' ? serve404(req) |
699 | | - : serveBuffer(500, err.message) |
700 | | - : 'if-modified-since' in req.headers && |
701 | | - new Date(req.headers['if-modified-since']) >= stats.mtime ? |
702 | | - pull.once([304]) |
703 | | - : stats.isDirectory() ? |
704 | | - serveBuffer(403, req._t('error.DirectoryNotListable')) |
705 | | - : cat([ |
706 | | - pull.once([200, { |
707 | | - 'Content-Type': getContentType(filename), |
708 | | - 'Content-Length': stats.size, |
709 | | - 'Last-Modified': stats.mtime.toGMTString() |
710 | | - }]), |
711 | | - toPull(fs.createReadStream(filename)) |
712 | | - ])) |
713 | | - }) |
| 683 | + case 'new-issue': |
| 684 | + var msg = Issues.schemas.new(dir, data.title, data.text) |
| 685 | + var mentions = Mentions(data.text) |
| 686 | + if (mentions.length) |
| 687 | + msg.mentions = mentions |
| 688 | + return self.ssb.publish(msg, function (err, msg) { |
| 689 | + if (err) return cb(null, self.serveError(req, err)) |
| 690 | + cb(null, self.serveRedirect(req, encodeLink(msg.key))) |
| 691 | + }) |
| 692 | + |
| 693 | + case 'new-pull': |
| 694 | + var msg = PullRequests.schemas.new(dir, data.branch, |
| 695 | + data.head_repo, data.head_branch, data.title, data.text) |
| 696 | + var mentions = Mentions(data.text) |
| 697 | + if (mentions.length) |
| 698 | + msg.mentions = mentions |
| 699 | + return self.ssb.publish(msg, function (err, msg) { |
| 700 | + if (err) return cb(null, self.serveError(req, err)) |
| 701 | + cb(null, self.serveRedirect(req, encodeLink(msg.key))) |
| 702 | + }) |
| 703 | + |
| 704 | + case 'markdown': |
| 705 | + return cb(null, self.serveMarkdown(data.text, {id: data.repo})) |
| 706 | + |
| 707 | + default: |
| 708 | + cb(null, self.serveBuffer(400, req._t('error.UnknownAction', data))) |
| 709 | + } |
714 | 710 | }) |
715 | | - } |
| 711 | + }) |
| 712 | +} |
716 | 713 | |
717 | | - function servePlainError(code, msg) { |
718 | | - return pull.values([ |
719 | | - [code, { |
720 | | - 'Content-Length': Buffer.byteLength(msg), |
721 | | - 'Content-Type': 'text/plain; charset=utf-8' |
722 | | - }], |
723 | | - msg |
724 | | - ]) |
725 | | - } |
| 714 | +G.serveFile = function (req, dirs, outside) { |
| 715 | + var filename = path.resolve.apply(path, [__dirname].concat(dirs)) |
| 716 | + |
| 717 | + if (!outside && filename.indexOf('../') === 0) |
| 718 | + return this.serveBuffer(403, req._t("error.403Forbidden")) |
726 | 719 | |
727 | | - function serveBuffer(code, buf, contentType, headers) { |
728 | | - headers = headers || {} |
729 | | - headers['Content-Type'] = contentType || 'text/plain; charset=utf-8' |
730 | | - headers['Content-Length'] = Buffer.byteLength(buf) |
731 | | - return pull.values([ |
732 | | - [code, headers], |
733 | | - buf |
734 | | - ]) |
735 | | - } |
| 720 | + return readNext(function (cb) { |
| 721 | + fs.stat(filename, function (err, stats) { |
| 722 | + cb(null, err ? |
| 723 | + err.code == 'ENOENT' ? this.serve404(req) |
| 724 | + : this.serveBuffer(500, err.message) |
| 725 | + : 'if-modified-since' in req.headers && |
| 726 | + new Date(req.headers['if-modified-since']) >= stats.mtime ? |
| 727 | + pull.once([304]) |
| 728 | + : stats.isDirectory() ? |
| 729 | + this.serveBuffer(403, req._t('error.DirectoryNotListable')) |
| 730 | + : cat([ |
| 731 | + pull.once([200, { |
| 732 | + 'Content-Type': getContentType(filename), |
| 733 | + 'Content-Length': stats.size, |
| 734 | + 'Last-Modified': stats.mtime.toGMTString() |
| 735 | + }]), |
| 736 | + toPull(fs.createReadStream(filename)) |
| 737 | + ])) |
| 738 | + }.bind(this)) |
| 739 | + }.bind(this)) |
| 740 | +} |
736 | 741 | |
737 | | - function serve404(req) { |
738 | | - return serveBuffer(404, req._t("error.404NotFound")) |
739 | | - } |
| 742 | +G.serveBuffer = function (code, buf, contentType, headers) { |
| 743 | + headers = headers || {} |
| 744 | + headers['Content-Type'] = contentType || 'text/plain; charset=utf-8' |
| 745 | + headers['Content-Length'] = Buffer.byteLength(buf) |
| 746 | + return pull.values([ |
| 747 | + [code, headers], |
| 748 | + buf |
| 749 | + ]) |
| 750 | +} |
740 | 751 | |
741 | | - function serveRedirect(req, path) { |
742 | | - return serveBuffer(302, |
743 | | - '<!doctype><html><head>' + |
744 | | - '<title>' + req._t('Redirect') + '</title></head><body>' + |
745 | | - '<p><a href="' + escapeHTML(path) + '">' + |
746 | | - req._t('Continue') + '</a></p>' + |
747 | | - '</body></html>', 'text/html; charset=utf-8', {Location: path}) |
748 | | - } |
| 752 | +G.serve404 = function (req) { |
| 753 | + return this.serveBuffer(404, req._t("error.404NotFound")) |
| 754 | +} |
749 | 755 | |
750 | | - function serveMarkdown(text, repo) { |
751 | | - return serveBuffer(200, markdown(text, repo), 'text/html; charset=utf-8') |
752 | | - } |
| 756 | +G.serveRedirect = function (req, path) { |
| 757 | + return this.serveBuffer(302, |
| 758 | + '<!doctype><html><head>' + |
| 759 | + '<title>' + req._t('Redirect') + '</title></head><body>' + |
| 760 | + '<p><a href="' + escapeHTML(path) + '">' + |
| 761 | + req._t('Continue') + '</a></p>' + |
| 762 | + '</body></html>', 'text/html; charset=utf-8', {Location: path}) |
| 763 | +} |
753 | 764 | |
754 | | - function renderError(err, tag) { |
755 | | - tag = tag || 'h3' |
756 | | - return '<' + tag + '>' + err.name + '</' + tag + '>' + |
757 | | - '<pre>' + escapeHTML(err.stack) + '</pre>' |
758 | | - } |
| 765 | +G.serveMarkdown = function (text, repo) { |
| 766 | + return this.serveBuffer(200, markdown(text, repo), |
| 767 | + 'text/html; charset=utf-8') |
| 768 | +} |
759 | 769 | |
760 | | - function renderTry(read) { |
761 | | - var ended |
762 | | - return function (end, cb) { |
763 | | - if (ended) return cb(ended) |
764 | | - read(end, function (err, data) { |
765 | | - if (err === true) |
766 | | - cb(true) |
767 | | - else if (err) { |
768 | | - ended = true |
769 | | - cb(null, renderError(err, 'h3')) |
770 | | - } else |
771 | | - cb(null, data) |
772 | | - }) |
773 | | - } |
774 | | - } |
| 770 | +function renderError(err, tag) { |
| 771 | + tag = tag || 'h3' |
| 772 | + return '<' + tag + '>' + err.name + '</' + tag + '>' + |
| 773 | + '<pre>' + escapeHTML(err.stack) + '</pre>' |
| 774 | +} |
775 | 775 | |
776 | | - function serveTemplate(req, title, code, read) { |
777 | | - if (read === undefined) return serveTemplate.bind(this, req, title, code) |
778 | | - var q = req._u.query.q && escapeHTML(req._u.query.q) || '' |
779 | | - var app = 'git ssb' |
780 | | - if (req._t) app = req._t(app) |
781 | | - return cat([ |
782 | | - pull.values([ |
783 | | - [code || 200, { |
784 | | - 'Content-Type': 'text/html' |
785 | | - }], |
786 | | - '<!doctype html><html><head><meta charset=utf-8>', |
787 | | - '<title>' + (title || app) + '</title>', |
788 | | - '<link rel=stylesheet href="/static/styles.css"/>', |
789 | | - '<link rel=stylesheet href="/highlight/github.css"/>', |
790 | | - '</head>\n', |
791 | | - '<body>', |
792 | | - '<header><form action="/search" method="get">' + |
793 | | - '<h1><a href="/">' + app + '' + |
794 | | - (ssbAppname != 'ssb' ? ' <sub>' + ssbAppname + '</sub>' : '') + |
795 | | - '</a> ' + |
796 | | - '<input class="search-bar" name="q" size="60"' + |
797 | | - ' placeholder="🔍" value="' + q + '" />' + |
798 | | - '</h1>', |
799 | | - '</form></header>', |
800 | | - '<article>']), |
801 | | - renderTry(read), |
802 | | - pull.once('<hr/></article></body></html>') |
803 | | - ]) |
| 776 | +function renderTry(read) { |
| 777 | + var ended |
| 778 | + return function (end, cb) { |
| 779 | + if (ended) return cb(ended) |
| 780 | + read(end, function (err, data) { |
| 781 | + if (err === true) |
| 782 | + cb(true) |
| 783 | + else if (err) { |
| 784 | + ended = true |
| 785 | + cb(null, renderError(err, 'h3')) |
| 786 | + } else |
| 787 | + cb(null, data) |
| 788 | + }) |
804 | 789 | } |
| 790 | +} |
805 | 791 | |
806 | | - function serveError(req, err, status) { |
807 | | - if (err.message == 'stream is closed') |
808 | | - reconnect() |
809 | | - return pull( |
810 | | - pull.once(renderError(err, 'h2')), |
811 | | - serveTemplate(req, err.name, status || 500) |
812 | | - ) |
813 | | - } |
| 792 | +G.serveTemplate = function (req, title, code, read) { |
| 793 | + if (read === undefined) |
| 794 | + return this.serveTemplate.bind(this, req, title, code) |
| 795 | + var q = req._u.query.q && escapeHTML(req._u.query.q) || '' |
| 796 | + var app = 'git ssb' |
| 797 | + var appName = this.ssbAppname |
| 798 | + if (req._t) app = req._t(app) |
| 799 | + return cat([ |
| 800 | + pull.values([ |
| 801 | + [code || 200, { |
| 802 | + 'Content-Type': 'text/html' |
| 803 | + }], |
| 804 | + '<!doctype html><html><head><meta charset=utf-8>', |
| 805 | + '<title>' + (title || app) + '</title>', |
| 806 | + '<link rel=stylesheet href="/static/styles.css"/>', |
| 807 | + '<link rel=stylesheet href="/highlight/github.css"/>', |
| 808 | + '</head>\n', |
| 809 | + '<body>', |
| 810 | + '<header><form action="/search" method="get">' + |
| 811 | + '<h1><a href="/">' + app + '' + |
| 812 | + (appName != 'ssb' ? ' <sub>' + appName + '</sub>' : '') + |
| 813 | + '</a> ' + |
| 814 | + '<input class="search-bar" name="q" size="60"' + |
| 815 | + ' placeholder="🔍" value="' + q + '" />' + |
| 816 | + '</h1>', |
| 817 | + '</form></header>', |
| 818 | + '<article>']), |
| 819 | + renderTry(read), |
| 820 | + pull.once('<hr/></article></body></html>') |
| 821 | + ]) |
| 822 | +} |
814 | 823 | |
815 | | - function renderObjectData(obj, filename, repo, rev, path) { |
816 | | - var ext = getExtension(filename) |
817 | | - return readOnce(function (cb) { |
818 | | - readObjectString(obj, function (err, buf) { |
819 | | - buf = buf.toString('utf8') |
820 | | - if (err) return cb(err) |
821 | | - cb(null, (ext == 'md' || ext == 'markdown') |
822 | | - ? markdown(buf, {repo: repo, rev: rev, path: path}) |
823 | | - : renderCodeTable(buf, ext)) |
824 | | - }) |
| 824 | +G.serveError = function (req, err, status) { |
| 825 | + if (err.message == 'stream is closed') |
| 826 | + this.reconnect && this.reconnect() |
| 827 | + return pull( |
| 828 | + pull.once(renderError(err, 'h2')), |
| 829 | + this.serveTemplate(req, err.name, status || 500) |
| 830 | + ) |
| 831 | +} |
| 832 | + |
| 833 | +function renderObjectData(obj, filename, repo, rev, path) { |
| 834 | + var ext = getExtension(filename) |
| 835 | + return readOnce(function (cb) { |
| 836 | + readObjectString(obj, function (err, buf) { |
| 837 | + buf = buf.toString('utf8') |
| 838 | + if (err) return cb(err) |
| 839 | + cb(null, (ext == 'md' || ext == 'markdown') |
| 840 | + ? markdown(buf, {repo: repo, rev: rev, path: path}) |
| 841 | + : renderCodeTable(buf, ext)) |
825 | 842 | }) |
826 | | - } |
| 843 | + }) |
| 844 | +} |
827 | 845 | |
828 | | - function renderCodeTable(buf, ext) { |
829 | | - return '<pre><table class="code">' + |
830 | | - highlight(buf, ext).split('\n').map(function (line, i) { |
831 | | - i++ |
832 | | - return '<tr id="L' + i + '">' + |
833 | | - '<td class="code-linenum">' + '<a href="#L' + i + '">' + i + '</td>' + |
834 | | - '<td class="code-text">' + line + '</td></tr>' |
835 | | - }).join('') + |
836 | | - '</table></pre>' |
837 | | - } |
| 846 | +function renderCodeTable(buf, ext) { |
| 847 | + return '<pre><table class="code">' + |
| 848 | + highlight(buf, ext).split('\n').map(function (line, i) { |
| 849 | + i++ |
| 850 | + return '<tr id="L' + i + '">' + |
| 851 | + '<td class="code-linenum">' + '<a href="#L' + i + '">' + i + '</td>' + |
| 852 | + '<td class="code-text">' + line + '</td></tr>' |
| 853 | + }).join('') + |
| 854 | + '</table></pre>' |
| 855 | +} |
838 | 856 | |
839 | | - |
| 857 | + |
840 | 858 | |
841 | | - function renderFeed(req, feedId, filter) { |
842 | | - var query = req._u.query |
843 | | - var opts = { |
844 | | - reverse: !query.forwards, |
845 | | - lt: query.lt && +query.lt || Date.now(), |
846 | | - gt: query.gt && +query.gt, |
847 | | - id: feedId |
848 | | - } |
849 | | - return pull( |
850 | | - feedId ? ssb.createUserStream(opts) : ssb.createFeedStream(opts), |
851 | | - pull.filter(function (msg) { |
852 | | - var c = msg.value.content |
853 | | - return c.type in msgTypes |
854 | | - || (c.type == 'post' && c.repo && c.issue) |
855 | | - }), |
856 | | - typeof filter == 'function' ? filter(opts) : filter, |
857 | | - pull.take(20), |
858 | | - addAuthorName(about), |
859 | | - query.forwards && pullReverse(), |
860 | | - paginate( |
861 | | - function (first, cb) { |
862 | | - if (!query.lt && !query.gt) return cb(null, '') |
863 | | - var gt = feedId ? first.value.sequence : first.value.timestamp + 1 |
864 | | - query.gt = gt |
865 | | - query.forwards = 1 |
866 | | - delete query.lt |
867 | | - cb(null, '<a href="?' + qs.stringify(query) + '">' + |
868 | | - req._t('Newer') + '</a>') |
869 | | - }, |
870 | | - paramap(renderFeedItem.bind(null, req), 8), |
871 | | - function (last, cb) { |
872 | | - query.lt = feedId ? last.value.sequence : last.value.timestamp - 1 |
| 859 | +G.renderFeed = function (req, feedId, filter) { |
| 860 | + var query = req._u.query |
| 861 | + var opts = { |
| 862 | + reverse: !query.forwards, |
| 863 | + lt: query.lt && +query.lt || Date.now(), |
| 864 | + gt: query.gt ? +query.gt : -Infinity, |
| 865 | + id: feedId |
| 866 | + } |
| 867 | + return pull( |
| 868 | + feedId ? this.ssb.createUserStream(opts) : this.ssb.createFeedStream(opts), |
| 869 | + pull.filter(function (msg) { |
| 870 | + var c = msg.value.content |
| 871 | + return c.type in msgTypes |
| 872 | + || (c.type == 'post' && c.repo && c.issue) |
| 873 | + }), |
| 874 | + typeof filter == 'function' ? filter(opts) : filter, |
| 875 | + pull.take(20), |
| 876 | + this.addAuthorName(), |
| 877 | + query.forwards && pullReverse(), |
| 878 | + paginate( |
| 879 | + function (first, cb) { |
| 880 | + if (!query.lt && !query.gt) return cb(null, '') |
| 881 | + var gt = feedId ? first.value.sequence : first.value.timestamp + 1 |
| 882 | + query.gt = gt |
| 883 | + query.forwards = 1 |
| 884 | + delete query.lt |
| 885 | + cb(null, '<a href="?' + qs.stringify(query) + '">' + |
| 886 | + req._t('Newer') + '</a>') |
| 887 | + }, |
| 888 | + paramap(this.renderFeedItem.bind(this, req), 8), |
| 889 | + function (last, cb) { |
| 890 | + query.lt = feedId ? last.value.sequence : last.value.timestamp - 1 |
| 891 | + delete query.gt |
| 892 | + delete query.forwards |
| 893 | + cb(null, '<a href="?' + qs.stringify(query) + '">' + |
| 894 | + req._t('Older') + '</a>') |
| 895 | + }, |
| 896 | + function (cb) { |
| 897 | + if (query.forwards) { |
873 | 898 | delete query.gt |
874 | 899 | delete query.forwards |
875 | | - cb(null, '<a href="?' + qs.stringify(query) + '">' + |
876 | | - req._t('Older') + '</a>') |
877 | | - }, |
878 | | - function (cb) { |
879 | | - if (query.forwards) { |
880 | | - delete query.gt |
881 | | - delete query.forwards |
882 | | - query.lt = opts.gt + 1 |
883 | | - } else { |
884 | | - delete query.lt |
885 | | - query.gt = opts.lt - 1 |
886 | | - query.forwards = 1 |
887 | | - } |
888 | | - cb(null, '<a href="?' + qs.stringify(query) + '">' + |
889 | | - req._t(query.forwards ? 'Older' : 'Newer') + '</a>') |
| 900 | + query.lt = opts.gt + 1 |
| 901 | + } else { |
| 902 | + delete query.lt |
| 903 | + query.gt = opts.lt - 1 |
| 904 | + query.forwards = 1 |
890 | 905 | } |
891 | | - ) |
| 906 | + cb(null, '<a href="?' + qs.stringify(query) + '">' + |
| 907 | + req._t(query.forwards ? 'Older' : 'Newer') + '</a>') |
| 908 | + } |
892 | 909 | ) |
893 | | - } |
| 910 | + ) |
| 911 | +} |
894 | 912 | |
895 | | - function renderFeedItem(req, msg, cb) { |
896 | | - var c = msg.value.content |
897 | | - var msgLink = link([msg.key], |
898 | | - new Date(msg.value.timestamp).toLocaleString(req._locale)) |
899 | | - var author = msg.value.author |
900 | | - var authorLink = link([msg.value.author], msg.authorName) |
901 | | - switch (c.type) { |
902 | | - case 'git-repo': |
903 | | - var done = multicb({ pluck: 1, spread: true }) |
904 | | - getRepoName(about, author, msg.key, done()) |
905 | | - if (c.upstream) { |
906 | | - return getMsg(c.upstream, function (err, upstreamMsg) { |
907 | | - if (err) return cb(null, serveError(req, err)) |
908 | | - getRepoName(about, upstreamMsg.author, c.upstream, done()) |
909 | | - done(function (err, repoName, upstreamName) { |
910 | | - cb(null, '<section class="collapse">' + msgLink + '<br>' + |
911 | | - req._t('Forked', { |
912 | | - name: authorLink, |
913 | | - upstream: link([c.upstream], upstreamName), |
914 | | - repo: link([msg.key], repoName) |
915 | | - }) + '</section>') |
916 | | - }) |
917 | | - }) |
918 | | - } else { |
919 | | - return done(function (err, repoName) { |
920 | | - if (err) return cb(err) |
921 | | - var repoLink = link([msg.key], repoName) |
| 913 | +G.renderFeedItem = function (req, msg, cb) { |
| 914 | + var self = this |
| 915 | + var c = msg.value.content |
| 916 | + var msgLink = link([msg.key], |
| 917 | + new Date(msg.value.timestamp).toLocaleString(req._locale)) |
| 918 | + var author = msg.value.author |
| 919 | + var authorLink = link([msg.value.author], msg.authorName) |
| 920 | + switch (c.type) { |
| 921 | + case 'git-repo': |
| 922 | + var done = multicb({ pluck: 1, spread: true }) |
| 923 | + self.getRepoName(author, msg.key, done()) |
| 924 | + if (c.upstream) { |
| 925 | + return self.getMsg(c.upstream, function (err, upstreamMsg) { |
| 926 | + if (err) return cb(null, self.serveError(req, err)) |
| 927 | + self.getRepoName(upstreamMsg.author, c.upstream, done()) |
| 928 | + done(function (err, repoName, upstreamName) { |
922 | 929 | cb(null, '<section class="collapse">' + msgLink + '<br>' + |
923 | | - req._t('CreatedRepo', { |
| 930 | + req._t('Forked', { |
924 | 931 | name: authorLink, |
925 | | - repo: repoLink |
| 932 | + upstream: link([c.upstream], upstreamName), |
| 933 | + repo: link([msg.key], repoName) |
926 | 934 | }) + '</section>') |
927 | 935 | }) |
928 | | - } |
929 | | - case 'git-update': |
930 | | - return getRepoName(about, author, c.repo, function (err, repoName) { |
| 936 | + }) |
| 937 | + } else { |
| 938 | + return done(function (err, repoName) { |
931 | 939 | if (err) return cb(err) |
932 | | - var repoLink = link([c.repo], repoName) |
| 940 | + var repoLink = link([msg.key], repoName) |
933 | 941 | cb(null, '<section class="collapse">' + msgLink + '<br>' + |
934 | | - req._t('Pushed', { |
| 942 | + req._t('CreatedRepo', { |
935 | 943 | name: authorLink, |
936 | 944 | repo: repoLink |
937 | 945 | }) + '</section>') |
938 | 946 | }) |
939 | | - case 'issue': |
940 | | - case 'pull-request': |
941 | | - var issueLink = link([msg.key], c.title) |
942 | | - return getMsg(c.project, function (err, projectMsg) { |
943 | | - if (err) return cb(null, serveRepoNotFound(req, c.repo, err)) |
944 | | - getRepoName(about, projectMsg.author, c.project, |
945 | | - function (err, repoName) { |
946 | | - if (err) return cb(err) |
947 | | - var repoLink = link([c.project], repoName) |
948 | | - cb(null, '<section class="collapse">' + msgLink + '<br>' + |
949 | | - req._t('OpenedIssue', { |
950 | | - name: authorLink, |
951 | | - type: req._t(c.type == 'pull-request' ? |
952 | | - 'pull request' : 'issue.'), |
953 | | - title: issueLink, |
954 | | - project: repoLink |
955 | | - }) + '</section>') |
956 | | - }) |
957 | | - }) |
958 | | - case 'about': |
| 947 | + } |
| 948 | + case 'git-update': |
| 949 | + return self.getRepoName(author, c.repo, function (err, repoName) { |
| 950 | + if (err) return cb(err) |
| 951 | + var repoLink = link([c.repo], repoName) |
| 952 | + cb(null, '<section class="collapse">' + msgLink + '<br>' + |
| 953 | + req._t('Pushed', { |
| 954 | + name: authorLink, |
| 955 | + repo: repoLink |
| 956 | + }) + '</section>') |
| 957 | + }) |
| 958 | + case 'issue': |
| 959 | + case 'pull-request': |
| 960 | + var issueLink = link([msg.key], c.title) |
| 961 | + return self.getMsg(c.project, function (err, projectMsg) { |
| 962 | + if (err) return cb(null, self.serveRepoNotFound(req, c.repo, err)) |
| 963 | + self.getRepoName(projectMsg.author, c.project, |
| 964 | + function (err, repoName) { |
| 965 | + if (err) return cb(err) |
| 966 | + var repoLink = link([c.project], repoName) |
| 967 | + cb(null, '<section class="collapse">' + msgLink + '<br>' + |
| 968 | + req._t('OpenedIssue', { |
| 969 | + name: authorLink, |
| 970 | + type: req._t(c.type == 'pull-request' ? |
| 971 | + 'pull request' : 'issue.'), |
| 972 | + title: issueLink, |
| 973 | + project: repoLink |
| 974 | + }) + '</section>') |
| 975 | + }) |
| 976 | + }) |
| 977 | + case 'about': |
| 978 | + return cb(null, '<section class="collapse">' + msgLink + '<br>' + |
| 979 | + req._t('Named', { |
| 980 | + author: authorLink, |
| 981 | + target: '<tt>' + escapeHTML(c.about) + '</tt>', |
| 982 | + name: link([c.about], c.name) |
| 983 | + }) + '</section>') |
| 984 | + case 'post': |
| 985 | + return this.pullReqs.get(c.issue, function (err, pr) { |
| 986 | + if (err) return cb(err) |
| 987 | + var type = pr.msg.value.content.type == 'pull-request' ? |
| 988 | + 'pull request' : 'issue.' |
959 | 989 | return cb(null, '<section class="collapse">' + msgLink + '<br>' + |
960 | | - req._t('Named', { |
| 990 | + req._t('CommentedOn', { |
961 | 991 | author: authorLink, |
962 | | - target: '<tt>' + escapeHTML(c.about) + '</tt>', |
963 | | - name: link([c.about], c.name) |
964 | | - }) + '</section>') |
965 | | - case 'post': |
966 | | - return pullReqs.get(c.issue, function (err, pr) { |
967 | | - if (err) return cb(err) |
968 | | - var type = pr.msg.value.content.type == 'pull-request' ? |
969 | | - 'pull request' : 'issue.' |
970 | | - return cb(null, '<section class="collapse">' + msgLink + '<br>' + |
971 | | - req._t('CommentedOn', { |
972 | | - author: authorLink, |
973 | | - target: req._t(type) + ' ' + link([pr.id], pr.title, true) |
974 | | - }) + |
975 | | - '<blockquote>' + markdown(c.text) + '</blockquote>' + |
976 | | - '</section>') |
977 | | - }) |
978 | | - default: |
979 | | - return cb(null, json(msg)) |
980 | | - } |
| 992 | + target: req._t(type) + ' ' + link([pr.id], pr.title, true) |
| 993 | + }) + |
| 994 | + '<blockquote>' + markdown(c.text) + '</blockquote>' + |
| 995 | + '</section>') |
| 996 | + }) |
| 997 | + default: |
| 998 | + return cb(null, json(msg)) |
981 | 999 | } |
| 1000 | +} |
982 | 1001 | |
983 | | - |
| 1002 | + |
984 | 1003 | |
985 | | - function serveIndex(req) { |
986 | | - return serveTemplate(req)(renderFeed(req)) |
987 | | - } |
| 1004 | +G.serveIndex = function (req) { |
| 1005 | + return this.serveTemplate(req)(this.renderFeed(req)) |
| 1006 | +} |
988 | 1007 | |
989 | | - function serveUserPage(req, feedId, dirs) { |
990 | | - switch (dirs[0]) { |
991 | | - case undefined: |
992 | | - case '': |
993 | | - case 'activity': |
994 | | - return serveUserActivity(req, feedId) |
995 | | - case 'repos': |
996 | | - return serveUserRepos(req, feedId) |
997 | | - } |
| 1008 | +G.serveUserPage = function (req, feedId, dirs) { |
| 1009 | + switch (dirs[0]) { |
| 1010 | + case undefined: |
| 1011 | + case '': |
| 1012 | + case 'activity': |
| 1013 | + return this.serveUserActivity(req, feedId) |
| 1014 | + case 'repos': |
| 1015 | + return this.serveUserRepos(req, feedId) |
998 | 1016 | } |
| 1017 | +} |
999 | 1018 | |
1000 | | - function renderUserPage(req, feedId, page, titleTemplate, body) { |
1001 | | - return readNext(function (cb) { |
1002 | | - about.getName(feedId, function (err, name) { |
1003 | | - if (err) return cb(err) |
1004 | | - var title = titleTemplate ? titleTemplate |
1005 | | - .replace(/\%{name\}/g, escapeHTML(name)) |
1006 | | - : escapeHTML(name) |
1007 | | - cb(null, serveTemplate(req, title)(cat([ |
1008 | | - pull.once('<h2>' + link([feedId], name) + |
1009 | | - '<code class="user-id">' + feedId + '</code></h2>' + |
1010 | | - nav([ |
1011 | | - [[feedId], req._t('Activity'), 'activity'], |
1012 | | - [[feedId, 'repos'], req._t('Repos'), 'repos'] |
1013 | | - ], page)), |
1014 | | - body |
1015 | | - ]))) |
1016 | | - }) |
| 1019 | +G.renderUserPage = function (req, feedId, page, titleTemplate, body) { |
| 1020 | + var self = this |
| 1021 | + return readNext(function (cb) { |
| 1022 | + self.about.getName(feedId, function (err, name) { |
| 1023 | + if (err) return cb(err) |
| 1024 | + var title = titleTemplate ? titleTemplate |
| 1025 | + .replace(/\%{name\}/g, escapeHTML(name)) |
| 1026 | + : escapeHTML(name) |
| 1027 | + cb(null, self.serveTemplate(req, title)(cat([ |
| 1028 | + pull.once('<h2>' + link([feedId], name) + |
| 1029 | + '<code class="user-id">' + feedId + '</code></h2>' + |
| 1030 | + nav([ |
| 1031 | + [[feedId], req._t('Activity'), 'activity'], |
| 1032 | + [[feedId, 'repos'], req._t('Repos'), 'repos'] |
| 1033 | + ], page)), |
| 1034 | + body |
| 1035 | + ]))) |
1017 | 1036 | }) |
1018 | | - } |
| 1037 | + }) |
| 1038 | +} |
1019 | 1039 | |
1020 | | - function serveUserActivity(req, feedId) { |
1021 | | - return renderUserPage(req, feedId, 'activity', null, |
1022 | | - renderFeed(req, feedId)) |
1023 | | - } |
| 1040 | +G.serveUserActivity = function (req, feedId) { |
| 1041 | + return this.renderUserPage(req, feedId, 'activity', null, |
| 1042 | + this.renderFeed(req, feedId)) |
| 1043 | +} |
1024 | 1044 | |
1025 | | - function serveUserRepos(req, feedId) { |
1026 | | - var title = req._t('UsersRepos', {name: '%{name}'}) |
1027 | | - return renderUserPage(req, feedId, 'repos', title, pull( |
1028 | | - cat([ |
1029 | | - ssb.messagesByType({ |
1030 | | - type: 'git-update', |
1031 | | - reverse: true |
1032 | | - }), |
1033 | | - ssb.messagesByType({ |
1034 | | - type: 'git-repo', |
1035 | | - reverse: true |
1036 | | - }) |
1037 | | - ]), |
1038 | | - pull.filter(function (msg) { |
1039 | | - return msg.value.author == feedId |
| 1045 | +G.serveUserRepos = function (req, feedId) { |
| 1046 | + var self = this |
| 1047 | + var title = req._t('UsersRepos', {name: '%{name}'}) |
| 1048 | + return this.renderUserPage(req, feedId, 'repos', title, pull( |
| 1049 | + cat([ |
| 1050 | + self.ssb.messagesByType({ |
| 1051 | + type: 'git-update', |
| 1052 | + reverse: true |
1040 | 1053 | }), |
1041 | | - pull.unique(function (msg) { |
1042 | | - return msg.value.content.repo || msg.key |
1043 | | - }), |
1044 | | - pull.take(20), |
1045 | | - paramap(function (msg, cb) { |
1046 | | - var repoId = msg.value.content.repo || msg.key |
1047 | | - var done = multicb({ pluck: 1, spread: true }) |
1048 | | - getRepoName(about, feedId, repoId, done()) |
1049 | | - getVotes(repoId, done()) |
1050 | | - done(function (err, repoName, votes) { |
1051 | | - if (err) return cb(err) |
1052 | | - cb(null, '<section class="collapse">' + |
1053 | | - '<span class="right-bar">' + |
1054 | | - '<i>✌</i> ' + |
1055 | | - link([repoId, 'digs'], votes.upvotes, true, |
1056 | | - ' title="' + req._t('Digs') + '"') + |
1057 | | - '</span>' + |
1058 | | - '<strong>' + link([repoId], repoName) + '</strong>' + |
1059 | | - '<div class="date-info">' + |
1060 | | - req._t(msg.value.content.type == 'git-update' ? |
1061 | | - 'UpdatedOnDate' : 'CreatedOnDate', |
1062 | | - { |
1063 | | - date: timestamp(msg.value.timestamp, req) |
1064 | | - }) + '</div>' + |
1065 | | - '</section>') |
1066 | | - }) |
1067 | | - }, 8) |
1068 | | - )) |
1069 | | - } |
| 1054 | + self.ssb.messagesByType({ |
| 1055 | + type: 'git-repo', |
| 1056 | + reverse: true |
| 1057 | + }) |
| 1058 | + ]), |
| 1059 | + pull.filter(function (msg) { |
| 1060 | + return msg.value.author == feedId |
| 1061 | + }), |
| 1062 | + pull.unique(function (msg) { |
| 1063 | + return msg.value.content.repo || msg.key |
| 1064 | + }), |
| 1065 | + pull.take(20), |
| 1066 | + paramap(function (msg, cb) { |
| 1067 | + var repoId = msg.value.content.repo || msg.key |
| 1068 | + var done = multicb({ pluck: 1, spread: true }) |
| 1069 | + self.getRepoName(feedId, repoId, done()) |
| 1070 | + self.getVotes(repoId, done()) |
| 1071 | + done(function (err, repoName, votes) { |
| 1072 | + if (err) return cb(err) |
| 1073 | + cb(null, '<section class="collapse">' + |
| 1074 | + '<span class="right-bar">' + |
| 1075 | + '<i>✌</i> ' + |
| 1076 | + link([repoId, 'digs'], votes.upvotes, true, |
| 1077 | + ' title="' + req._t('Digs') + '"') + |
| 1078 | + '</span>' + |
| 1079 | + '<strong>' + link([repoId], repoName) + '</strong>' + |
| 1080 | + '<div class="date-info">' + |
| 1081 | + req._t(msg.value.content.type == 'git-update' ? |
| 1082 | + 'UpdatedOnDate' : 'CreatedOnDate', |
| 1083 | + { |
| 1084 | + date: timestamp(msg.value.timestamp, req) |
| 1085 | + }) + '</div>' + |
| 1086 | + '</section>') |
| 1087 | + }) |
| 1088 | + }, 8) |
| 1089 | + )) |
| 1090 | +} |
1070 | 1091 | |
1071 | 1092 | |
1072 | 1093 | |
1073 | | - function serveMessage(req, id, path) { |
1074 | | - return readNext(function (cb) { |
1075 | | - ssb.get(id, function (err, msg) { |
1076 | | - if (err) return cb(null, serveError(req, err)) |
1077 | | - var c = msg.content || {} |
1078 | | - switch (c.type) { |
1079 | | - case 'git-repo': |
1080 | | - return getRepo(id, function (err, repo) { |
1081 | | - if (err) return cb(null, serveError(req, err)) |
1082 | | - cb(null, serveRepoPage(req, Repo(repo), path)) |
| 1094 | +G.serveMessage = function (req, id, path) { |
| 1095 | + var self = this |
| 1096 | + return readNext(function (cb) { |
| 1097 | + self.ssb.get(id, function (err, msg) { |
| 1098 | + if (err) return cb(null, self.serveError(req, err)) |
| 1099 | + var c = msg.content || {} |
| 1100 | + switch (c.type) { |
| 1101 | + case 'git-repo': |
| 1102 | + return self.getRepo(id, function (err, repo) { |
| 1103 | + if (err) return cb(null, self.serveError(req, err)) |
| 1104 | + cb(null, self.serveRepoPage(req, Repo(repo), path)) |
| 1105 | + }) |
| 1106 | + case 'git-update': |
| 1107 | + return self.getRepo(c.repo, function (err, repo) { |
| 1108 | + if (err) return cb(null, self.serveRepoNotFound(req, c.repo, err)) |
| 1109 | + cb(null, self.serveRepoUpdate(req, Repo(repo), id, msg, path)) |
| 1110 | + }) |
| 1111 | + case 'issue': |
| 1112 | + return self.getRepo(c.project, function (err, repo) { |
| 1113 | + if (err) return cb(null, |
| 1114 | + self.serveRepoNotFound(req, c.project, err)) |
| 1115 | + self.issues.get(id, function (err, issue) { |
| 1116 | + if (err) return cb(null, self.serveError(req, err)) |
| 1117 | + cb(null, self.serveRepoIssue(req, Repo(repo), issue, path)) |
1083 | 1118 | }) |
1084 | | - case 'git-update': |
1085 | | - return getRepo(c.repo, function (err, repo) { |
1086 | | - if (err) return cb(null, serveRepoNotFound(req, c.repo, err)) |
1087 | | - cb(null, serveRepoUpdate(req, Repo(repo), id, msg, path)) |
| 1119 | + }) |
| 1120 | + case 'pull-request': |
| 1121 | + return self.getRepo(c.repo, function (err, repo) { |
| 1122 | + if (err) return cb(null, |
| 1123 | + self.serveRepoNotFound(req, c.project, err)) |
| 1124 | + self.pullReqs.get(id, function (err, pr) { |
| 1125 | + if (err) return cb(null, self.serveError(req, err)) |
| 1126 | + cb(null, self.serveRepoPullReq(req, Repo(repo), pr, path)) |
1088 | 1127 | }) |
1089 | | - case 'issue': |
1090 | | - return getRepo(c.project, function (err, repo) { |
1091 | | - if (err) return cb(null, serveRepoNotFound(req, c.project, err)) |
1092 | | - issues.get(id, function (err, issue) { |
1093 | | - if (err) return cb(null, serveError(req, err)) |
1094 | | - cb(null, serveRepoIssue(req, Repo(repo), issue, path)) |
| 1128 | + }) |
| 1129 | + case 'issue-edit': |
| 1130 | + if (ref.isMsgId(c.issue)) { |
| 1131 | + return self.pullReqs.get(c.issue, function (err, issue) { |
| 1132 | + if (err) return cb(err) |
| 1133 | + var serve = issue.msg.value.content.type == 'pull-request' ? |
| 1134 | + self.serveRepoPullReq : self.serveRepoIssue |
| 1135 | + self.getRepo(issue.project, function (err, repo) { |
| 1136 | + if (err) { |
| 1137 | + if (!repo) return cb(null, |
| 1138 | + self.serveRepoNotFound(req, c.repo, err)) |
| 1139 | + return cb(null, self.serveError(req, err)) |
| 1140 | + } |
| 1141 | + cb(null, serve.call(self, req, Repo(repo), issue, path, id)) |
1095 | 1142 | }) |
1096 | 1143 | }) |
1097 | | - case 'pull-request': |
1098 | | - return getRepo(c.repo, function (err, repo) { |
1099 | | - if (err) return cb(null, serveRepoNotFound(req, c.project, err)) |
1100 | | - pullReqs.get(id, function (err, pr) { |
1101 | | - if (err) return cb(null, serveError(req, err)) |
1102 | | - cb(null, serveRepoPullReq(req, Repo(repo), pr, path)) |
1103 | | - }) |
| 1144 | + } |
| 1145 | + |
| 1146 | + case 'post': |
| 1147 | + if (ref.isMsgId(c.issue) && ref.isMsgId(c.repo)) { |
| 1148 | + |
| 1149 | + var done = multicb({ pluck: 1, spread: true }) |
| 1150 | + self.getRepo(c.repo, done()) |
| 1151 | + self.pullReqs.get(c.issue, done()) |
| 1152 | + return done(function (err, repo, issue) { |
| 1153 | + if (err) { |
| 1154 | + if (!repo) return cb(null, |
| 1155 | + self.serveRepoNotFound(req, c.repo, err)) |
| 1156 | + return cb(null, self.serveError(req, err)) |
| 1157 | + } |
| 1158 | + var serve = issue.msg.value.content.type == 'pull-request' ? |
| 1159 | + self.serveRepoPullReq : self.serveRepoIssue |
| 1160 | + cb(null, serve.call(self, req, Repo(repo), issue, path, id)) |
1104 | 1161 | }) |
1105 | | - case 'issue-edit': |
1106 | | - if (ref.isMsgId(c.issue)) { |
1107 | | - return pullReqs.get(c.issue, function (err, issue) { |
1108 | | - if (err) return cb(err) |
1109 | | - var serve = issue.msg.value.content.type == 'pull-request' ? |
1110 | | - serveRepoPullReq : serveRepoIssue |
1111 | | - getRepo(issue.project, function (err, repo) { |
1112 | | - if (err) { |
1113 | | - if (!repo) return cb(null, |
1114 | | - serveRepoNotFound(req, c.repo, err)) |
1115 | | - return cb(null, serveError(req, err)) |
1116 | | - } |
1117 | | - cb(null, serve(req, Repo(repo), issue, path, id)) |
1118 | | - }) |
1119 | | - }) |
1120 | | - } |
1121 | | - |
1122 | | - case 'post': |
1123 | | - if (ref.isMsgId(c.issue) && ref.isMsgId(c.repo)) { |
1124 | | - |
1125 | | - var done = multicb({ pluck: 1, spread: true }) |
1126 | | - getRepo(c.repo, done()) |
1127 | | - pullReqs.get(c.issue, done()) |
1128 | | - return done(function (err, repo, issue) { |
1129 | | - if (err) { |
1130 | | - if (!repo) return cb(null, |
1131 | | - serveRepoNotFound(req, c.repo, err)) |
1132 | | - return cb(null, serveError(req, err)) |
| 1162 | + } else if (ref.isMsgId(c.root)) { |
| 1163 | + |
| 1164 | + return self.getMsg(c.root, function (err, root) { |
| 1165 | + if (err) return cb(null, self.serveError(req, err)) |
| 1166 | + var repoId = root.content.repo || root.content.project |
| 1167 | + if (!ref.isMsgId(repoId)) |
| 1168 | + return cb(null, self.serveGenericMessage(req, id, msg, path)) |
| 1169 | + self.getRepo(repoId, function (err, repo) { |
| 1170 | + if (err) return cb(null, self.serveError(req, err)) |
| 1171 | + switch (root.content && root.content.type) { |
| 1172 | + case 'issue': |
| 1173 | + return self.issues.get(c.root, function (err, issue) { |
| 1174 | + if (err) return cb(null, self.serveError(req, err)) |
| 1175 | + return cb(null, |
| 1176 | + self.serveRepoIssue(req, Repo(repo), issue, path, id)) |
| 1177 | + }) |
| 1178 | + case 'pull-request': |
| 1179 | + return self.pullReqs.get(c.root, function (err, pr) { |
| 1180 | + if (err) return cb(null, self.serveError(req, err)) |
| 1181 | + return cb(null, |
| 1182 | + self.serveRepoPullReq(req, Repo(repo), pr, path, id)) |
| 1183 | + }) |
1133 | 1184 | } |
1134 | | - var serve = issue.msg.value.content.type == 'pull-request' ? |
1135 | | - serveRepoPullReq : serveRepoIssue |
1136 | | - cb(null, serve(req, Repo(repo), issue, path, id)) |
1137 | 1185 | }) |
1138 | | - } else if (ref.isMsgId(c.root)) { |
1139 | | - |
1140 | | - return getMsg(c.root, function (err, root) { |
1141 | | - if (err) return cb(null, serveError(req, err)) |
1142 | | - var repoId = root.content.repo || root.content.project |
1143 | | - if (!ref.isMsgId(repoId)) |
1144 | | - return cb(null, serveGenericMessage(req, id, msg, path)) |
1145 | | - getRepo(repoId, function (err, repo) { |
1146 | | - if (err) return cb(null, serveError(req, err)) |
1147 | | - switch (root.content && root.content.type) { |
1148 | | - case 'issue': |
1149 | | - return issues.get(c.root, function (err, issue) { |
1150 | | - if (err) return cb(null, serveError(req, err)) |
1151 | | - return cb(null, |
1152 | | - serveRepoIssue(req, Repo(repo), issue, path, id)) |
1153 | | - }) |
1154 | | - case 'pull-request': |
1155 | | - pullReqs.get(c.root, function (err, pr) { |
1156 | | - if (err) return cb(null, serveError(req, err)) |
1157 | | - return cb(null, |
1158 | | - serveRepoPullReq(req, Repo(repo), pr, path, id)) |
1159 | | - }) |
1160 | | - } |
1161 | | - }) |
1162 | | - }) |
1163 | | - } |
1164 | | - |
1165 | | - default: |
1166 | | - if (ref.isMsgId(c.repo)) |
1167 | | - return getRepo(c.repo, function (err, repo) { |
1168 | | - if (err) return cb(null, serveRepoNotFound(req, c.repo, err)) |
1169 | | - cb(null, serveRepoSomething(req, Repo(repo), id, msg, path)) |
1170 | | - }) |
1171 | | - else |
1172 | | - return cb(null, serveGenericMessage(req, id, msg, path)) |
1173 | | - } |
1174 | | - }) |
| 1186 | + }) |
| 1187 | + } |
| 1188 | + |
| 1189 | + default: |
| 1190 | + if (ref.isMsgId(c.repo)) |
| 1191 | + return self.getRepo(c.repo, function (err, repo) { |
| 1192 | + if (err) return cb(null, |
| 1193 | + self.serveRepoNotFound(req, c.repo, err)) |
| 1194 | + cb(null, self.serveRepoSomething(req, Repo(repo), id, msg, path)) |
| 1195 | + }) |
| 1196 | + else |
| 1197 | + return cb(null, self.serveGenericMessage(req, id, msg, path)) |
| 1198 | + } |
1175 | 1199 | }) |
1176 | | - } |
| 1200 | + }) |
| 1201 | +} |
1177 | 1202 | |
1178 | | - function serveGenericMessage(req, id, msg, path) { |
1179 | | - return serveTemplate(req, id)(pull.once( |
1180 | | - '<section><h2>' + link([id]) + '</h2>' + |
1181 | | - json(msg) + |
1182 | | - '</section>')) |
1183 | | - } |
| 1203 | +G.serveGenericMessage = function (req, id, msg, path) { |
| 1204 | + return this.serveTemplate(req, id)(pull.once( |
| 1205 | + '<section><h2>' + link([id]) + '</h2>' + |
| 1206 | + json(msg) + |
| 1207 | + '</section>')) |
| 1208 | +} |
1184 | 1209 | |
1185 | 1210 | |
1186 | 1211 | |
1187 | | - function serveRepoPage(req, repo, path) { |
1188 | | - var defaultBranch = 'master' |
1189 | | - var query = req._u.query |
| 1212 | +G.serveRepoPage = function (req, repo, path) { |
| 1213 | + var self = this |
| 1214 | + var defaultBranch = 'master' |
| 1215 | + var query = req._u.query |
1190 | 1216 | |
1191 | | - if (query.rev != null) { |
1192 | | - |
1193 | | - |
1194 | | - path[0] = path[0] || 'tree' |
1195 | | - path[1] = query.rev |
1196 | | - req._u.pathname = encodeLink([repo.id].concat(path)) |
1197 | | - delete req._u.query.rev |
1198 | | - delete req._u.search |
1199 | | - return serveRedirect(req, url.format(req._u)) |
1200 | | - } |
| 1217 | + if (query.rev != null) { |
| 1218 | + |
| 1219 | + |
| 1220 | + path[0] = path[0] || 'tree' |
| 1221 | + path[1] = query.rev |
| 1222 | + req._u.pathname = encodeLink([repo.id].concat(path)) |
| 1223 | + delete req._u.query.rev |
| 1224 | + delete req._u.search |
| 1225 | + return self.serveRedirect(req, url.format(req._u)) |
| 1226 | + } |
1201 | 1227 | |
1202 | | - |
1203 | | - return path[1] ? |
1204 | | - serveRepoPage2(req, repo, path) : |
1205 | | - readNext(function (cb) { |
1206 | | - |
1207 | | - repo.getSymRef('HEAD', true, function (err, ref) { |
1208 | | - if (err) return cb(err) |
1209 | | - repo.resolveRef(ref, function (err, rev) { |
1210 | | - path[1] = rev ? ref : null |
1211 | | - cb(null, serveRepoPage2(req, repo, path)) |
1212 | | - }) |
| 1228 | + |
| 1229 | + return path[1] ? |
| 1230 | + G_serveRepoPage2.call(self, req, repo, path) : |
| 1231 | + readNext(function (cb) { |
| 1232 | + |
| 1233 | + repo.getSymRef('HEAD', true, function (err, ref) { |
| 1234 | + if (err) return cb(err) |
| 1235 | + repo.resolveRef(ref, function (err, rev) { |
| 1236 | + path[1] = rev ? ref : null |
| 1237 | + cb(null, G_serveRepoPage2.call(self, req, repo, path)) |
1213 | 1238 | }) |
1214 | 1239 | }) |
1215 | | - } |
| 1240 | + }) |
| 1241 | +} |
1216 | 1242 | |
1217 | | - function serveRepoPage2(req, repo, path) { |
1218 | | - var branch = path[1] |
1219 | | - var filePath = path.slice(2) |
1220 | | - switch (path[0]) { |
1221 | | - case undefined: |
1222 | | - case '': |
1223 | | - return serveRepoTree(req, repo, branch, []) |
1224 | | - case 'activity': |
1225 | | - return serveRepoActivity(req, repo, branch) |
1226 | | - case 'commits': |
1227 | | - return serveRepoCommits(req, repo, branch) |
1228 | | - case 'commit': |
1229 | | - return serveRepoCommit(req, repo, path[1]) |
1230 | | - case 'tag': |
1231 | | - return serveRepoTag(req, repo, branch) |
1232 | | - case 'tree': |
1233 | | - return serveRepoTree(req, repo, branch, filePath) |
1234 | | - case 'blob': |
1235 | | - return serveRepoBlob(req, repo, branch, filePath) |
1236 | | - case 'raw': |
1237 | | - return serveRepoRaw(req, repo, branch, filePath) |
1238 | | - case 'digs': |
1239 | | - return serveRepoDigs(req, repo) |
1240 | | - case 'fork': |
1241 | | - return serveRepoForkPrompt(req, repo) |
1242 | | - case 'forks': |
1243 | | - return serveRepoForks(req, repo) |
1244 | | - case 'issues': |
1245 | | - switch (path[1]) { |
1246 | | - case 'new': |
1247 | | - if (filePath.length == 0) |
1248 | | - return serveRepoNewIssue(req, repo) |
1249 | | - break |
1250 | | - default: |
1251 | | - return serveRepoIssues(req, repo, false) |
1252 | | - } |
1253 | | - case 'pulls': |
1254 | | - return serveRepoIssues(req, repo, true) |
1255 | | - case 'compare': |
1256 | | - return serveRepoCompare(req, repo) |
1257 | | - case 'comparing': |
1258 | | - return serveRepoComparing(req, repo) |
1259 | | - default: |
1260 | | - return serve404(req) |
1261 | | - } |
| 1243 | +function G_serveRepoPage2(req, repo, path) { |
| 1244 | + var branch = path[1] |
| 1245 | + var filePath = path.slice(2) |
| 1246 | + switch (path[0]) { |
| 1247 | + case undefined: |
| 1248 | + case '': |
| 1249 | + return this.serveRepoTree(req, repo, branch, []) |
| 1250 | + case 'activity': |
| 1251 | + return this.serveRepoActivity(req, repo, branch) |
| 1252 | + case 'commits': |
| 1253 | + return this.serveRepoCommits(req, repo, branch) |
| 1254 | + case 'commit': |
| 1255 | + return this.serveRepoCommit(req, repo, path[1]) |
| 1256 | + case 'tag': |
| 1257 | + return this.serveRepoTag(req, repo, branch) |
| 1258 | + case 'tree': |
| 1259 | + return this.serveRepoTree(req, repo, branch, filePath) |
| 1260 | + case 'blob': |
| 1261 | + return this.serveRepoBlob(req, repo, branch, filePath) |
| 1262 | + case 'raw': |
| 1263 | + return this.serveRepoRaw(req, repo, branch, filePath) |
| 1264 | + case 'digs': |
| 1265 | + return this.serveRepoDigs(req, repo) |
| 1266 | + case 'fork': |
| 1267 | + return this.serveRepoForkPrompt(req, repo) |
| 1268 | + case 'forks': |
| 1269 | + return this.serveRepoForks(req, repo) |
| 1270 | + case 'issues': |
| 1271 | + switch (path[1]) { |
| 1272 | + case 'new': |
| 1273 | + if (filePath.length == 0) |
| 1274 | + return this.serveRepoNewIssue(req, repo) |
| 1275 | + break |
| 1276 | + default: |
| 1277 | + return this.serveRepoIssues(req, repo, false) |
| 1278 | + } |
| 1279 | + case 'pulls': |
| 1280 | + return this.serveRepoIssues(req, repo, true) |
| 1281 | + case 'compare': |
| 1282 | + return this.serveRepoCompare(req, repo) |
| 1283 | + case 'comparing': |
| 1284 | + return this.serveRepoComparing(req, repo) |
| 1285 | + default: |
| 1286 | + return this.serve404(req) |
1262 | 1287 | } |
| 1288 | +} |
1263 | 1289 | |
1264 | | - function serveRepoNotFound(req, id, err) { |
1265 | | - return serveTemplate(req, req._t('error.RepoNotFound'), 404)(pull.values([ |
1266 | | - '<h2>' + req._t('error.RepoNotFound') + '</h2>', |
1267 | | - '<p>' + req._t('error.RepoNameNotFound') + '</p>', |
1268 | | - '<pre>' + escapeHTML(err.stack) + '</pre>' |
1269 | | - ])) |
1270 | | - } |
| 1290 | +G.serveRepoNotFound = function (req, id, err) { |
| 1291 | + return this.serveTemplate(req, req._t('error.RepoNotFound'), 404) |
| 1292 | + (pull.values([ |
| 1293 | + '<h2>' + req._t('error.RepoNotFound') + '</h2>', |
| 1294 | + '<p>' + req._t('error.RepoNameNotFound') + '</p>', |
| 1295 | + '<pre>' + escapeHTML(err.stack) + '</pre>' |
| 1296 | + ])) |
| 1297 | +} |
1271 | 1298 | |
1272 | | - function renderRepoPage(req, repo, page, branch, titleTemplate, body) { |
1273 | | - var gitUrl = 'ssb://' + repo.id |
1274 | | - var gitLink = '<input class="clone-url" readonly="readonly" ' + |
1275 | | - 'value="' + gitUrl + '" size="45" ' + |
1276 | | - 'onclick="this.select()"/>' |
1277 | | - var digsPath = [repo.id, 'digs'] |
| 1299 | +G.renderRepoPage = function (req, repo, page, branch, titleTemplate, body) { |
| 1300 | + var self = this |
| 1301 | + var gitUrl = 'ssb://' + repo.id |
| 1302 | + var gitLink = '<input class="clone-url" readonly="readonly" ' + |
| 1303 | + 'value="' + gitUrl + '" size="45" ' + |
| 1304 | + 'onclick="this.select()"/>' |
| 1305 | + var digsPath = [repo.id, 'digs'] |
1278 | 1306 | |
1279 | | - var done = multicb({ pluck: 1, spread: true }) |
1280 | | - getRepoName(about, repo.feed, repo.id, done()) |
1281 | | - about.getName(repo.feed, done()) |
1282 | | - getVotes(repo.id, done()) |
| 1307 | + var done = multicb({ pluck: 1, spread: true }) |
| 1308 | + self.getRepoName(repo.feed, repo.id, done()) |
| 1309 | + self.about.getName(repo.feed, done()) |
| 1310 | + self.getVotes(repo.id, done()) |
1283 | 1311 | |
1284 | | - if (repo.upstream) { |
1285 | | - getRepoName(about, repo.upstream.feed, repo.upstream.id, done()) |
1286 | | - about.getName(repo.upstream.feed, done()) |
1287 | | - } |
| 1312 | + if (repo.upstream) { |
| 1313 | + self.getRepoName(repo.upstream.feed, repo.upstream.id, done()) |
| 1314 | + self.about.getName(repo.upstream.feed, done()) |
| 1315 | + } |
1288 | 1316 | |
1289 | | - return readNext(function (cb) { |
1290 | | - done(function (err, repoName, authorName, votes, |
1291 | | - upstreamName, upstreamAuthorName) { |
1292 | | - if (err) return cb(null, serveError(req, err)) |
1293 | | - var upvoted = votes.upvoters[myId] > 0 |
1294 | | - var upstreamLink = !repo.upstream ? '' : |
1295 | | - link([repo.upstream]) |
1296 | | - var title = titleTemplate ? titleTemplate |
1297 | | - .replace(/%\{repo\}/g, repoName) |
1298 | | - .replace(/%\{author\}/g, authorName) |
1299 | | - : authorName + '/' + repoName |
1300 | | - cb(null, serveTemplate(req, title)(cat([ |
1301 | | - pull.once( |
1302 | | - '<div class="repo-title">' + |
1303 | | - '<form class="right-bar" action="" method="post">' + |
1304 | | - '<button class="btn" name="action" value="vote" ' + |
1305 | | - (isPublic ? 'disabled="disabled"' : ' type="submit"') + '>' + |
1306 | | - '<i>✌</i> ' + req._t(!isPublic && upvoted ? 'Undig' : 'Dig') + |
1307 | | - '</button>' + |
1308 | | - (isPublic ? '' : '<input type="hidden" name="value" value="' + |
1309 | | - (upvoted ? '0' : '1') + '">' + |
1310 | | - '<input type="hidden" name="id" value="' + |
1311 | | - escapeHTML(repo.id) + '">') + ' ' + |
1312 | | - '<strong>' + link(digsPath, votes.upvotes) + '</strong> ' + |
1313 | | - (isPublic ? '' : '<button class="btn" type="submit" ' + |
1314 | | - ' name="action" value="fork-prompt">' + |
1315 | | - '<i>⑂</i> ' + req._t('Fork') + |
1316 | | - '</button>') + ' ' + |
1317 | | - link([repo.id, 'forks'], '+', false, ' title="' + |
1318 | | - req._t('Forks') + '"') + |
1319 | | - '</form>' + |
1320 | | - renderNameForm(req, !isPublic, repo.id, repoName, 'repo-name', |
1321 | | - null, req._t('repo.Rename'), |
1322 | | - '<h2 class="bgslash">' + link([repo.feed], authorName) + ' / ' + |
1323 | | - link([repo.id], repoName) + '</h2>') + |
1324 | | - '</div>' + |
1325 | | - (repo.upstream ? '<small class="bgslash">' + req._t('ForkedFrom', { |
1326 | | - repo: link([repo.upstream.feed], upstreamAuthorName) + '/' + |
1327 | | - link([repo.upstream.id], upstreamName) |
1328 | | - }) + '</small>' : '') + |
1329 | | - nav([ |
1330 | | - [[repo.id], req._t('Code'), 'code'], |
1331 | | - [[repo.id, 'activity'], req._t('Activity'), 'activity'], |
1332 | | - [[repo.id, 'commits', branch||''], req._t('Commits'), 'commits'], |
1333 | | - [[repo.id, 'issues'], req._t('Issues'), 'issues'], |
1334 | | - [[repo.id, 'pulls'], req._t('PullRequests'), 'pulls'] |
1335 | | - ], page, gitLink)), |
1336 | | - body |
1337 | | - ]))) |
1338 | | - }) |
| 1317 | + return readNext(function (cb) { |
| 1318 | + done(function (err, repoName, authorName, votes, |
| 1319 | + upstreamName, upstreamAuthorName) { |
| 1320 | + if (err) return cb(null, self.serveError(req, err)) |
| 1321 | + var upvoted = votes.upvoters[self.myId] > 0 |
| 1322 | + var upstreamLink = !repo.upstream ? '' : |
| 1323 | + link([repo.upstream]) |
| 1324 | + var title = titleTemplate ? titleTemplate |
| 1325 | + .replace(/%\{repo\}/g, repoName) |
| 1326 | + .replace(/%\{author\}/g, authorName) |
| 1327 | + : authorName + '/' + repoName |
| 1328 | + var isPublic = self.isPublic |
| 1329 | + cb(null, self.serveTemplate(req, title)(cat([ |
| 1330 | + pull.once( |
| 1331 | + '<div class="repo-title">' + |
| 1332 | + '<form class="right-bar" action="" method="post">' + |
| 1333 | + '<button class="btn" name="action" value="vote" ' + |
| 1334 | + (isPublic ? 'disabled="disabled"' : ' type="submit"') + '>' + |
| 1335 | + '<i>✌</i> ' + req._t(!isPublic && upvoted ? 'Undig' : 'Dig') + |
| 1336 | + '</button>' + |
| 1337 | + (isPublic ? '' : '<input type="hidden" name="value" value="' + |
| 1338 | + (upvoted ? '0' : '1') + '">' + |
| 1339 | + '<input type="hidden" name="id" value="' + |
| 1340 | + escapeHTML(repo.id) + '">') + ' ' + |
| 1341 | + '<strong>' + link(digsPath, votes.upvotes) + '</strong> ' + |
| 1342 | + (isPublic ? '' : '<button class="btn" type="submit" ' + |
| 1343 | + ' name="action" value="fork-prompt">' + |
| 1344 | + '<i>⑂</i> ' + req._t('Fork') + |
| 1345 | + '</button>') + ' ' + |
| 1346 | + link([repo.id, 'forks'], '+', false, ' title="' + |
| 1347 | + req._t('Forks') + '"') + |
| 1348 | + '</form>' + |
| 1349 | + renderNameForm(req, !isPublic, repo.id, repoName, 'repo-name', |
| 1350 | + null, req._t('repo.Rename'), |
| 1351 | + '<h2 class="bgslash">' + link([repo.feed], authorName) + ' / ' + |
| 1352 | + link([repo.id], repoName) + '</h2>') + |
| 1353 | + '</div>' + |
| 1354 | + (repo.upstream ? '<small class="bgslash">' + req._t('ForkedFrom', { |
| 1355 | + repo: link([repo.upstream.feed], upstreamAuthorName) + '/' + |
| 1356 | + link([repo.upstream.id], upstreamName) |
| 1357 | + }) + '</small>' : '') + |
| 1358 | + nav([ |
| 1359 | + [[repo.id], req._t('Code'), 'code'], |
| 1360 | + [[repo.id, 'activity'], req._t('Activity'), 'activity'], |
| 1361 | + [[repo.id, 'commits', branch||''], req._t('Commits'), 'commits'], |
| 1362 | + [[repo.id, 'issues'], req._t('Issues'), 'issues'], |
| 1363 | + [[repo.id, 'pulls'], req._t('PullRequests'), 'pulls'] |
| 1364 | + ], page, gitLink)), |
| 1365 | + body |
| 1366 | + ]))) |
1339 | 1367 | }) |
1340 | | - } |
| 1368 | + }) |
| 1369 | +} |
1341 | 1370 | |
1342 | | - function serveEmptyRepo(req, repo) { |
1343 | | - if (repo.feed != myId) |
1344 | | - return renderRepoPage(req, repo, 'code', null, null, pull.once( |
1345 | | - '<section>' + |
1346 | | - '<h3>' + req._t('EmptyRepo') + '</h3>' + |
1347 | | - '</section>')) |
1348 | | - |
1349 | | - var gitUrl = 'ssb://' + repo.id |
1350 | | - return renderRepoPage(req, repo, 'code', null, null, pull.once( |
| 1371 | +G.serveEmptyRepo = function (req, repo) { |
| 1372 | + if (repo.feed != this.myId) |
| 1373 | + return this.renderRepoPage(req, repo, 'code', null, null, pull.once( |
1351 | 1374 | '<section>' + |
1352 | | - '<h3>' + req._t('initRepo.GettingStarted') + '</h3>' + |
1353 | | - '<h4>' + req._t('initRepo.CreateNew') + '</h4><pre>' + |
1354 | | - 'touch ' + req._t('initRepo.README') + '.md\n' + |
1355 | | - 'git init\n' + |
1356 | | - 'git add ' + req._t('initRepo.README') + '.md\n' + |
1357 | | - 'git commit -m "' + req._t('initRepo.InitialCommit') + '"\n' + |
1358 | | - 'git remote add origin ' + gitUrl + '\n' + |
1359 | | - 'git push -u origin master</pre>\n' + |
1360 | | - '<h4>' + req._t('initRepo.PushExisting') + '</h4>\n' + |
1361 | | - '<pre>git remote add origin ' + gitUrl + '\n' + |
1362 | | - 'git push -u origin master</pre>' + |
| 1375 | + '<h3>' + req._t('EmptyRepo') + '</h3>' + |
1363 | 1376 | '</section>')) |
1364 | | - } |
1365 | 1377 | |
1366 | | - function serveRepoTree(req, repo, rev, path) { |
1367 | | - if (!rev) return serveEmptyRepo(req, repo) |
1368 | | - var type = repo.isCommitHash(rev) ? 'Tree' : 'Branch' |
1369 | | - var title = (path.length ? path.join('/') + ' · ' : '') + |
1370 | | - '%{author}/%{repo}' + |
1371 | | - (repo.head == 'refs/heads/' + rev ? '' : '@' + rev) |
1372 | | - return renderRepoPage(req, repo, 'code', rev, title, cat([ |
1373 | | - pull.once('<section><form action="" method="get">' + |
1374 | | - '<h3>' + req._t(type) + ': ' + rev + ' '), |
1375 | | - revMenu(req, repo, rev), |
1376 | | - pull.once('</h3></form>'), |
1377 | | - type == 'Branch' && renderRepoLatest(req, repo, rev), |
1378 | | - pull.once('</section><section>'), |
1379 | | - renderRepoTree(req, repo, rev, path), |
1380 | | - pull.once('</section>'), |
1381 | | - renderRepoReadme(req, repo, rev, path) |
1382 | | - ])) |
1383 | | - } |
| 1378 | + var gitUrl = 'ssb://' + repo.id |
| 1379 | + return this.renderRepoPage(req, repo, 'code', null, null, pull.once( |
| 1380 | + '<section>' + |
| 1381 | + '<h3>' + req._t('initRepo.GettingStarted') + '</h3>' + |
| 1382 | + '<h4>' + req._t('initRepo.CreateNew') + '</h4><pre>' + |
| 1383 | + 'touch ' + req._t('initRepo.README') + '.md\n' + |
| 1384 | + 'git init\n' + |
| 1385 | + 'git add ' + req._t('initRepo.README') + '.md\n' + |
| 1386 | + 'git commit -m "' + req._t('initRepo.InitialCommit') + '"\n' + |
| 1387 | + 'git remote add origin ' + gitUrl + '\n' + |
| 1388 | + 'git push -u origin master</pre>\n' + |
| 1389 | + '<h4>' + req._t('initRepo.PushExisting') + '</h4>\n' + |
| 1390 | + '<pre>git remote add origin ' + gitUrl + '\n' + |
| 1391 | + 'git push -u origin master</pre>' + |
| 1392 | + '</section>')) |
| 1393 | +} |
1384 | 1394 | |
1385 | | - |
| 1395 | +G.serveRepoTree = function (req, repo, rev, path) { |
| 1396 | + if (!rev) return this.serveEmptyRepo(req, repo) |
| 1397 | + var type = repo.isCommitHash(rev) ? 'Tree' : 'Branch' |
| 1398 | + var title = (path.length ? path.join('/') + ' · ' : '') + |
| 1399 | + '%{author}/%{repo}' + |
| 1400 | + (repo.head == 'refs/heads/' + rev ? '' : '@' + rev) |
| 1401 | + return this.renderRepoPage(req, repo, 'code', rev, title, cat([ |
| 1402 | + pull.once('<section><form action="" method="get">' + |
| 1403 | + '<h3>' + req._t(type) + ': ' + rev + ' '), |
| 1404 | + revMenu(req, repo, rev), |
| 1405 | + pull.once('</h3></form>'), |
| 1406 | + type == 'Branch' && renderRepoLatest(req, repo, rev), |
| 1407 | + pull.once('</section><section>'), |
| 1408 | + renderRepoTree(req, repo, rev, path), |
| 1409 | + pull.once('</section>'), |
| 1410 | + renderRepoReadme(req, repo, rev, path) |
| 1411 | + ])) |
| 1412 | +} |
1386 | 1413 | |
1387 | | - function serveSearch(req) { |
1388 | | - var q = String(req._u.query.q || '') |
1389 | | - if (!q) return serveIndex(req) |
1390 | | - var qId = q.replace(/^ssb:\/*/, '') |
1391 | | - if (ref.type(qId)) |
1392 | | - return serveRedirect(req, encodeURIComponent(qId)) |
| 1414 | + |
1393 | 1415 | |
1394 | | - var search = new RegExp(q, 'i') |
1395 | | - return serveTemplate(req, req._t('Search') + ' · ' + q, 200)( |
1396 | | - renderFeed(req, null, function (opts) { |
1397 | | - opts.type == 'about' |
1398 | | - return function (read) { |
1399 | | - return pull( |
1400 | | - many([ |
1401 | | - getRepoNames(opts), |
1402 | | - read |
1403 | | - ]), |
1404 | | - pull.filter(function (msg) { |
1405 | | - var c = msg.value.content |
1406 | | - return ( |
1407 | | - search.test(msg.key) || |
1408 | | - c.text && search.test(c.text) || |
1409 | | - c.name && search.test(c.name) || |
1410 | | - c.title && search.test(c.title)) |
1411 | | - }) |
1412 | | - ) |
1413 | | - } |
1414 | | - }) |
1415 | | - ) |
1416 | | - } |
| 1416 | +G.serveSearch = function (req) { |
| 1417 | + var self = this |
| 1418 | + var q = String(req._u.query.q || '') |
| 1419 | + if (!q) return this.serveIndex(req) |
| 1420 | + var qId = q.replace(/^ssb:\/*/, '') |
| 1421 | + if (ref.type(qId)) |
| 1422 | + return this.serveRedirect(req, encodeURIComponent(qId)) |
1417 | 1423 | |
1418 | | - function getRepoNames(opts) { |
1419 | | - return pull( |
1420 | | - ssb.messagesByType({ |
1421 | | - type: 'about', |
1422 | | - reverse: opts.reverse, |
1423 | | - lt: opts.lt, |
1424 | | - gt: opts.gt, |
| 1424 | + var search = new RegExp(q, 'i') |
| 1425 | + return this.serveTemplate(req, req._t('Search') + ' · ' + q, 200)( |
| 1426 | + this.renderFeed(req, null, function (opts) { |
| 1427 | + opts.type == 'about' |
| 1428 | + return function (read) { |
| 1429 | + return pull( |
| 1430 | + many([ |
| 1431 | + self.getRepoNames(opts), |
| 1432 | + read |
| 1433 | + ]), |
| 1434 | + pull.filter(function (msg) { |
| 1435 | + var c = msg.value.content |
| 1436 | + return ( |
| 1437 | + search.test(msg.key) || |
| 1438 | + c.text && search.test(c.text) || |
| 1439 | + c.name && search.test(c.name) || |
| 1440 | + c.title && search.test(c.title)) |
| 1441 | + }) |
| 1442 | + ) |
| 1443 | + } |
| 1444 | + }) |
| 1445 | + ) |
| 1446 | +} |
| 1447 | + |
| 1448 | +G.getRepoNames = function (opts) { |
| 1449 | + return pull( |
| 1450 | + this.ssb.messagesByType({ |
| 1451 | + type: 'about', |
| 1452 | + reverse: opts.reverse, |
| 1453 | + lt: opts.lt, |
| 1454 | + gt: opts.gt, |
| 1455 | + }), |
| 1456 | + pull.filter(function (msg) { |
| 1457 | + return '%' == String(msg.value.content.about)[0] |
| 1458 | + && msg.value.content.name |
| 1459 | + }) |
| 1460 | + ) |
| 1461 | +} |
| 1462 | + |
| 1463 | + |
| 1464 | + |
| 1465 | +G.serveRepoActivity = function (req, repo, branch) { |
| 1466 | + var self = this |
| 1467 | + var title = req._t('Activity') + ' · %{author}/%{repo}' |
| 1468 | + return self.renderRepoPage(req, repo, 'activity', branch, title, cat([ |
| 1469 | + pull.once('<h3>' + req._t('Activity') + '</h3>'), |
| 1470 | + pull( |
| 1471 | + self.ssb.links({ |
| 1472 | + dest: repo.id, |
| 1473 | + source: repo.feed, |
| 1474 | + rel: 'repo', |
| 1475 | + values: true, |
| 1476 | + reverse: true |
1425 | 1477 | }), |
1426 | | - pull.filter(function (msg) { |
1427 | | - return '%' == String(msg.value.content.about)[0] |
1428 | | - && msg.value.content.name |
| 1478 | + pull.map(renderRepoUpdate.bind(self, req, repo)) |
| 1479 | + ), |
| 1480 | + readOnce(function (cb) { |
| 1481 | + var done = multicb({ pluck: 1, spread: true }) |
| 1482 | + self.about.getName(repo.feed, done()) |
| 1483 | + self.getMsg(repo.id, done()) |
| 1484 | + done(function (err, authorName, msg) { |
| 1485 | + if (err) return cb(err) |
| 1486 | + self.renderFeedItem(req, { |
| 1487 | + key: repo.id, |
| 1488 | + value: msg, |
| 1489 | + authorName: authorName |
| 1490 | + }, cb) |
1429 | 1491 | }) |
1430 | | - ) |
1431 | | - } |
| 1492 | + }) |
| 1493 | + ])) |
| 1494 | +} |
1432 | 1495 | |
1433 | | - |
| 1496 | +function renderRepoUpdate(req, repo, msg, full) { |
| 1497 | + var c = msg.value.content |
1434 | 1498 | |
1435 | | - function serveRepoActivity(req, repo, branch) { |
1436 | | - var title = req._t('Activity') + ' · %{author}/%{repo}' |
1437 | | - return renderRepoPage(req, repo, 'activity', branch, title, cat([ |
1438 | | - pull.once('<h3>' + req._t('Activity') + '</h3>'), |
1439 | | - pull( |
1440 | | - ssb.links({ |
1441 | | - dest: repo.id, |
1442 | | - source: repo.feed, |
1443 | | - rel: 'repo', |
1444 | | - values: true, |
1445 | | - reverse: true |
1446 | | - }), |
1447 | | - pull.map(renderRepoUpdate.bind(this, req, repo)) |
1448 | | - ), |
1449 | | - readOnce(function (cb) { |
1450 | | - var done = multicb({ pluck: 1, spread: true }) |
1451 | | - about.getName(repo.feed, done()) |
1452 | | - getMsg(repo.id, done()) |
1453 | | - done(function (err, authorName, msg) { |
1454 | | - if (err) return cb(err) |
1455 | | - renderFeedItem(req, { |
1456 | | - key: repo.id, |
1457 | | - value: msg, |
1458 | | - authorName: authorName |
1459 | | - }, cb) |
1460 | | - }) |
1461 | | - }) |
1462 | | - ])) |
| 1499 | + if (c.type != 'git-update') { |
| 1500 | + return '' |
| 1501 | + |
| 1502 | + |
1463 | 1503 | } |
1464 | 1504 | |
1465 | | - function renderRepoUpdate(req, repo, msg, full) { |
1466 | | - var c = msg.value.content |
| 1505 | + var branches = [] |
| 1506 | + var tags = [] |
| 1507 | + if (c.refs) for (var name in c.refs) { |
| 1508 | + var m = name.match(/^refs\/(heads|tags)\/(.*)$/) || [,, name] |
| 1509 | + ;(m[1] == 'tags' ? tags : branches) |
| 1510 | + .push({name: m[2], value: c.refs[name]}) |
| 1511 | + } |
| 1512 | + var numObjects = c.objects ? Object.keys(c.objects).length : 0 |
1467 | 1513 | |
1468 | | - if (c.type != 'git-update') { |
1469 | | - return '' |
1470 | | - |
1471 | | - |
1472 | | - } |
| 1514 | + var dateStr = new Date(msg.value.timestamp).toLocaleString(req._locale) |
| 1515 | + return '<section class="collapse">' + |
| 1516 | + link([msg.key], dateStr) + '<br>' + |
| 1517 | + branches.map(function (update) { |
| 1518 | + if (!update.value) { |
| 1519 | + return '<s>' + escapeHTML(update.name) + '</s><br/>' |
| 1520 | + } else { |
| 1521 | + var commitLink = link([repo.id, 'commit', update.value]) |
| 1522 | + var branchLink = link([repo.id, 'tree', update.name]) |
| 1523 | + return branchLink + ' → <tt>' + commitLink + '</tt><br/>' |
| 1524 | + } |
| 1525 | + }).join('') + |
| 1526 | + tags.map(function (update) { |
| 1527 | + return update.value |
| 1528 | + ? link([repo.id, 'tag', update.value], update.name) |
| 1529 | + : '<s>' + escapeHTML(update.name) + '</s>' |
| 1530 | + }).join(', ') + |
| 1531 | + '</section>' |
| 1532 | +} |
1473 | 1533 | |
1474 | | - var branches = [] |
1475 | | - var tags = [] |
1476 | | - if (c.refs) for (var name in c.refs) { |
1477 | | - var m = name.match(/^refs\/(heads|tags)\/(.*)$/) || [,, name] |
1478 | | - ;(m[1] == 'tags' ? tags : branches) |
1479 | | - .push({name: m[2], value: c.refs[name]}) |
1480 | | - } |
1481 | | - var numObjects = c.objects ? Object.keys(c.objects).length : 0 |
| 1534 | + |
1482 | 1535 | |
1483 | | - var dateStr = new Date(msg.value.timestamp).toLocaleString(req._locale) |
1484 | | - return '<section class="collapse">' + |
1485 | | - link([msg.key], dateStr) + '<br>' + |
1486 | | - branches.map(function (update) { |
1487 | | - if (!update.value) { |
1488 | | - return '<s>' + escapeHTML(update.name) + '</s><br/>' |
1489 | | - } else { |
1490 | | - var commitLink = link([repo.id, 'commit', update.value]) |
1491 | | - var branchLink = link([repo.id, 'tree', update.name]) |
1492 | | - return branchLink + ' → <tt>' + commitLink + '</tt><br/>' |
| 1536 | +G.serveRepoCommits = function (req, repo, branch) { |
| 1537 | + var query = req._u.query |
| 1538 | + var title = req._t('Commits') + ' · %{author}/%{repo}' |
| 1539 | + return this.renderRepoPage(req, repo, 'commits', branch, title, cat([ |
| 1540 | + pull.once('<h3>' + req._t('Commits') + '</h3>'), |
| 1541 | + pull( |
| 1542 | + repo.readLog(query.start || branch), |
| 1543 | + pull.take(20), |
| 1544 | + paramap(repo.getCommitParsed.bind(repo), 8), |
| 1545 | + paginate( |
| 1546 | + !query.start ? '' : function (first, cb) { |
| 1547 | + cb(null, '…') |
| 1548 | + }, |
| 1549 | + pull.map(renderCommit.bind(this, req, repo)), |
| 1550 | + function (commit, cb) { |
| 1551 | + cb(null, commit.parents && commit.parents[0] ? |
| 1552 | + '<a href="?start=' + commit.id + '">' + |
| 1553 | + req._t('Older') + '</a>' : '') |
1493 | 1554 | } |
1494 | | - }).join('') + |
1495 | | - tags.map(function (update) { |
1496 | | - return update.value |
1497 | | - ? link([repo.id, 'tag', update.value], update.name) |
1498 | | - : '<s>' + escapeHTML(update.name) + '</s>' |
1499 | | - }).join(', ') + |
1500 | | - '</section>' |
1501 | | - } |
1502 | | - |
1503 | | - |
1504 | | - |
1505 | | - function serveRepoCommits(req, repo, branch) { |
1506 | | - var query = req._u.query |
1507 | | - var title = req._t('Commits') + ' · %{author}/%{repo}' |
1508 | | - return renderRepoPage(req, repo, 'commits', branch, title, cat([ |
1509 | | - pull.once('<h3>' + req._t('Commits') + '</h3>'), |
1510 | | - pull( |
1511 | | - repo.readLog(query.start || branch), |
1512 | | - pull.take(20), |
1513 | | - paramap(repo.getCommitParsed.bind(repo), 8), |
1514 | | - paginate( |
1515 | | - !query.start ? '' : function (first, cb) { |
1516 | | - cb(null, '…') |
1517 | | - }, |
1518 | | - pull.map(renderCommit.bind(this, req, repo)), |
1519 | | - function (commit, cb) { |
1520 | | - cb(null, commit.parents && commit.parents[0] ? |
1521 | | - '<a href="?start=' + commit.id + '">' + |
1522 | | - req._t('Older') + '</a>' : '') |
1523 | | - } |
1524 | | - ) |
1525 | 1555 | ) |
1526 | | - ])) |
1527 | | - } |
| 1556 | + ) |
| 1557 | + ])) |
| 1558 | +} |
1528 | 1559 | |
1529 | | - function renderCommit(req, repo, commit) { |
1530 | | - var commitPath = [repo.id, 'commit', commit.id] |
1531 | | - var treePath = [repo.id, 'tree', commit.id] |
1532 | | - return '<section class="collapse">' + |
1533 | | - '<strong>' + link(commitPath, commit.title) + '</strong><br>' + |
1534 | | - '<tt>' + commit.id + '</tt> ' + |
1535 | | - link(treePath, req._t('Tree')) + '<br>' + |
1536 | | - escapeHTML(commit.author.name) + ' · ' + |
1537 | | - commit.author.date.toLocaleString(req._locale) + |
1538 | | - (commit.separateAuthor ? '<br>' + req._t('CommittedOn', { |
1539 | | - name: escapeHTML(commit.committer.name), |
1540 | | - date: commit.committer.date.toLocaleString(req._locale) |
1541 | | - }) : '') + |
1542 | | - '</section>' |
1543 | | - } |
| 1560 | +function renderCommit(req, repo, commit) { |
| 1561 | + var commitPath = [repo.id, 'commit', commit.id] |
| 1562 | + var treePath = [repo.id, 'tree', commit.id] |
| 1563 | + return '<section class="collapse">' + |
| 1564 | + '<strong>' + link(commitPath, commit.title) + '</strong><br>' + |
| 1565 | + '<tt>' + commit.id + '</tt> ' + |
| 1566 | + link(treePath, req._t('Tree')) + '<br>' + |
| 1567 | + escapeHTML(commit.author.name) + ' · ' + |
| 1568 | + commit.author.date.toLocaleString(req._locale) + |
| 1569 | + (commit.separateAuthor ? '<br>' + req._t('CommittedOn', { |
| 1570 | + name: escapeHTML(commit.committer.name), |
| 1571 | + date: commit.committer.date.toLocaleString(req._locale) |
| 1572 | + }) : '') + |
| 1573 | + '</section>' |
| 1574 | +} |
1544 | 1575 | |
1545 | | - |
| 1576 | + |
1546 | 1577 | |
1547 | | - function formatRevOptions(currentName) { |
1548 | | - return function (name) { |
1549 | | - var htmlName = escapeHTML(name) |
1550 | | - return '<option value="' + htmlName + '"' + |
1551 | | - (name == currentName ? ' selected="selected"' : '') + |
1552 | | - '>' + htmlName + '</option>' |
1553 | | - } |
| 1578 | +function formatRevOptions(currentName) { |
| 1579 | + return function (name) { |
| 1580 | + var htmlName = escapeHTML(name) |
| 1581 | + return '<option value="' + htmlName + '"' + |
| 1582 | + (name == currentName ? ' selected="selected"' : '') + |
| 1583 | + '>' + htmlName + '</option>' |
1554 | 1584 | } |
| 1585 | +} |
1555 | 1586 | |
1556 | | - function formatRevType(req, type) { |
1557 | | - return ( |
1558 | | - type == 'heads' ? req._t('Branches') : |
1559 | | - type == 'tags' ? req._t('Tags') : |
1560 | | - type) |
1561 | | - } |
| 1587 | +function formatRevType(req, type) { |
| 1588 | + return ( |
| 1589 | + type == 'heads' ? req._t('Branches') : |
| 1590 | + type == 'tags' ? req._t('Tags') : |
| 1591 | + type) |
| 1592 | +} |
1562 | 1593 | |
1563 | | - function revMenu(req, repo, currentName) { |
1564 | | - return readOnce(function (cb) { |
1565 | | - repo.getRefNames(function (err, refs) { |
1566 | | - if (err) return cb(err) |
1567 | | - cb(null, '<select name="rev" onchange="this.form.submit()">' + |
1568 | | - Object.keys(refs).map(function (group) { |
1569 | | - return '<optgroup label="' + formatRevType(req, group) + '">' + |
1570 | | - refs[group].map(formatRevOptions(currentName)).join('') + |
1571 | | - '</optgroup>' |
1572 | | - }).join('') + |
1573 | | - '</select><noscript> ' + |
1574 | | - '<input type="submit" value="' + req._t('Go') + '"/></noscript>') |
1575 | | - }) |
| 1594 | +function revMenu(req, repo, currentName) { |
| 1595 | + return readOnce(function (cb) { |
| 1596 | + repo.getRefNames(function (err, refs) { |
| 1597 | + if (err) return cb(err) |
| 1598 | + cb(null, '<select name="rev" onchange="this.form.submit()">' + |
| 1599 | + Object.keys(refs).map(function (group) { |
| 1600 | + return '<optgroup label="' + formatRevType(req, group) + '">' + |
| 1601 | + refs[group].map(formatRevOptions(currentName)).join('') + |
| 1602 | + '</optgroup>' |
| 1603 | + }).join('') + |
| 1604 | + '</select><noscript> ' + |
| 1605 | + '<input type="submit" value="' + req._t('Go') + '"/></noscript>') |
1576 | 1606 | }) |
1577 | | - } |
| 1607 | + }) |
| 1608 | +} |
1578 | 1609 | |
1579 | | - function branchMenu(repo, name, currentName) { |
1580 | | - return cat([ |
1581 | | - pull.once('<select name="' + name + '">'), |
1582 | | - pull( |
1583 | | - repo.refs(), |
1584 | | - pull.map(function (ref) { |
1585 | | - var m = ref.name.match(/^refs\/([^\/]*)\/(.*)$/) || [,, ref.name] |
1586 | | - return m[1] == 'heads' && m[2] |
1587 | | - }), |
1588 | | - pull.filter(Boolean), |
1589 | | - pullSort(), |
1590 | | - pull.map(formatRevOptions(currentName)) |
1591 | | - ), |
1592 | | - pull.once('</select>') |
1593 | | - ]) |
1594 | | - } |
| 1610 | +function branchMenu(repo, name, currentName) { |
| 1611 | + return cat([ |
| 1612 | + pull.once('<select name="' + name + '">'), |
| 1613 | + pull( |
| 1614 | + repo.refs(), |
| 1615 | + pull.map(function (ref) { |
| 1616 | + var m = ref.name.match(/^refs\/([^\/]*)\/(.*)$/) || [,, ref.name] |
| 1617 | + return m[1] == 'heads' && m[2] |
| 1618 | + }), |
| 1619 | + pull.filter(Boolean), |
| 1620 | + pullSort(), |
| 1621 | + pull.map(formatRevOptions(currentName)) |
| 1622 | + ), |
| 1623 | + pull.once('</select>') |
| 1624 | + ]) |
| 1625 | +} |
1595 | 1626 | |
1596 | | - |
| 1627 | + |
1597 | 1628 | |
1598 | | - function renderRepoLatest(req, repo, rev) { |
1599 | | - return readOnce(function (cb) { |
1600 | | - repo.getCommitParsed(rev, function (err, commit) { |
1601 | | - if (err) return cb(err) |
1602 | | - var commitPath = [repo.id, 'commit', commit.id] |
1603 | | - cb(null, |
1604 | | - req._t('Latest') + ': ' + |
1605 | | - '<strong>' + link(commitPath, commit.title) + '</strong><br/>' + |
1606 | | - '<tt>' + commit.id + '</tt><br/> ' + |
1607 | | - req._t('CommittedOn', { |
1608 | | - name: escapeHTML(commit.committer.name), |
1609 | | - date: commit.committer.date.toLocaleString(req._locale) |
1610 | | - }) + |
1611 | | - (commit.separateAuthor ? '<br/>' + req._t('AuthoredOn', { |
1612 | | - name: escapeHTML(commit.author.name), |
1613 | | - date: commit.author.date.toLocaleString(req._locale) |
1614 | | - }) : '')) |
1615 | | - }) |
| 1629 | +function renderRepoLatest(req, repo, rev) { |
| 1630 | + return readOnce(function (cb) { |
| 1631 | + repo.getCommitParsed(rev, function (err, commit) { |
| 1632 | + if (err) return cb(err) |
| 1633 | + var commitPath = [repo.id, 'commit', commit.id] |
| 1634 | + cb(null, |
| 1635 | + req._t('Latest') + ': ' + |
| 1636 | + '<strong>' + link(commitPath, commit.title) + '</strong><br/>' + |
| 1637 | + '<tt>' + commit.id + '</tt><br/> ' + |
| 1638 | + req._t('CommittedOn', { |
| 1639 | + name: escapeHTML(commit.committer.name), |
| 1640 | + date: commit.committer.date.toLocaleString(req._locale) |
| 1641 | + }) + |
| 1642 | + (commit.separateAuthor ? '<br/>' + req._t('AuthoredOn', { |
| 1643 | + name: escapeHTML(commit.author.name), |
| 1644 | + date: commit.author.date.toLocaleString(req._locale) |
| 1645 | + }) : '')) |
1616 | 1646 | }) |
1617 | | - } |
| 1647 | + }) |
| 1648 | +} |
1618 | 1649 | |
1619 | | - |
1620 | | - function linkPath(basePath, path) { |
1621 | | - path = path.slice() |
1622 | | - var last = path.pop() |
1623 | | - return path.map(function (dir, i) { |
1624 | | - return link(basePath.concat(path.slice(0, i+1)), dir) |
1625 | | - }).concat(last).join(' / ') |
1626 | | - } |
| 1650 | + |
| 1651 | +function linkPath(basePath, path) { |
| 1652 | + path = path.slice() |
| 1653 | + var last = path.pop() |
| 1654 | + return path.map(function (dir, i) { |
| 1655 | + return link(basePath.concat(path.slice(0, i+1)), dir) |
| 1656 | + }).concat(last).join(' / ') |
| 1657 | +} |
1627 | 1658 | |
1628 | | - function renderRepoTree(req, repo, rev, path) { |
1629 | | - var pathLinks = path.length === 0 ? '' : |
1630 | | - ': ' + linkPath([repo.id, 'tree'], [rev].concat(path)) |
1631 | | - return cat([ |
1632 | | - pull.once('<h3>' + req._t('Files') + pathLinks + '</h3>'), |
1633 | | - pull( |
1634 | | - repo.readDir(rev, path), |
1635 | | - pull.map(function (file) { |
1636 | | - var type = (file.mode === 040000) ? 'tree' : |
1637 | | - (file.mode === 0160000) ? 'commit' : 'blob' |
1638 | | - if (type == 'commit') |
1639 | | - return [ |
1640 | | - '<span title="' + req._t('gitCommitLink') + '">🖈</span>', |
1641 | | - '<span title="' + escapeHTML(file.id) + '">' + |
1642 | | - escapeHTML(file.name) + '</span>'] |
1643 | | - var filePath = [repo.id, type, rev].concat(path, file.name) |
1644 | | - return ['<i>' + (type == 'tree' ? '📁' : '📄') + '</i>', |
1645 | | - link(filePath, file.name)] |
1646 | | - }), |
1647 | | - table('class="files"') |
1648 | | - ) |
1649 | | - ]) |
1650 | | - } |
| 1659 | +function renderRepoTree(req, repo, rev, path) { |
| 1660 | + var pathLinks = path.length === 0 ? '' : |
| 1661 | + ': ' + linkPath([repo.id, 'tree'], [rev].concat(path)) |
| 1662 | + return cat([ |
| 1663 | + pull.once('<h3>' + req._t('Files') + pathLinks + '</h3>'), |
| 1664 | + pull( |
| 1665 | + repo.readDir(rev, path), |
| 1666 | + pull.map(function (file) { |
| 1667 | + var type = (file.mode === 040000) ? 'tree' : |
| 1668 | + (file.mode === 0160000) ? 'commit' : 'blob' |
| 1669 | + if (type == 'commit') |
| 1670 | + return [ |
| 1671 | + '<span title="' + req._t('gitCommitLink') + '">🖈</span>', |
| 1672 | + '<span title="' + escapeHTML(file.id) + '">' + |
| 1673 | + escapeHTML(file.name) + '</span>'] |
| 1674 | + var filePath = [repo.id, type, rev].concat(path, file.name) |
| 1675 | + return ['<i>' + (type == 'tree' ? '📁' : '📄') + '</i>', |
| 1676 | + link(filePath, file.name)] |
| 1677 | + }), |
| 1678 | + table('class="files"') |
| 1679 | + ) |
| 1680 | + ]) |
| 1681 | +} |
1651 | 1682 | |
1652 | | - |
| 1683 | + |
1653 | 1684 | |
1654 | | - function renderRepoReadme(req, repo, branch, path) { |
1655 | | - return readNext(function (cb) { |
1656 | | - pull( |
1657 | | - repo.readDir(branch, path), |
1658 | | - pull.filter(function (file) { |
1659 | | - return /readme(\.|$)/i.test(file.name) |
1660 | | - }), |
1661 | | - pull.take(1), |
1662 | | - pull.collect(function (err, files) { |
1663 | | - if (err) return cb(null, pull.empty()) |
1664 | | - var file = files[0] |
1665 | | - if (!file) |
1666 | | - return cb(null, pull.once(path.length ? '' : |
1667 | | - '<p>' + req._t('NoReadme') + '</p>')) |
1668 | | - repo.getObjectFromAny(file.id, function (err, obj) { |
1669 | | - if (err) return cb(err) |
1670 | | - cb(null, cat([ |
1671 | | - pull.once('<section><h4><a name="readme">' + |
1672 | | - escapeHTML(file.name) + '</a></h4><hr/>'), |
1673 | | - renderObjectData(obj, file.name, repo, branch, path), |
1674 | | - pull.once('</section>') |
1675 | | - ])) |
1676 | | - }) |
| 1685 | +function renderRepoReadme(req, repo, branch, path) { |
| 1686 | + return readNext(function (cb) { |
| 1687 | + pull( |
| 1688 | + repo.readDir(branch, path), |
| 1689 | + pull.filter(function (file) { |
| 1690 | + return /readme(\.|$)/i.test(file.name) |
| 1691 | + }), |
| 1692 | + pull.take(1), |
| 1693 | + pull.collect(function (err, files) { |
| 1694 | + if (err) return cb(null, pull.empty()) |
| 1695 | + var file = files[0] |
| 1696 | + if (!file) |
| 1697 | + return cb(null, pull.once(path.length ? '' : |
| 1698 | + '<p>' + req._t('NoReadme') + '</p>')) |
| 1699 | + repo.getObjectFromAny(file.id, function (err, obj) { |
| 1700 | + if (err) return cb(err) |
| 1701 | + cb(null, cat([ |
| 1702 | + pull.once('<section><h4><a name="readme">' + |
| 1703 | + escapeHTML(file.name) + '</a></h4><hr/>'), |
| 1704 | + renderObjectData(obj, file.name, repo, branch, path), |
| 1705 | + pull.once('</section>') |
| 1706 | + ])) |
1677 | 1707 | }) |
1678 | | - ) |
1679 | | - }) |
1680 | | - } |
| 1708 | + }) |
| 1709 | + ) |
| 1710 | + }) |
| 1711 | +} |
1681 | 1712 | |
1682 | | - |
| 1713 | + |
1683 | 1714 | |
1684 | | - function serveRepoCommit(req, repo, rev) { |
1685 | | - return readNext(function (cb) { |
1686 | | - repo.getCommitParsed(rev, function (err, commit) { |
1687 | | - if (err) return cb(err) |
1688 | | - var commitPath = [repo.id, 'commit', commit.id] |
1689 | | - var treePath = [repo.id, 'tree', commit.id] |
1690 | | - var title = escapeHTML(commit.title) + ' · ' + |
1691 | | - '%{author}/%{repo}@' + commit.id.substr(0, 8) |
1692 | | - cb(null, renderRepoPage(req, repo, null, rev, title, cat([ |
1693 | | - pull.once( |
1694 | | - '<h3>' + link(commitPath, |
1695 | | - req._t('CommitRev', {rev: rev})) + '</h3>' + |
1696 | | - '<section class="collapse">' + |
1697 | | - '<div class="right-bar">' + |
1698 | | - link(treePath, req._t('BrowseFiles')) + |
1699 | | - '</div>' + |
1700 | | - '<h4>' + linkify(escapeHTML(commit.title)) + '</h4>' + |
1701 | | - (commit.body ? linkify(pre(commit.body)) : '') + |
1702 | | - (commit.separateAuthor ? req._t('AuthoredOn', { |
1703 | | - name: escapeHTML(commit.author.name), |
1704 | | - date: commit.author.date.toLocaleString(req._locale) |
1705 | | - }) + '<br/>' : '') + |
1706 | | - req._t('CommittedOn', { |
1707 | | - name: escapeHTML(commit.committer.name), |
1708 | | - date: commit.committer.date.toLocaleString(req._locale) |
1709 | | - }) + '<br/>' + |
1710 | | - commit.parents.map(function (id) { |
1711 | | - return req._t('Parent') + ': ' + |
1712 | | - link([repo.id, 'commit', id], id) |
1713 | | - }).join('<br>') + |
1714 | | - '</section>' + |
1715 | | - '<section><h3>' + req._t('FilesChanged') + '</h3>'), |
1716 | | - |
1717 | | - renderDiffStat(req, [repo, repo], [commit.parents[0], commit.id]), |
1718 | | - pull.once('</section>') |
1719 | | - ]))) |
1720 | | - }) |
| 1715 | +G.serveRepoCommit = function (req, repo, rev) { |
| 1716 | + var self = this |
| 1717 | + return readNext(function (cb) { |
| 1718 | + repo.getCommitParsed(rev, function (err, commit) { |
| 1719 | + if (err) return cb(err) |
| 1720 | + var commitPath = [repo.id, 'commit', commit.id] |
| 1721 | + var treePath = [repo.id, 'tree', commit.id] |
| 1722 | + var title = escapeHTML(commit.title) + ' · ' + |
| 1723 | + '%{author}/%{repo}@' + commit.id.substr(0, 8) |
| 1724 | + cb(null, self.renderRepoPage(req, repo, null, rev, title, cat([ |
| 1725 | + pull.once( |
| 1726 | + '<h3>' + link(commitPath, |
| 1727 | + req._t('CommitRev', {rev: rev})) + '</h3>' + |
| 1728 | + '<section class="collapse">' + |
| 1729 | + '<div class="right-bar">' + |
| 1730 | + link(treePath, req._t('BrowseFiles')) + |
| 1731 | + '</div>' + |
| 1732 | + '<h4>' + linkify(escapeHTML(commit.title)) + '</h4>' + |
| 1733 | + (commit.body ? linkify(pre(commit.body)) : '') + |
| 1734 | + (commit.separateAuthor ? req._t('AuthoredOn', { |
| 1735 | + name: escapeHTML(commit.author.name), |
| 1736 | + date: commit.author.date.toLocaleString(req._locale) |
| 1737 | + }) + '<br/>' : '') + |
| 1738 | + req._t('CommittedOn', { |
| 1739 | + name: escapeHTML(commit.committer.name), |
| 1740 | + date: commit.committer.date.toLocaleString(req._locale) |
| 1741 | + }) + '<br/>' + |
| 1742 | + commit.parents.map(function (id) { |
| 1743 | + return req._t('Parent') + ': ' + |
| 1744 | + link([repo.id, 'commit', id], id) |
| 1745 | + }).join('<br>') + |
| 1746 | + '</section>' + |
| 1747 | + '<section><h3>' + req._t('FilesChanged') + '</h3>'), |
| 1748 | + |
| 1749 | + renderDiffStat(req, [repo, repo], [commit.parents[0], commit.id]), |
| 1750 | + pull.once('</section>') |
| 1751 | + ]))) |
1721 | 1752 | }) |
1722 | | - } |
| 1753 | + }) |
| 1754 | +} |
1723 | 1755 | |
1724 | | - |
| 1756 | + |
1725 | 1757 | |
1726 | | - function serveRepoTag(req, repo, rev) { |
1727 | | - return readNext(function (cb) { |
1728 | | - repo.getTagParsed(rev, function (err, tag) { |
1729 | | - if (err) return cb(err) |
1730 | | - var title = req._t('TagName', { |
1731 | | - tag: escapeHTML(tag.tag) |
1732 | | - }) + ' · %{author}/%{repo}' |
1733 | | - var body = (tag.title + '\n\n' + |
1734 | | - tag.body.replace(/-----BEGIN PGP SIGNATURE-----\n[^.]*?\n-----END PGP SIGNATURE-----\s*$/, '')).trim() |
1735 | | - cb(null, renderRepoPage(req, repo, 'tags', tag.object, title, pull.once( |
| 1758 | +G.serveRepoTag = function (req, repo, rev) { |
| 1759 | + var self = this |
| 1760 | + return readNext(function (cb) { |
| 1761 | + repo.getTagParsed(rev, function (err, tag) { |
| 1762 | + if (err) return cb(err) |
| 1763 | + var title = req._t('TagName', { |
| 1764 | + tag: escapeHTML(tag.tag) |
| 1765 | + }) + ' · %{author}/%{repo}' |
| 1766 | + var body = (tag.title + '\n\n' + |
| 1767 | + tag.body.replace(/-----BEGIN PGP SIGNATURE-----\n[^.]*?\n-----END PGP SIGNATURE-----\s*$/, '')).trim() |
| 1768 | + cb(null, self.renderRepoPage(req, repo, 'tags', tag.object, title, |
| 1769 | + pull.once( |
1736 | 1770 | '<section class="collapse">' + |
1737 | 1771 | '<h3>' + link([repo.id, 'tag', rev], tag.tag) + '</h3>' + |
1738 | 1772 | req._t('TaggedOn', { |
1739 | 1773 | name: escapeHTML(tag.tagger.name), |
1741 | 1775 | }) + '<br/>' + |
1742 | 1776 | link([repo.id, tag.type, tag.object]) + |
1743 | 1777 | linkify(pre(body)) + |
1744 | 1778 | '</section>'))) |
1745 | | - }) |
1746 | 1779 | }) |
1747 | | - } |
| 1780 | + }) |
| 1781 | +} |
1748 | 1782 | |
1749 | 1783 | |
1750 | | - |
| 1784 | + |
1751 | 1785 | |
1752 | | - function renderDiffStat(req, repos, treeIds) { |
1753 | | - if (treeIds.length == 0) treeIds = [null] |
1754 | | - var id = treeIds[0] |
1755 | | - var lastI = treeIds.length - 1 |
1756 | | - var oldTree = treeIds[0] |
1757 | | - var changedFiles = [] |
1758 | | - return cat([ |
1759 | | - pull( |
1760 | | - Repo.diffTrees(repos, treeIds, true), |
1761 | | - pull.map(function (item) { |
1762 | | - var filename = escapeHTML(item.filename = item.path.join('/')) |
1763 | | - var oldId = item.id && item.id[0] |
1764 | | - var newId = item.id && item.id[lastI] |
1765 | | - var oldMode = item.mode && item.mode[0] |
1766 | | - var newMode = item.mode && item.mode[lastI] |
1767 | | - var action = |
1768 | | - !oldId && newId ? req._t('action.added') : |
1769 | | - oldId && !newId ? req._t('action.deleted') : |
1770 | | - oldMode != newMode ? req._t('action.changedMode', { |
1771 | | - old: oldMode.toString(8), |
1772 | | - new: newMode.toString(8) |
1773 | | - }) : req._t('changed') |
1774 | | - if (item.id) |
1775 | | - changedFiles.push(item) |
1776 | | - var blobsPath = item.id[1] |
1777 | | - ? [repos[1].id, 'blob', treeIds[1]] |
1778 | | - : [repos[0].id, 'blob', treeIds[0]] |
1779 | | - var rawsPath = item.id[1] |
1780 | | - ? [repos[1].id, 'raw', treeIds[1]] |
1781 | | - : [repos[0].id, 'raw', treeIds[0]] |
1782 | | - item.blobPath = blobsPath.concat(item.path) |
1783 | | - item.rawPath = rawsPath.concat(item.path) |
1784 | | - var fileHref = item.id ? |
1785 | | - '#' + encodeURIComponent(item.path.join('/')) : |
1786 | | - encodeLink(item.blobPath) |
1787 | | - return ['<a href="' + fileHref + '">' + filename + '</a>', action] |
1788 | | - }), |
1789 | | - table() |
1790 | | - ), |
1791 | | - pull( |
1792 | | - pull.values(changedFiles), |
1793 | | - paramap(function (item, cb) { |
1794 | | - var extension = getExtension(item.filename) |
1795 | | - if (extension in imgMimes) { |
1796 | | - var filename = escapeHTML(item.filename) |
1797 | | - return cb(null, |
1798 | | - '<pre><table class="code">' + |
1799 | | - '<tr><th id="' + escapeHTML(item.filename) + '">' + |
1800 | | - filename + '</th></tr>' + |
1801 | | - '<tr><td><img src="' + encodeLink(item.rawPath) + '"' + |
1802 | | - ' alt="' + filename + '"/></td></tr>' + |
1803 | | - '</table></pre>') |
1804 | | - } |
1805 | | - var done = multicb({ pluck: 1, spread: true }) |
1806 | | - getRepoObjectString(repos[0], item.id[0], done()) |
1807 | | - getRepoObjectString(repos[1], item.id[lastI], done()) |
1808 | | - done(function (err, strOld, strNew) { |
1809 | | - if (err) return cb(err) |
1810 | | - cb(null, htmlLineDiff(req, item.filename, item.filename, |
1811 | | - strOld, strNew, |
1812 | | - encodeLink(item.blobPath))) |
1813 | | - }) |
1814 | | - }, 4) |
1815 | | - ) |
1816 | | - ]) |
1817 | | - } |
| 1786 | +function renderDiffStat(req, repos, treeIds) { |
| 1787 | + if (treeIds.length == 0) treeIds = [null] |
| 1788 | + var id = treeIds[0] |
| 1789 | + var lastI = treeIds.length - 1 |
| 1790 | + var oldTree = treeIds[0] |
| 1791 | + var changedFiles = [] |
| 1792 | + return cat([ |
| 1793 | + pull( |
| 1794 | + Repo.diffTrees(repos, treeIds, true), |
| 1795 | + pull.map(function (item) { |
| 1796 | + var filename = escapeHTML(item.filename = item.path.join('/')) |
| 1797 | + var oldId = item.id && item.id[0] |
| 1798 | + var newId = item.id && item.id[lastI] |
| 1799 | + var oldMode = item.mode && item.mode[0] |
| 1800 | + var newMode = item.mode && item.mode[lastI] |
| 1801 | + var action = |
| 1802 | + !oldId && newId ? req._t('action.added') : |
| 1803 | + oldId && !newId ? req._t('action.deleted') : |
| 1804 | + oldMode != newMode ? req._t('action.changedMode', { |
| 1805 | + old: oldMode.toString(8), |
| 1806 | + new: newMode.toString(8) |
| 1807 | + }) : req._t('changed') |
| 1808 | + if (item.id) |
| 1809 | + changedFiles.push(item) |
| 1810 | + var blobsPath = item.id[1] |
| 1811 | + ? [repos[1].id, 'blob', treeIds[1]] |
| 1812 | + : [repos[0].id, 'blob', treeIds[0]] |
| 1813 | + var rawsPath = item.id[1] |
| 1814 | + ? [repos[1].id, 'raw', treeIds[1]] |
| 1815 | + : [repos[0].id, 'raw', treeIds[0]] |
| 1816 | + item.blobPath = blobsPath.concat(item.path) |
| 1817 | + item.rawPath = rawsPath.concat(item.path) |
| 1818 | + var fileHref = item.id ? |
| 1819 | + '#' + encodeURIComponent(item.path.join('/')) : |
| 1820 | + encodeLink(item.blobPath) |
| 1821 | + return ['<a href="' + fileHref + '">' + filename + '</a>', action] |
| 1822 | + }), |
| 1823 | + table() |
| 1824 | + ), |
| 1825 | + pull( |
| 1826 | + pull.values(changedFiles), |
| 1827 | + paramap(function (item, cb) { |
| 1828 | + var extension = getExtension(item.filename) |
| 1829 | + if (extension in imgMimes) { |
| 1830 | + var filename = escapeHTML(item.filename) |
| 1831 | + return cb(null, |
| 1832 | + '<pre><table class="code">' + |
| 1833 | + '<tr><th id="' + escapeHTML(item.filename) + '">' + |
| 1834 | + filename + '</th></tr>' + |
| 1835 | + '<tr><td><img src="' + encodeLink(item.rawPath) + '"' + |
| 1836 | + ' alt="' + filename + '"/></td></tr>' + |
| 1837 | + '</table></pre>') |
| 1838 | + } |
| 1839 | + var done = multicb({ pluck: 1, spread: true }) |
| 1840 | + getRepoObjectString(repos[0], item.id[0], done()) |
| 1841 | + getRepoObjectString(repos[1], item.id[lastI], done()) |
| 1842 | + done(function (err, strOld, strNew) { |
| 1843 | + if (err) return cb(err) |
| 1844 | + cb(null, htmlLineDiff(req, item.filename, item.filename, |
| 1845 | + strOld, strNew, |
| 1846 | + encodeLink(item.blobPath))) |
| 1847 | + }) |
| 1848 | + }, 4) |
| 1849 | + ) |
| 1850 | + ]) |
| 1851 | +} |
1818 | 1852 | |
1819 | | - function htmlLineDiff(req, filename, anchor, oldStr, newStr, blobHref) { |
1820 | | - var diff = JsDiff.structuredPatch('', '', oldStr, newStr) |
1821 | | - var groups = diff.hunks.map(function (hunk) { |
1822 | | - var oldLine = hunk.oldStart |
1823 | | - var newLine = hunk.newStart |
1824 | | - var header = '<tr class="diff-hunk-header"><td colspan=2></td><td>' + |
1825 | | - '@@ -' + oldLine + ',' + hunk.oldLines + ' ' + |
1826 | | - '+' + newLine + ',' + hunk.newLines + ' @@' + |
1827 | | - '</td></tr>' |
1828 | | - return [header].concat(hunk.lines.map(function (line) { |
1829 | | - var s = line[0] |
1830 | | - if (s == '\\') return |
1831 | | - var html = highlight(line, getExtension(filename)) |
1832 | | - var trClass = s == '+' ? 'diff-new' : s == '-' ? 'diff-old' : '' |
1833 | | - var lineNums = [s == '+' ? '' : oldLine++, s == '-' ? '' : newLine++] |
1834 | | - var id = [filename].concat(lineNums).join('-') |
1835 | | - return '<tr id="' + escapeHTML(id) + '" class="' + trClass + '">' + |
1836 | | - lineNums.map(function (num) { |
1837 | | - return '<td class="code-linenum">' + |
1838 | | - (num ? '<a href="#' + encodeURIComponent(id) + '">' + |
1839 | | - num + '</a>' : '') + '</td>' |
1840 | | - }).join('') + |
1841 | | - '<td class="code-text">' + html + '</td></tr>' |
1842 | | - })) |
1843 | | - }) |
1844 | | - return '<pre><table class="code">' + |
1845 | | - '<tr><th colspan=3 id="' + escapeHTML(anchor) + '">' + filename + |
1846 | | - '<span class="right-bar">' + |
1847 | | - '<a href="' + blobHref + '">' + req._t('View') + '</a> ' + |
1848 | | - '</span></th></tr>' + |
1849 | | - [].concat.apply([], groups).join('') + |
1850 | | - '</table></pre>' |
1851 | | - } |
| 1853 | +function htmlLineDiff(req, filename, anchor, oldStr, newStr, blobHref) { |
| 1854 | + var diff = JsDiff.structuredPatch('', '', oldStr, newStr) |
| 1855 | + var groups = diff.hunks.map(function (hunk) { |
| 1856 | + var oldLine = hunk.oldStart |
| 1857 | + var newLine = hunk.newStart |
| 1858 | + var header = '<tr class="diff-hunk-header"><td colspan=2></td><td>' + |
| 1859 | + '@@ -' + oldLine + ',' + hunk.oldLines + ' ' + |
| 1860 | + '+' + newLine + ',' + hunk.newLines + ' @@' + |
| 1861 | + '</td></tr>' |
| 1862 | + return [header].concat(hunk.lines.map(function (line) { |
| 1863 | + var s = line[0] |
| 1864 | + if (s == '\\') return |
| 1865 | + var html = highlight(line, getExtension(filename)) |
| 1866 | + var trClass = s == '+' ? 'diff-new' : s == '-' ? 'diff-old' : '' |
| 1867 | + var lineNums = [s == '+' ? '' : oldLine++, s == '-' ? '' : newLine++] |
| 1868 | + var id = [filename].concat(lineNums).join('-') |
| 1869 | + return '<tr id="' + escapeHTML(id) + '" class="' + trClass + '">' + |
| 1870 | + lineNums.map(function (num) { |
| 1871 | + return '<td class="code-linenum">' + |
| 1872 | + (num ? '<a href="#' + encodeURIComponent(id) + '">' + |
| 1873 | + num + '</a>' : '') + '</td>' |
| 1874 | + }).join('') + |
| 1875 | + '<td class="code-text">' + html + '</td></tr>' |
| 1876 | + })) |
| 1877 | + }) |
| 1878 | + return '<pre><table class="code">' + |
| 1879 | + '<tr><th colspan=3 id="' + escapeHTML(anchor) + '">' + filename + |
| 1880 | + '<span class="right-bar">' + |
| 1881 | + '<a href="' + blobHref + '">' + req._t('View') + '</a> ' + |
| 1882 | + '</span></th></tr>' + |
| 1883 | + [].concat.apply([], groups).join('') + |
| 1884 | + '</table></pre>' |
| 1885 | +} |
1852 | 1886 | |
1853 | | - |
| 1887 | + |
1854 | 1888 | |
1855 | | - function serveRepoSomething(req, repo, id, msg, path) { |
1856 | | - return renderRepoPage(req, repo, null, null, null, |
1857 | | - pull.once('<section><h3>' + link([id]) + '</h3>' + |
1858 | | - json(msg) + '</section>')) |
1859 | | - } |
| 1889 | +G.serveRepoSomething = function (req, repo, id, msg, path) { |
| 1890 | + return this.renderRepoPage(req, repo, null, null, null, |
| 1891 | + pull.once('<section><h3>' + link([id]) + '</h3>' + |
| 1892 | + json(msg) + '</section>')) |
| 1893 | +} |
1860 | 1894 | |
1861 | | - |
| 1895 | + |
1862 | 1896 | |
1863 | | - function objsArr(objs) { |
1864 | | - return Array.isArray(objs) ? objs : |
1865 | | - Object.keys(objs).map(function (sha1) { |
1866 | | - var obj = Object.create(objs[sha1]) |
1867 | | - obj.sha1 = sha1 |
1868 | | - return obj |
1869 | | - }) |
1870 | | - } |
| 1897 | +function objsArr(objs) { |
| 1898 | + return Array.isArray(objs) ? objs : |
| 1899 | + Object.keys(objs).map(function (sha1) { |
| 1900 | + var obj = Object.create(objs[sha1]) |
| 1901 | + obj.sha1 = sha1 |
| 1902 | + return obj |
| 1903 | + }) |
| 1904 | +} |
1871 | 1905 | |
1872 | | - function serveRepoUpdate(req, repo, id, msg, path) { |
1873 | | - var raw = req._u.query.raw != null |
1874 | | - var title = req._t('Update') + ' · %{author}/%{repo}' |
| 1906 | +G.serveRepoUpdate = function (req, repo, id, msg, path) { |
| 1907 | + var self = this |
| 1908 | + var raw = req._u.query.raw != null |
| 1909 | + var title = req._t('Update') + ' · %{author}/%{repo}' |
1875 | 1910 | |
1876 | | - if (raw) |
1877 | | - return renderRepoPage(req, repo, 'activity', null, title, pull.once( |
1878 | | - '<a href="?" class="raw-link header-align">' + |
1879 | | - req._t('Info') + '</a>' + |
1880 | | - '<h3>' + req._t('Update') + '</h3>' + |
1881 | | - '<section class="collapse">' + |
1882 | | - json({key: id, value: msg}) + '</section>')) |
| 1911 | + if (raw) |
| 1912 | + return self.renderRepoPage(req, repo, 'activity', null, title, pull.once( |
| 1913 | + '<a href="?" class="raw-link header-align">' + |
| 1914 | + req._t('Info') + '</a>' + |
| 1915 | + '<h3>' + req._t('Update') + '</h3>' + |
| 1916 | + '<section class="collapse">' + |
| 1917 | + json({key: id, value: msg}) + '</section>')) |
1883 | 1918 | |
1884 | | - |
1885 | | - if (msg.content.indexes) { |
1886 | | - for (var i = 0; i < msg.content.indexes.length; i++) { |
1887 | | - msg.content.packs[i] = { |
1888 | | - pack: {link: msg.content.packs[i].link}, |
1889 | | - idx: msg.content.indexes[i] |
1890 | | - } |
| 1919 | + |
| 1920 | + if (msg.content.indexes) { |
| 1921 | + for (var i = 0; i < msg.content.indexes.length; i++) { |
| 1922 | + msg.content.packs[i] = { |
| 1923 | + pack: {link: msg.content.packs[i].link}, |
| 1924 | + idx: msg.content.indexes[i] |
1891 | 1925 | } |
1892 | 1926 | } |
| 1927 | + } |
1893 | 1928 | |
1894 | | - var commits = cat([ |
1895 | | - msg.content.objects && pull( |
1896 | | - pull.values(msg.content.objects), |
1897 | | - pull.filter(function (obj) { return obj.type == 'commit' }), |
1898 | | - paramap(function (obj, cb) { |
1899 | | - getBlob(req, obj.link || obj.key, function (err, readObject) { |
1900 | | - if (err) return cb(err) |
1901 | | - Repo.getCommitParsed({read: readObject}, cb) |
1902 | | - }) |
1903 | | - }, 8) |
1904 | | - ), |
1905 | | - msg.content.packs && pull( |
1906 | | - pull.values(msg.content.packs), |
1907 | | - paramap(function (pack, cb) { |
1908 | | - var done = multicb({ pluck: 1, spread: true }) |
1909 | | - getBlob(req, pack.pack.link, done()) |
1910 | | - getBlob(req, pack.idx.link, done()) |
1911 | | - done(function (err, readPack, readIdx) { |
1912 | | - if (err) return cb(renderError(err)) |
1913 | | - cb(null, gitPack.decodeWithIndex(repo, readPack, readIdx)) |
1914 | | - }) |
1915 | | - }, 4), |
1916 | | - pull.flatten(), |
1917 | | - pull.asyncMap(function (obj, cb) { |
1918 | | - if (obj.type == 'commit') |
1919 | | - Repo.getCommitParsed(obj, cb) |
1920 | | - else |
1921 | | - pull(obj.read, pull.drain(null, cb)) |
1922 | | - }), |
1923 | | - pull.filter() |
1924 | | - ) |
1925 | | - ]) |
| 1929 | + var commits = cat([ |
| 1930 | + msg.content.objects && pull( |
| 1931 | + pull.values(msg.content.objects), |
| 1932 | + pull.filter(function (obj) { return obj.type == 'commit' }), |
| 1933 | + paramap(function (obj, cb) { |
| 1934 | + self.getBlob(req, obj.link || obj.key, function (err, readObject) { |
| 1935 | + if (err) return cb(err) |
| 1936 | + Repo.getCommitParsed({read: readObject}, cb) |
| 1937 | + }) |
| 1938 | + }, 8) |
| 1939 | + ), |
| 1940 | + msg.content.packs && pull( |
| 1941 | + pull.values(msg.content.packs), |
| 1942 | + paramap(function (pack, cb) { |
| 1943 | + var done = multicb({ pluck: 1, spread: true }) |
| 1944 | + self.getBlob(req, pack.pack.link, done()) |
| 1945 | + self.getBlob(req, pack.idx.link, done()) |
| 1946 | + done(function (err, readPack, readIdx) { |
| 1947 | + if (err) return cb(renderError(err)) |
| 1948 | + cb(null, gitPack.decodeWithIndex(repo, readPack, readIdx)) |
| 1949 | + }) |
| 1950 | + }, 4), |
| 1951 | + pull.flatten(), |
| 1952 | + pull.asyncMap(function (obj, cb) { |
| 1953 | + if (obj.type == 'commit') |
| 1954 | + Repo.getCommitParsed(obj, cb) |
| 1955 | + else |
| 1956 | + pull(obj.read, pull.drain(null, cb)) |
| 1957 | + }), |
| 1958 | + pull.filter() |
| 1959 | + ) |
| 1960 | + ]) |
1926 | 1961 | |
1927 | | - return renderRepoPage(req, repo, 'activity', null, title, cat([ |
1928 | | - pull.once('<a href="?raw" class="raw-link header-align">' + |
1929 | | - req._t('Data') + '</a>' + |
1930 | | - '<h3>' + req._t('Update') + '</h3>' + |
1931 | | - renderRepoUpdate(req, repo, {key: id, value: msg}, true)), |
1932 | | - (msg.content.objects || msg.content.packs) && |
1933 | | - pull.once('<h3>' + req._t('Commits') + '</h3>'), |
1934 | | - pull(commits, pull.map(function (commit) { |
1935 | | - return renderCommit(req, repo, commit) |
1936 | | - })) |
1937 | | - ])) |
1938 | | - } |
| 1962 | + return self.renderRepoPage(req, repo, 'activity', null, title, cat([ |
| 1963 | + pull.once('<a href="?raw" class="raw-link header-align">' + |
| 1964 | + req._t('Data') + '</a>' + |
| 1965 | + '<h3>' + req._t('Update') + '</h3>' + |
| 1966 | + renderRepoUpdate(req, repo, {key: id, value: msg}, true)), |
| 1967 | + (msg.content.objects || msg.content.packs) && |
| 1968 | + pull.once('<h3>' + req._t('Commits') + '</h3>'), |
| 1969 | + pull(commits, pull.map(function (commit) { |
| 1970 | + return renderCommit(req, repo, commit) |
| 1971 | + })) |
| 1972 | + ])) |
| 1973 | +} |
1939 | 1974 | |
1940 | | - |
| 1975 | + |
1941 | 1976 | |
1942 | | - function serveRepoBlob(req, repo, rev, path) { |
1943 | | - return readNext(function (cb) { |
1944 | | - repo.getFile(rev, path, function (err, object) { |
1945 | | - if (err) return cb(null, serveBlobNotFound(req, repo.id, err)) |
1946 | | - var type = repo.isCommitHash(rev) ? 'Tree' : 'Branch' |
1947 | | - var pathLinks = path.length === 0 ? '' : |
1948 | | - ': ' + linkPath([repo.id, 'tree'], [rev].concat(path)) |
1949 | | - var rawFilePath = [repo.id, 'raw', rev].concat(path) |
1950 | | - var dirPath = path.slice(0, path.length-1) |
1951 | | - var filename = path[path.length-1] |
1952 | | - var extension = getExtension(filename) |
1953 | | - var title = (path.length ? path.join('/') + ' · ' : '') + |
1954 | | - '%{author}/%{repo}' + |
1955 | | - (repo.head == 'refs/heads/' + rev ? '' : '@' + rev) |
1956 | | - cb(null, renderRepoPage(req, repo, 'code', rev, title, cat([ |
1957 | | - pull.once('<section><form action="" method="get">' + |
1958 | | - '<h3>' + req._t(type) + ': ' + rev + ' '), |
1959 | | - revMenu(req, repo, rev), |
1960 | | - pull.once('</h3></form>'), |
1961 | | - type == 'Branch' && renderRepoLatest(req, repo, rev), |
1962 | | - pull.once('</section><section class="collapse">' + |
1963 | | - '<h3>' + req._t('Files') + pathLinks + '</h3>' + |
1964 | | - '<div>' + object.length + ' bytes' + |
1965 | | - '<span class="raw-link">' + |
1966 | | - link(rawFilePath, req._t('Raw')) + '</span>' + |
1967 | | - '</div></section>' + |
1968 | | - '<section>'), |
1969 | | - extension in imgMimes |
1970 | | - ? pull.once('<img src="' + encodeLink(rawFilePath) + |
1971 | | - '" alt="' + escapeHTML(filename) + '" />') |
1972 | | - : renderObjectData(object, filename, repo, rev, dirPath), |
1973 | | - pull.once('</section>') |
1974 | | - ]))) |
1975 | | - }) |
| 1977 | +G.serveRepoBlob = function (req, repo, rev, path) { |
| 1978 | + var self = this |
| 1979 | + return readNext(function (cb) { |
| 1980 | + repo.getFile(rev, path, function (err, object) { |
| 1981 | + if (err) return cb(null, self.serveBlobNotFound(req, repo.id, err)) |
| 1982 | + var type = repo.isCommitHash(rev) ? 'Tree' : 'Branch' |
| 1983 | + var pathLinks = path.length === 0 ? '' : |
| 1984 | + ': ' + linkPath([repo.id, 'tree'], [rev].concat(path)) |
| 1985 | + var rawFilePath = [repo.id, 'raw', rev].concat(path) |
| 1986 | + var dirPath = path.slice(0, path.length-1) |
| 1987 | + var filename = path[path.length-1] |
| 1988 | + var extension = getExtension(filename) |
| 1989 | + var title = (path.length ? path.join('/') + ' · ' : '') + |
| 1990 | + '%{author}/%{repo}' + |
| 1991 | + (repo.head == 'refs/heads/' + rev ? '' : '@' + rev) |
| 1992 | + cb(null, self.renderRepoPage(req, repo, 'code', rev, title, cat([ |
| 1993 | + pull.once('<section><form action="" method="get">' + |
| 1994 | + '<h3>' + req._t(type) + ': ' + rev + ' '), |
| 1995 | + revMenu(req, repo, rev), |
| 1996 | + pull.once('</h3></form>'), |
| 1997 | + type == 'Branch' && renderRepoLatest(req, repo, rev), |
| 1998 | + pull.once('</section><section class="collapse">' + |
| 1999 | + '<h3>' + req._t('Files') + pathLinks + '</h3>' + |
| 2000 | + '<div>' + object.length + ' bytes' + |
| 2001 | + '<span class="raw-link">' + |
| 2002 | + link(rawFilePath, req._t('Raw')) + '</span>' + |
| 2003 | + '</div></section>' + |
| 2004 | + '<section>'), |
| 2005 | + extension in imgMimes |
| 2006 | + ? pull.once('<img src="' + encodeLink(rawFilePath) + |
| 2007 | + '" alt="' + escapeHTML(filename) + '" />') |
| 2008 | + : renderObjectData(object, filename, repo, rev, dirPath), |
| 2009 | + pull.once('</section>') |
| 2010 | + ]))) |
1976 | 2011 | }) |
1977 | | - } |
| 2012 | + }) |
| 2013 | +} |
1978 | 2014 | |
1979 | | - function serveBlobNotFound(req, repoId, err) { |
1980 | | - return serveTemplate(req, req._t('error.BlobNotFound'), 404)(pull.once( |
1981 | | - '<h2>' + req._t('error.BlobNotFound') + '</h2>' + |
1982 | | - '<p>' + req._t('error.BlobNotFoundInRepo', { |
1983 | | - repo: link([repoId]) |
1984 | | - }) + '</p>' + |
1985 | | - '<pre>' + escapeHTML(err.stack) + '</pre>' |
1986 | | - )) |
1987 | | - } |
| 2015 | +G.serveBlobNotFound = function (req, repoId, err) { |
| 2016 | + return this.serveTemplate(req, req._t('error.BlobNotFound'), 404)(pull.once( |
| 2017 | + '<h2>' + req._t('error.BlobNotFound') + '</h2>' + |
| 2018 | + '<p>' + req._t('error.BlobNotFoundInRepo', { |
| 2019 | + repo: link([repoId]) |
| 2020 | + }) + '</p>' + |
| 2021 | + '<pre>' + escapeHTML(err.stack) + '</pre>' |
| 2022 | + )) |
| 2023 | +} |
1988 | 2024 | |
1989 | | - |
| 2025 | + |
1990 | 2026 | |
1991 | | - function serveRepoRaw(req, repo, branch, path) { |
1992 | | - return readNext(function (cb) { |
1993 | | - repo.getFile(branch, path, function (err, object) { |
1994 | | - if (err) return cb(null, |
1995 | | - serveBuffer(404, req._t('error.BlobNotFound'))) |
1996 | | - var extension = getExtension(path[path.length-1]) |
1997 | | - var contentType = imgMimes[extension] |
1998 | | - cb(null, pull(object.read, serveRaw(object.length, contentType))) |
1999 | | - }) |
| 2027 | +G.serveRepoRaw = function (req, repo, branch, path) { |
| 2028 | + return readNext(function (cb) { |
| 2029 | + repo.getFile(branch, path, function (err, object) { |
| 2030 | + if (err) return cb(null, |
| 2031 | + this.serveBuffer(404, req._t('error.BlobNotFound'))) |
| 2032 | + var extension = getExtension(path[path.length-1]) |
| 2033 | + var contentType = imgMimes[extension] |
| 2034 | + cb(null, pull(object.read, this.serveRaw(object.length, contentType))) |
2000 | 2035 | }) |
2001 | | - } |
| 2036 | + }) |
| 2037 | +} |
2002 | 2038 | |
2003 | | - function serveRaw(length, contentType) { |
2004 | | - var headers = { |
2005 | | - 'Content-Type': contentType || 'text/plain; charset=utf-8', |
2006 | | - 'Cache-Control': 'max-age=31536000' |
2007 | | - } |
2008 | | - if (length != null) |
2009 | | - headers['Content-Length'] = length |
2010 | | - return function (read) { |
2011 | | - return cat([pull.once([200, headers]), read]) |
2012 | | - } |
| 2039 | +G.serveRaw = function (length, contentType) { |
| 2040 | + var headers = { |
| 2041 | + 'Content-Type': contentType || 'text/plain; charset=utf-8', |
| 2042 | + 'Cache-Control': 'max-age=31536000' |
2013 | 2043 | } |
2014 | | - |
2015 | | - function getBlob(req, key, cb) { |
2016 | | - ssb.blobs.want(key, function (err, got) { |
2017 | | - if (err) cb(err) |
2018 | | - else if (!got) cb(new Error(req._t('error.MissingBlob', {key: key}))) |
2019 | | - else cb(null, ssb.blobs.get(key)) |
2020 | | - }) |
| 2044 | + if (length != null) |
| 2045 | + headers['Content-Length'] = length |
| 2046 | + return function (read) { |
| 2047 | + return cat([pull.once([200, headers]), read]) |
2021 | 2048 | } |
| 2049 | +} |
2022 | 2050 | |
2023 | | - function serveBlob(req, key) { |
2024 | | - return readNext(function (cb) { |
2025 | | - getBlob(req, key, function (err, read) { |
2026 | | - if (err) cb(null, serveError(req, err)) |
2027 | | - else if (!read) cb(null, serve404(req)) |
2028 | | - else cb(null, serveRaw()(read)) |
2029 | | - }) |
| 2051 | +G.getBlob = function (req, key, cb) { |
| 2052 | + var blobs = this.ssb.blobs |
| 2053 | + blobs.want(key, function (err, got) { |
| 2054 | + if (err) cb(err) |
| 2055 | + else if (!got) cb(new Error(req._t('error.MissingBlob', {key: key}))) |
| 2056 | + else cb(null, blobs.get(key)) |
| 2057 | + }) |
| 2058 | +} |
| 2059 | + |
| 2060 | +G.serveBlob = function (req, key) { |
| 2061 | + var self = this |
| 2062 | + return readNext(function (cb) { |
| 2063 | + self.getBlob(req, key, function (err, read) { |
| 2064 | + if (err) cb(null, self.serveError(req, err)) |
| 2065 | + else if (!read) cb(null, self.serve404(req)) |
| 2066 | + else cb(null, self.serveRaw()(read)) |
2030 | 2067 | }) |
2031 | | - } |
| 2068 | + }) |
| 2069 | +} |
2032 | 2070 | |
2033 | | - |
| 2071 | + |
2034 | 2072 | |
2035 | | - function serveRepoDigs(req, repo) { |
2036 | | - return readNext(function (cb) { |
2037 | | - var title = req._t('Digs') + ' · %{author}/%{repo}' |
2038 | | - getVotes(repo.id, function (err, votes) { |
2039 | | - cb(null, renderRepoPage(req, repo, null, null, title, cat([ |
2040 | | - pull.once('<section><h3>' + req._t('Digs') + '</h3>' + |
2041 | | - '<div>' + req._t('Total') + ': ' + votes.upvotes + '</div>'), |
2042 | | - pull( |
2043 | | - pull.values(Object.keys(votes.upvoters)), |
2044 | | - paramap(function (feedId, cb) { |
2045 | | - about.getName(feedId, function (err, name) { |
2046 | | - if (err) return cb(err) |
2047 | | - cb(null, link([feedId], name)) |
2048 | | - }) |
2049 | | - }, 8), |
2050 | | - ul() |
2051 | | - ), |
2052 | | - pull.once('</section>') |
2053 | | - ]))) |
2054 | | - }) |
| 2073 | +G.serveRepoDigs = function (req, repo) { |
| 2074 | + var self = this |
| 2075 | + return readNext(function (cb) { |
| 2076 | + var title = req._t('Digs') + ' · %{author}/%{repo}' |
| 2077 | + self.getVotes(repo.id, function (err, votes) { |
| 2078 | + cb(null, self.renderRepoPage(req, repo, null, null, title, cat([ |
| 2079 | + pull.once('<section><h3>' + req._t('Digs') + '</h3>' + |
| 2080 | + '<div>' + req._t('Total') + ': ' + votes.upvotes + '</div>'), |
| 2081 | + pull( |
| 2082 | + pull.values(Object.keys(votes.upvoters)), |
| 2083 | + paramap(function (feedId, cb) { |
| 2084 | + self.about.getName(feedId, function (err, name) { |
| 2085 | + if (err) return cb(err) |
| 2086 | + cb(null, link([feedId], name)) |
| 2087 | + }) |
| 2088 | + }, 8), |
| 2089 | + ul() |
| 2090 | + ), |
| 2091 | + pull.once('</section>') |
| 2092 | + ]))) |
2055 | 2093 | }) |
2056 | | - } |
| 2094 | + }) |
| 2095 | +} |
2057 | 2096 | |
2058 | | - |
| 2097 | + |
2059 | 2098 | |
2060 | | - function getForks(repo, includeSelf) { |
2061 | | - return pull( |
2062 | | - cat([ |
2063 | | - includeSelf && readOnce(function (cb) { |
2064 | | - getMsg(repo.id, function (err, value) { |
2065 | | - cb(err, value && {key: repo.id, value: value}) |
2066 | | - }) |
2067 | | - }), |
2068 | | - ssb.links({ |
2069 | | - dest: repo.id, |
2070 | | - values: true, |
2071 | | - rel: 'upstream' |
| 2099 | +G.getForks = function (repo, includeSelf) { |
| 2100 | + var self = this |
| 2101 | + return pull( |
| 2102 | + cat([ |
| 2103 | + includeSelf && readOnce(function (cb) { |
| 2104 | + self.getMsg(repo.id, function (err, value) { |
| 2105 | + cb(err, value && {key: repo.id, value: value}) |
2072 | 2106 | }) |
2073 | | - ]), |
2074 | | - pull.filter(function (msg) { |
2075 | | - return msg.value.content && msg.value.content.type == 'git-repo' |
2076 | 2107 | }), |
2077 | | - paramap(function (msg, cb) { |
2078 | | - getRepoFullName(about, msg.value.author, msg.key, |
2079 | | - function (err, repoName, authorName) { |
2080 | | - if (err) return cb(err) |
2081 | | - cb(null, { |
2082 | | - key: msg.key, |
2083 | | - value: msg.value, |
2084 | | - repoName: repoName, |
2085 | | - authorName: authorName |
2086 | | - }) |
| 2108 | + this.ssb.links({ |
| 2109 | + dest: repo.id, |
| 2110 | + values: true, |
| 2111 | + rel: 'upstream' |
| 2112 | + }) |
| 2113 | + ]), |
| 2114 | + pull.filter(function (msg) { |
| 2115 | + return msg.value.content && msg.value.content.type == 'git-repo' |
| 2116 | + }), |
| 2117 | + paramap(function (msg, cb) { |
| 2118 | + self.getRepoFullName(msg.value.author, msg.key, |
| 2119 | + function (err, repoName, authorName) { |
| 2120 | + if (err) return cb(err) |
| 2121 | + cb(null, { |
| 2122 | + key: msg.key, |
| 2123 | + value: msg.value, |
| 2124 | + repoName: repoName, |
| 2125 | + authorName: authorName |
2087 | 2126 | }) |
2088 | | - }, 8) |
2089 | | - ) |
2090 | | - } |
| 2127 | + }) |
| 2128 | + }, 8) |
| 2129 | + ) |
| 2130 | +} |
2091 | 2131 | |
2092 | | - function serveRepoForks(req, repo) { |
2093 | | - var hasForks |
2094 | | - var title = req._t('Forks') + ' · %{author}/%{repo}' |
2095 | | - return renderRepoPage(req, repo, null, null, title, cat([ |
2096 | | - pull.once('<h3>' + req._t('Forks') + '</h3>'), |
2097 | | - pull( |
2098 | | - getForks(repo), |
2099 | | - pull.map(function (msg) { |
2100 | | - hasForks = true |
2101 | | - return '<section class="collapse">' + |
2102 | | - link([msg.value.author], msg.authorName) + ' / ' + |
2103 | | - link([msg.key], msg.repoName) + |
2104 | | - '<span class="right-bar">' + |
2105 | | - timestamp(msg.value.timestamp, req) + |
2106 | | - '</span></section>' |
2107 | | - }) |
2108 | | - ), |
2109 | | - readOnce(function (cb) { |
2110 | | - cb(null, hasForks ? '' : req._t('NoForks')) |
| 2132 | +G.serveRepoForks = function (req, repo) { |
| 2133 | + var hasForks |
| 2134 | + var title = req._t('Forks') + ' · %{author}/%{repo}' |
| 2135 | + return this.renderRepoPage(req, repo, null, null, title, cat([ |
| 2136 | + pull.once('<h3>' + req._t('Forks') + '</h3>'), |
| 2137 | + pull( |
| 2138 | + this.getForks(repo), |
| 2139 | + pull.map(function (msg) { |
| 2140 | + hasForks = true |
| 2141 | + return '<section class="collapse">' + |
| 2142 | + link([msg.value.author], msg.authorName) + ' / ' + |
| 2143 | + link([msg.key], msg.repoName) + |
| 2144 | + '<span class="right-bar">' + |
| 2145 | + timestamp(msg.value.timestamp, req) + |
| 2146 | + '</span></section>' |
2111 | 2147 | }) |
2112 | | - ])) |
2113 | | - } |
| 2148 | + ), |
| 2149 | + readOnce(function (cb) { |
| 2150 | + cb(null, hasForks ? '' : req._t('NoForks')) |
| 2151 | + }) |
| 2152 | + ])) |
| 2153 | +} |
2114 | 2154 | |
2115 | | - function serveRepoForkPrompt(req, repo) { |
2116 | | - var title = req._t('Fork') + ' · %{author}/%{repo}' |
2117 | | - return renderRepoPage(req, repo, null, null, title, pull.once( |
2118 | | - '<form action="" method="post" onreset="history.back()">' + |
2119 | | - '<h3>' + req._t('ForkRepoPrompt') + '</h3>' + |
2120 | | - '<p>' + hiddenInputs({ id: repo.id }) + |
2121 | | - '<button class="btn open" type="submit" name="action" value="fork">' + |
2122 | | - req._t('Fork') + |
2123 | | - '</button>' + |
2124 | | - ' <button class="btn" type="reset">' + |
2125 | | - req._t('Cancel') + '</button>' + |
2126 | | - '</p></form>' |
2127 | | - )) |
2128 | | - } |
| 2155 | +G.serveRepoForkPrompt = function (req, repo) { |
| 2156 | + var title = req._t('Fork') + ' · %{author}/%{repo}' |
| 2157 | + return this.renderRepoPage(req, repo, null, null, title, pull.once( |
| 2158 | + '<form action="" method="post" onreset="history.back()">' + |
| 2159 | + '<h3>' + req._t('ForkRepoPrompt') + '</h3>' + |
| 2160 | + '<p>' + hiddenInputs({ id: repo.id }) + |
| 2161 | + '<button class="btn open" type="submit" name="action" value="fork">' + |
| 2162 | + req._t('Fork') + |
| 2163 | + '</button>' + |
| 2164 | + ' <button class="btn" type="reset">' + |
| 2165 | + req._t('Cancel') + '</button>' + |
| 2166 | + '</p></form>' |
| 2167 | + )) |
| 2168 | +} |
2129 | 2169 | |
2130 | 2170 | |
2131 | 2171 | |
2132 | | - function serveRepoIssues(req, repo, isPRs) { |
2133 | | - var count = 0 |
2134 | | - var state = req._u.query.state || 'open' |
2135 | | - var newPath = isPRs ? [repo.id, 'compare'] : [repo.id, 'issues', 'new'] |
2136 | | - var title = req._t('Issues') + ' · %{author}/%{repo}' |
2137 | | - return renderRepoPage(req, repo, isPRs ? 'pulls' : 'issues', null, title, cat([ |
2138 | | - pull.once( |
2139 | | - (isPublic ? '' : |
2140 | | - '<form class="right-bar" method="get"' + |
2141 | | - ' action="' + encodeLink(newPath) + '">' + |
2142 | | - '<button class="btn">+ ' + |
2143 | | - req._t(isPRs ? 'pullRequest.New' : 'issue.New') + |
2144 | | - '</button>' + |
2145 | | - '</form>') + |
2146 | | - '<h3>' + req._t(isPRs ? 'PullRequests' : 'Issues') + '</h3>' + |
2147 | | - nav([ |
2148 | | - ['?', req._t('issues.Open'), 'open'], |
2149 | | - ['?state=closed', req._t('issues.Closed'), 'closed'], |
2150 | | - ['?state=all', req._t('issues.All'), 'all'] |
2151 | | - ], state)), |
2152 | | - pull( |
2153 | | - (isPRs ? pullReqs : issues).list({ |
2154 | | - repo: repo.id, |
2155 | | - project: repo.id, |
2156 | | - open: {open: true, closed: false}[state] |
2157 | | - }), |
2158 | | - pull.map(function (issue) { |
2159 | | - count++ |
2160 | | - var state = (issue.open ? 'open' : 'closed') |
2161 | | - var stateStr = req._t(issue.open ? |
2162 | | - 'issue.state.Open' : 'issue.state.Closed') |
2163 | | - return '<section class="collapse">' + |
2164 | | - '<i class="issue-state issue-state-' + state + '"' + |
2165 | | - ' title="' + stateStr + '">◼</i> ' + |
2166 | | - '<a href="' + encodeLink(issue.id) + '">' + |
2167 | | - escapeHTML(issue.title) + |
2168 | | - '<span class="right-bar">' + |
2169 | | - new Date(issue.created_at).toLocaleString(req._locale) + |
2170 | | - '</span>' + |
2171 | | - '</a>' + |
2172 | | - '</section>' |
2173 | | - }) |
2174 | | - ), |
2175 | | - readOnce(function (cb) { |
2176 | | - cb(null, count > 0 ? '' : |
2177 | | - '<p>' + req._t(isPRs ? 'NoPullRequests' : 'NoIssues') + '</p>') |
| 2172 | +G.serveRepoIssues = function (req, repo, isPRs) { |
| 2173 | + var self = this |
| 2174 | + var count = 0 |
| 2175 | + var state = req._u.query.state || 'open' |
| 2176 | + var newPath = isPRs ? [repo.id, 'compare'] : [repo.id, 'issues', 'new'] |
| 2177 | + var title = req._t('Issues') + ' · %{author}/%{repo}' |
| 2178 | + var page = isPRs ? 'pulls' : 'issues' |
| 2179 | + return self.renderRepoPage(req, repo, page, null, title, cat([ |
| 2180 | + pull.once( |
| 2181 | + (self.isPublic ? '' : |
| 2182 | + '<form class="right-bar" method="get"' + |
| 2183 | + ' action="' + encodeLink(newPath) + '">' + |
| 2184 | + '<button class="btn">+ ' + |
| 2185 | + req._t(isPRs ? 'pullRequest.New' : 'issue.New') + |
| 2186 | + '</button>' + |
| 2187 | + '</form>') + |
| 2188 | + '<h3>' + req._t(isPRs ? 'PullRequests' : 'Issues') + '</h3>' + |
| 2189 | + nav([ |
| 2190 | + ['?', req._t('issues.Open'), 'open'], |
| 2191 | + ['?state=closed', req._t('issues.Closed'), 'closed'], |
| 2192 | + ['?state=all', req._t('issues.All'), 'all'] |
| 2193 | + ], state)), |
| 2194 | + pull( |
| 2195 | + (isPRs ? self.pullReqs : self.issues).list({ |
| 2196 | + repo: repo.id, |
| 2197 | + project: repo.id, |
| 2198 | + open: {open: true, closed: false}[state] |
| 2199 | + }), |
| 2200 | + pull.map(function (issue) { |
| 2201 | + count++ |
| 2202 | + var state = (issue.open ? 'open' : 'closed') |
| 2203 | + var stateStr = req._t(issue.open ? |
| 2204 | + 'issue.state.Open' : 'issue.state.Closed') |
| 2205 | + return '<section class="collapse">' + |
| 2206 | + '<i class="issue-state issue-state-' + state + '"' + |
| 2207 | + ' title="' + stateStr + '">◼</i> ' + |
| 2208 | + '<a href="' + encodeLink(issue.id) + '">' + |
| 2209 | + escapeHTML(issue.title) + |
| 2210 | + '<span class="right-bar">' + |
| 2211 | + new Date(issue.created_at).toLocaleString(req._locale) + |
| 2212 | + '</span>' + |
| 2213 | + '</a>' + |
| 2214 | + '</section>' |
2178 | 2215 | }) |
2179 | | - ])) |
2180 | | - } |
| 2216 | + ), |
| 2217 | + readOnce(function (cb) { |
| 2218 | + cb(null, count > 0 ? '' : |
| 2219 | + '<p>' + req._t(isPRs ? 'NoPullRequests' : 'NoIssues') + '</p>') |
| 2220 | + }) |
| 2221 | + ])) |
| 2222 | +} |
2181 | 2223 | |
2182 | | - |
| 2224 | + |
2183 | 2225 | |
2184 | | - function serveRepoNewIssue(req, repo, issueId, path) { |
2185 | | - var title = req._t('issue.New') + ' · %{author}/%{repo}' |
2186 | | - return renderRepoPage(req, repo, 'issues', null, title, pull.once( |
2187 | | - '<h3>' + req._t('issue.New') + '</h3>' + |
2188 | | - '<section><form action="" method="post">' + |
2189 | | - '<input type="hidden" name="action" value="new-issue">' + |
2190 | | - '<p><input class="wide-input" name="title" placeholder="' + |
2191 | | - req._t('issue.Title') + '" size="77" /></p>' + |
2192 | | - renderPostForm(req, repo, req._t('Description'), 8) + |
2193 | | - '<button type="submit" class="btn">' + req._t('Create') + '</button>' + |
2194 | | - '</form></section>')) |
2195 | | - } |
| 2226 | +G.serveRepoNewIssue = function (req, repo, issueId, path) { |
| 2227 | + var title = req._t('issue.New') + ' · %{author}/%{repo}' |
| 2228 | + return this.renderRepoPage(req, repo, 'issues', null, title, pull.once( |
| 2229 | + '<h3>' + req._t('issue.New') + '</h3>' + |
| 2230 | + '<section><form action="" method="post">' + |
| 2231 | + '<input type="hidden" name="action" value="new-issue">' + |
| 2232 | + '<p><input class="wide-input" name="title" placeholder="' + |
| 2233 | + req._t('issue.Title') + '" size="77" /></p>' + |
| 2234 | + renderPostForm(req, repo, req._t('Description'), 8) + |
| 2235 | + '<button type="submit" class="btn">' + req._t('Create') + '</button>' + |
| 2236 | + '</form></section>')) |
| 2237 | +} |
2196 | 2238 | |
2197 | | - |
| 2239 | + |
2198 | 2240 | |
2199 | | - function serveRepoIssue(req, repo, issue, path, postId) { |
2200 | | - var isAuthor = (myId == issue.author) || (myId == repo.feed) |
2201 | | - var newestMsg = {key: issue.id, value: {timestamp: issue.created_at}} |
2202 | | - var title = escapeHTML(issue.title) + ' · %{author}/%{repo}' |
2203 | | - return renderRepoPage(req, repo, 'issues', null, title, cat([ |
2204 | | - pull.once( |
2205 | | - renderNameForm(req, !isPublic, issue.id, issue.title, 'issue-title', |
2206 | | - null, req._t('issue.Rename'), |
2207 | | - '<h3>' + link([issue.id], issue.title) + '</h3>') + |
2208 | | - '<code>' + issue.id + '</code>' + |
2209 | | - '<section class="collapse">' + |
2210 | | - (issue.open |
2211 | | - ? '<strong class="issue-status open">' + |
2212 | | - req._t('issue.state.Open') + '</strong>' |
2213 | | - : '<strong class="issue-status closed">' + |
2214 | | - req._t('issue.state.Closed') + '</strong>')), |
2215 | | - readOnce(function (cb) { |
2216 | | - about.getName(issue.author, function (err, authorName) { |
2217 | | - if (err) return cb(err) |
2218 | | - var authorLink = link([issue.author], authorName) |
2219 | | - cb(null, req._t('issue.Opened', |
2220 | | - {name: authorLink, datetime: timestamp(issue.created_at, req)})) |
2221 | | - }) |
| 2241 | +G.serveRepoIssue = function (req, repo, issue, path, postId) { |
| 2242 | + var self = this |
| 2243 | + var isAuthor = (self.myId == issue.author) || (self.myId == repo.feed) |
| 2244 | + var newestMsg = {key: issue.id, value: {timestamp: issue.created_at}} |
| 2245 | + var title = escapeHTML(issue.title) + ' · %{author}/%{repo}' |
| 2246 | + return self.renderRepoPage(req, repo, 'issues', null, title, cat([ |
| 2247 | + pull.once( |
| 2248 | + renderNameForm(req, !self.isPublic, issue.id, issue.title, 'issue-title', |
| 2249 | + null, req._t('issue.Rename'), |
| 2250 | + '<h3>' + link([issue.id], issue.title) + '</h3>') + |
| 2251 | + '<code>' + issue.id + '</code>' + |
| 2252 | + '<section class="collapse">' + |
| 2253 | + (issue.open |
| 2254 | + ? '<strong class="issue-status open">' + |
| 2255 | + req._t('issue.state.Open') + '</strong>' |
| 2256 | + : '<strong class="issue-status closed">' + |
| 2257 | + req._t('issue.state.Closed') + '</strong>')), |
| 2258 | + readOnce(function (cb) { |
| 2259 | + self.about.getName(issue.author, function (err, authorName) { |
| 2260 | + if (err) return cb(err) |
| 2261 | + var authorLink = link([issue.author], authorName) |
| 2262 | + cb(null, req._t('issue.Opened', |
| 2263 | + {name: authorLink, datetime: timestamp(issue.created_at, req)})) |
| 2264 | + }) |
| 2265 | + }), |
| 2266 | + pull.once('<hr/>' + markdown(issue.text, repo) + '</section>'), |
| 2267 | + |
| 2268 | + pull( |
| 2269 | + self.ssb.links({ |
| 2270 | + dest: issue.id, |
| 2271 | + values: true |
2222 | 2272 | }), |
2223 | | - pull.once('<hr/>' + markdown(issue.text, repo) + '</section>'), |
2224 | | - |
2225 | | - pull( |
2226 | | - ssb.links({ |
2227 | | - dest: issue.id, |
2228 | | - values: true |
2229 | | - }), |
2230 | | - pull.unique('key'), |
2231 | | - addAuthorName(about), |
2232 | | - sortMsgs(), |
2233 | | - pull.through(function (msg) { |
2234 | | - if (msg.value.timestamp > newestMsg.value.timestamp) |
2235 | | - newestMsg = msg |
2236 | | - }), |
2237 | | - pull.map(renderIssueActivityMsg.bind(null, req, repo, issue, |
2238 | | - req._t('issue.'), postId)) |
2239 | | - ), |
2240 | | - isPublic ? pull.empty() : readOnce(function (cb) { |
2241 | | - cb(null, renderIssueCommentForm(req, issue, repo, newestMsg.key, |
2242 | | - isAuthor, req._t('issue.'))) |
2243 | | - }) |
2244 | | - ])) |
2245 | | - } |
| 2273 | + pull.unique('key'), |
| 2274 | + self.addAuthorName(), |
| 2275 | + sortMsgs(), |
| 2276 | + pull.through(function (msg) { |
| 2277 | + if (msg.value.timestamp > newestMsg.value.timestamp) |
| 2278 | + newestMsg = msg |
| 2279 | + }), |
| 2280 | + pull.map(self.renderIssueActivityMsg.bind(self, req, repo, issue, |
| 2281 | + req._t('issue.'), postId)) |
| 2282 | + ), |
| 2283 | + self.isPublic ? pull.empty() : readOnce(function (cb) { |
| 2284 | + cb(null, renderIssueCommentForm(req, issue, repo, newestMsg.key, |
| 2285 | + isAuthor, req._t('issue.'))) |
| 2286 | + }) |
| 2287 | + ])) |
| 2288 | +} |
2246 | 2289 | |
2247 | | - function renderIssueActivityMsg(req, repo, issue, type, postId, msg) { |
2248 | | - var authorLink = link([msg.value.author], msg.authorName) |
2249 | | - var msgHref = encodeLink(msg.key) + '#' + encodeURIComponent(msg.key) |
2250 | | - var msgTimeLink = '<a href="' + msgHref + '"' + |
2251 | | - ' name="' + escapeHTML(msg.key) + '">' + |
2252 | | - new Date(msg.value.timestamp).toLocaleString(req._locale) + '</a>' |
2253 | | - var c = msg.value.content |
2254 | | - switch (c.type) { |
2255 | | - case 'post': |
2256 | | - if (c.root == issue.id) { |
2257 | | - var changed = issues.isStatusChanged(msg, issue) |
2258 | | - return '<section class="collapse">' + |
2259 | | - (msg.key == postId ? '<div class="highlight">' : '') + |
2260 | | - '<tt class="right-bar item-id">' + msg.key + '</tt> ' + |
2261 | | - (changed == null ? authorLink : req._t( |
2262 | | - changed ? 'issue.Reopened' : 'issue.Closed', |
2263 | | - {name: authorLink, type: type})) + |
2264 | | - ' · ' + msgTimeLink + |
2265 | | - (msg.key == postId ? '</div>' : '') + |
2266 | | - markdown(c.text, repo) + |
2267 | | - '</section>' |
2268 | | - } else { |
2269 | | - var text = c.text || (c.type + ' ' + msg.key) |
2270 | | - return '<section class="collapse mention-preview">' + |
2271 | | - req._t('issue.MentionedIn', { |
2272 | | - name: authorLink, |
2273 | | - type: type, |
2274 | | - post: '<a href="/' + msg.key + '#' + msg.key + '">' + |
2275 | | - String(text).substr(0, 140) + '</a>' |
2276 | | - }) + '</section>' |
2277 | | - } |
2278 | | - case 'issue': |
2279 | | - case 'pull-request': |
| 2290 | +G.renderIssueActivityMsg = function (req, repo, issue, type, postId, msg) { |
| 2291 | + var authorLink = link([msg.value.author], msg.authorName) |
| 2292 | + var msgHref = encodeLink(msg.key) + '#' + encodeURIComponent(msg.key) |
| 2293 | + var msgTimeLink = '<a href="' + msgHref + '"' + |
| 2294 | + ' name="' + escapeHTML(msg.key) + '">' + |
| 2295 | + new Date(msg.value.timestamp).toLocaleString(req._locale) + '</a>' |
| 2296 | + var c = msg.value.content |
| 2297 | + switch (c.type) { |
| 2298 | + case 'post': |
| 2299 | + if (c.root == issue.id) { |
| 2300 | + var changed = this.issues.isStatusChanged(msg, issue) |
| 2301 | + return '<section class="collapse">' + |
| 2302 | + (msg.key == postId ? '<div class="highlight">' : '') + |
| 2303 | + '<tt class="right-bar item-id">' + msg.key + '</tt> ' + |
| 2304 | + (changed == null ? authorLink : req._t( |
| 2305 | + changed ? 'issue.Reopened' : 'issue.Closed', |
| 2306 | + {name: authorLink, type: type})) + |
| 2307 | + ' · ' + msgTimeLink + |
| 2308 | + (msg.key == postId ? '</div>' : '') + |
| 2309 | + markdown(c.text, repo) + |
| 2310 | + '</section>' |
| 2311 | + } else { |
| 2312 | + var text = c.text || (c.type + ' ' + msg.key) |
2280 | 2313 | return '<section class="collapse mention-preview">' + |
2281 | 2314 | req._t('issue.MentionedIn', { |
2282 | 2315 | name: authorLink, |
2283 | 2316 | type: type, |
2284 | | - post: link([msg.key], String(c.title || msg.key).substr(0, 140)) |
| 2317 | + post: '<a href="/' + msg.key + '#' + msg.key + '">' + |
| 2318 | + String(text).substr(0, 140) + '</a>' |
2285 | 2319 | }) + '</section>' |
2286 | | - case 'issue-edit': |
| 2320 | + } |
| 2321 | + case 'issue': |
| 2322 | + case 'pull-request': |
| 2323 | + return '<section class="collapse mention-preview">' + |
| 2324 | + req._t('issue.MentionedIn', { |
| 2325 | + name: authorLink, |
| 2326 | + type: type, |
| 2327 | + post: link([msg.key], String(c.title || msg.key).substr(0, 140)) |
| 2328 | + }) + '</section>' |
| 2329 | + case 'issue-edit': |
| 2330 | + return '<section class="collapse">' + |
| 2331 | + (msg.key == postId ? '<div class="highlight">' : '') + |
| 2332 | + (c.title == null ? '' : req._t('issue.Renamed', { |
| 2333 | + name: authorLink, |
| 2334 | + type: type, |
| 2335 | + name: '<q>' + escapeHTML(c.title) + '</q>' |
| 2336 | + })) + ' · ' + msgTimeLink + |
| 2337 | + (msg.key == postId ? '</div>' : '') + |
| 2338 | + '</section>' |
| 2339 | + case 'git-update': |
| 2340 | + var mention = this.issues.getMention(msg, issue) |
| 2341 | + if (mention) { |
| 2342 | + var commitLink = link([repo.id, 'commit', mention.object], |
| 2343 | + mention.label || mention.object) |
2287 | 2344 | return '<section class="collapse">' + |
2288 | | - (msg.key == postId ? '<div class="highlight">' : '') + |
2289 | | - (c.title == null ? '' : req._t('issue.Renamed', { |
| 2345 | + req._t(mention.open ? 'issue.Reopened' : 'issue.Closed', { |
2290 | 2346 | name: authorLink, |
2291 | | - type: type, |
2292 | | - name: '<q>' + escapeHTML(c.title) + '</q>' |
2293 | | - })) + ' · ' + msgTimeLink + |
2294 | | - (msg.key == postId ? '</div>' : '') + |
| 2347 | + type: type |
| 2348 | + }) + ' · ' + msgTimeLink + '<br/>' + |
| 2349 | + commitLink + |
2295 | 2350 | '</section>' |
2296 | | - case 'git-update': |
2297 | | - var mention = issues.getMention(msg, issue) |
2298 | | - if (mention) { |
2299 | | - var commitLink = link([repo.id, 'commit', mention.object], |
2300 | | - mention.label || mention.object) |
2301 | | - return '<section class="collapse">' + |
2302 | | - req._t(mention.open ? 'issue.Reopened' : 'issue.Closed', { |
2303 | | - name: authorLink, |
2304 | | - type: type |
2305 | | - }) + ' · ' + msgTimeLink + '<br/>' + |
2306 | | - commitLink + |
2307 | | - '</section>' |
2308 | | - } else if ((mention = getMention(msg, issue.id))) { |
2309 | | - var commitLink = link(mention.object ? |
2310 | | - [repo.id, 'commit', mention.object] : [msg.key], |
2311 | | - mention.label || mention.object || msg.key) |
2312 | | - return '<section class="collapse">' + |
2313 | | - req._t('issue.Mentioned', { |
2314 | | - name: authorLink, |
2315 | | - type: type |
2316 | | - }) + ' · ' + msgTimeLink + '<br/>' + |
2317 | | - commitLink + |
2318 | | - '</section>' |
2319 | | - } else { |
2320 | | - |
2321 | | - } |
2322 | | - |
2323 | | - default: |
| 2351 | + } else if ((mention = getMention(msg, issue.id))) { |
| 2352 | + var commitLink = link(mention.object ? |
| 2353 | + [repo.id, 'commit', mention.object] : [msg.key], |
| 2354 | + mention.label || mention.object || msg.key) |
2324 | 2355 | return '<section class="collapse">' + |
2325 | | - authorLink + |
2326 | | - ' · ' + msgTimeLink + |
2327 | | - json(c) + |
| 2356 | + req._t('issue.Mentioned', { |
| 2357 | + name: authorLink, |
| 2358 | + type: type |
| 2359 | + }) + ' · ' + msgTimeLink + '<br/>' + |
| 2360 | + commitLink + |
2328 | 2361 | '</section>' |
2329 | | - } |
| 2362 | + } else { |
| 2363 | + |
| 2364 | + } |
| 2365 | + |
| 2366 | + default: |
| 2367 | + return '<section class="collapse">' + |
| 2368 | + authorLink + |
| 2369 | + ' · ' + msgTimeLink + |
| 2370 | + json(c) + |
| 2371 | + '</section>' |
2330 | 2372 | } |
| 2373 | +} |
2331 | 2374 | |
2332 | 2375 | function renderIssueCommentForm(req, issue, repo, branch, isAuthor, type) { |
2333 | 2376 | return '<section><form action="" method="post">' + |
2334 | 2377 | '<input type="hidden" name="action" value="comment">' + |
2349 | 2392 | } |
2350 | 2393 | |
2351 | 2394 | |
2352 | 2395 | |
2353 | | - function serveRepoPullReq(req, repo, pr, path, postId) { |
2354 | | - var headRepo, authorLink |
2355 | | - var page = path[0] || 'activity' |
2356 | | - var title = escapeHTML(pr.title) + ' · %{author}/%{repo}' |
2357 | | - return renderRepoPage(req, repo, 'pulls', null, title, cat([ |
2358 | | - pull.once('<div class="pull-request">' + |
2359 | | - renderNameForm(req, !isPublic, pr.id, pr.title, 'issue-title', null, |
2360 | | - req._t('pullRequest.Rename'), |
2361 | | - '<h3>' + link([pr.id], pr.title) + '</h3>') + |
2362 | | - '<code>' + pr.id + '</code>'), |
2363 | | - readOnce(function (cb) { |
2364 | | - var done = multicb({ pluck: 1, spread: true }) |
2365 | | - var gotHeadRepo = done() |
2366 | | - about.getName(pr.author, done()) |
2367 | | - var sameRepo = (pr.headRepo == pr.baseRepo) |
2368 | | - getRepo(pr.headRepo, function (err, headRepo) { |
2369 | | - if (err) return cb(err) |
2370 | | - getRepoName(about, headRepo.feed, headRepo.id, done()) |
2371 | | - about.getName(headRepo.feed, done()) |
2372 | | - gotHeadRepo(null, Repo(headRepo)) |
2373 | | - }) |
| 2396 | +G.serveRepoPullReq = function (req, repo, pr, path, postId) { |
| 2397 | + var self = this |
| 2398 | + var headRepo, authorLink |
| 2399 | + var page = path[0] || 'activity' |
| 2400 | + var title = escapeHTML(pr.title) + ' · %{author}/%{repo}' |
| 2401 | + return self.renderRepoPage(req, repo, 'pulls', null, title, cat([ |
| 2402 | + pull.once('<div class="pull-request">' + |
| 2403 | + renderNameForm(req, !self.isPublic, pr.id, pr.title, 'issue-title', null, |
| 2404 | + req._t('pullRequest.Rename'), |
| 2405 | + '<h3>' + link([pr.id], pr.title) + '</h3>') + |
| 2406 | + '<code>' + pr.id + '</code>'), |
| 2407 | + readOnce(function (cb) { |
| 2408 | + var done = multicb({ pluck: 1, spread: true }) |
| 2409 | + var gotHeadRepo = done() |
| 2410 | + self.about.getName(pr.author, done()) |
| 2411 | + var sameRepo = (pr.headRepo == pr.baseRepo) |
| 2412 | + self.getRepo(pr.headRepo, function (err, headRepo) { |
| 2413 | + if (err) return cb(err) |
| 2414 | + self.getRepoName(headRepo.feed, headRepo.id, done()) |
| 2415 | + self.about.getName(headRepo.feed, done()) |
| 2416 | + gotHeadRepo(null, Repo(headRepo)) |
| 2417 | + }) |
2374 | 2418 | |
2375 | | - done(function (err, _headRepo, issueAuthorName, |
2376 | | - headRepoName, headRepoAuthorName) { |
2377 | | - if (err) return cb(err) |
2378 | | - headRepo = _headRepo |
2379 | | - authorLink = link([pr.author], issueAuthorName) |
2380 | | - var repoLink = link([pr.headRepo], headRepoName) |
2381 | | - var headRepoAuthorLink = link([headRepo.feed], headRepoAuthorName) |
2382 | | - var headRepoLink = link([headRepo.id], headRepoName) |
2383 | | - var headBranchLink = link([headRepo.id, 'tree', pr.headBranch]) |
2384 | | - var baseBranchLink = link([repo.id, 'tree', pr.baseBranch]) |
2385 | | - cb(null, '<section class="collapse">' + |
2386 | | - '<strong class="issue-status ' + |
2387 | | - (pr.open ? 'open' : 'closed') + '">' + |
2388 | | - req._t(pr.open ? 'issue.state.Open' : 'issue.state.Closed') + |
2389 | | - '</strong> ' + |
2390 | | - req._t('pullRequest.WantToMerge', { |
2391 | | - name: authorLink, |
2392 | | - base: '<code>' + baseBranchLink + '</code>', |
2393 | | - head: (sameRepo ? |
2394 | | - '<code>' + headBranchLink + '</code>' : |
2395 | | - '<code class="bgslash">' + |
2396 | | - headRepoAuthorLink + ' / ' + |
2397 | | - headRepoLink + ' / ' + |
2398 | | - headBranchLink + '</code>') |
2399 | | - }) + '</section>') |
2400 | | - }) |
2401 | | - }), |
2402 | | - pull.once( |
2403 | | - nav([ |
2404 | | - [[pr.id], req._t('Discussion'), 'activity'], |
2405 | | - [[pr.id, 'commits'], req._t('Commits'), 'commits'], |
2406 | | - [[pr.id, 'files'], req._t('Files'), 'files'] |
2407 | | - ], page)), |
2408 | | - readNext(function (cb) { |
2409 | | - if (page == 'commits') |
2410 | | - renderPullReqCommits(req, pr, repo, headRepo, cb) |
2411 | | - else if (page == 'files') |
2412 | | - renderPullReqFiles(req, pr, repo, headRepo, cb) |
2413 | | - else cb(null, |
2414 | | - renderPullReqActivity(req, pr, repo, headRepo, authorLink, postId)) |
| 2419 | + done(function (err, _headRepo, issueAuthorName, |
| 2420 | + headRepoName, headRepoAuthorName) { |
| 2421 | + if (err) return cb(err) |
| 2422 | + headRepo = _headRepo |
| 2423 | + authorLink = link([pr.author], issueAuthorName) |
| 2424 | + var repoLink = link([pr.headRepo], headRepoName) |
| 2425 | + var headRepoAuthorLink = link([headRepo.feed], headRepoAuthorName) |
| 2426 | + var headRepoLink = link([headRepo.id], headRepoName) |
| 2427 | + var headBranchLink = link([headRepo.id, 'tree', pr.headBranch]) |
| 2428 | + var baseBranchLink = link([repo.id, 'tree', pr.baseBranch]) |
| 2429 | + cb(null, '<section class="collapse">' + |
| 2430 | + '<strong class="issue-status ' + |
| 2431 | + (pr.open ? 'open' : 'closed') + '">' + |
| 2432 | + req._t(pr.open ? 'issue.state.Open' : 'issue.state.Closed') + |
| 2433 | + '</strong> ' + |
| 2434 | + req._t('pullRequest.WantToMerge', { |
| 2435 | + name: authorLink, |
| 2436 | + base: '<code>' + baseBranchLink + '</code>', |
| 2437 | + head: (sameRepo ? |
| 2438 | + '<code>' + headBranchLink + '</code>' : |
| 2439 | + '<code class="bgslash">' + |
| 2440 | + headRepoAuthorLink + ' / ' + |
| 2441 | + headRepoLink + ' / ' + |
| 2442 | + headBranchLink + '</code>') |
| 2443 | + }) + '</section>') |
2415 | 2444 | }) |
| 2445 | + }), |
| 2446 | + pull.once( |
| 2447 | + nav([ |
| 2448 | + [[pr.id], req._t('Discussion'), 'activity'], |
| 2449 | + [[pr.id, 'commits'], req._t('Commits'), 'commits'], |
| 2450 | + [[pr.id, 'files'], req._t('Files'), 'files'] |
| 2451 | + ], page)), |
| 2452 | + readNext(function (cb) { |
| 2453 | + if (page == 'commits') |
| 2454 | + self.renderPullReqCommits(req, pr, repo, headRepo, cb) |
| 2455 | + else if (page == 'files') |
| 2456 | + self.renderPullReqFiles(req, pr, repo, headRepo, cb) |
| 2457 | + else cb(null, |
| 2458 | + self.renderPullReqActivity(req, pr, repo, headRepo, authorLink, postId)) |
| 2459 | + }) |
| 2460 | + ])) |
| 2461 | +} |
| 2462 | + |
| 2463 | +G.renderPullReqCommits = function (req, pr, baseRepo, headRepo, cb) { |
| 2464 | + var self = this |
| 2465 | + self.pullReqs.getRevs(pr.id, function (err, revs) { |
| 2466 | + if (err) return cb(null, renderError(err)) |
| 2467 | + cb(null, cat([ |
| 2468 | + pull.once('<section>'), |
| 2469 | + self.renderCommitLog(req, baseRepo, revs.base, headRepo, revs.head), |
| 2470 | + pull.once('</section>') |
2416 | 2471 | ])) |
2417 | | - } |
| 2472 | + }) |
| 2473 | +} |
2418 | 2474 | |
2419 | | - function renderPullReqCommits(req, pr, baseRepo, headRepo, cb) { |
2420 | | - pullReqs.getRevs(pr.id, function (err, revs) { |
2421 | | - if (err) return cb(null, renderError(err)) |
2422 | | - cb(null, cat([ |
2423 | | - pull.once('<section>'), |
2424 | | - renderCommitLog(req, baseRepo, revs.base, headRepo, revs.head), |
2425 | | - pull.once('</section>') |
2426 | | - ])) |
2427 | | - }) |
2428 | | - } |
| 2475 | +G.renderPullReqFiles = function (req, pr, baseRepo, headRepo, cb) { |
| 2476 | + this.pullReqs.getRevs(pr.id, function (err, revs) { |
| 2477 | + if (err) return cb(null, renderError(err)) |
| 2478 | + cb(null, cat([ |
| 2479 | + pull.once('<section>'), |
| 2480 | + renderDiffStat(req, |
| 2481 | + [baseRepo, headRepo], [revs.base, revs.head]), |
| 2482 | + pull.once('</section>') |
| 2483 | + ])) |
| 2484 | + }) |
| 2485 | +} |
2429 | 2486 | |
2430 | | - function renderPullReqFiles(req, pr, baseRepo, headRepo, cb) { |
2431 | | - pullReqs.getRevs(pr.id, function (err, revs) { |
2432 | | - if (err) return cb(null, renderError(err)) |
2433 | | - cb(null, cat([ |
2434 | | - pull.once('<section>'), |
2435 | | - renderDiffStat(req, |
2436 | | - [baseRepo, headRepo], [revs.base, revs.head]), |
2437 | | - pull.once('</section>') |
2438 | | - ])) |
2439 | | - }) |
2440 | | - } |
2441 | | - |
2442 | | - function renderPullReqActivity(req, pr, repo, headRepo, authorLink, postId) { |
2443 | | - var msgTimeLink = link([pr.id], |
2444 | | - new Date(pr.created_at).toLocaleString(req._locale)) |
2445 | | - var newestMsg = {key: pr.id, value: {timestamp: pr.created_at}} |
2446 | | - var isAuthor = (myId == pr.author) || (myId == repo.feed) |
2447 | | - return cat([ |
2448 | | - readOnce(function (cb) { |
2449 | | - cb(null, |
2450 | | - '<section class="collapse">' + |
2451 | | - authorLink + ' · ' + msgTimeLink + |
2452 | | - markdown(pr.text, repo) + '</section>') |
2453 | | - }), |
2454 | | - |
2455 | | - pull( |
2456 | | - many([ |
2457 | | - ssb.links({ |
2458 | | - dest: pr.id, |
2459 | | - values: true |
2460 | | - }), |
2461 | | - readNext(function (cb) { |
2462 | | - cb(null, pull( |
2463 | | - ssb.links({ |
2464 | | - dest: headRepo.id, |
2465 | | - source: headRepo.feed, |
2466 | | - rel: 'repo', |
2467 | | - values: true, |
2468 | | - reverse: true |
2469 | | - }), |
2470 | | - pull.take(function (link) { |
2471 | | - return link.value.timestamp > pr.created_at |
2472 | | - }), |
2473 | | - pull.filter(function (link) { |
2474 | | - return link.value.content.type == 'git-update' |
2475 | | - && ('refs/heads/' + pr.headBranch) in link.value.content.refs |
2476 | | - }) |
2477 | | - )) |
2478 | | - }) |
2479 | | - ]), |
2480 | | - addAuthorName(about), |
2481 | | - pull.unique('key'), |
2482 | | - pull.through(function (msg) { |
2483 | | - if (msg.value.timestamp > newestMsg.value.timestamp) |
2484 | | - newestMsg = msg |
| 2487 | +G.renderPullReqActivity = function (req, pr, repo, headRepo, authorLink, postId) { |
| 2488 | + var self = this |
| 2489 | + var msgTimeLink = link([pr.id], |
| 2490 | + new Date(pr.created_at).toLocaleString(req._locale)) |
| 2491 | + var newestMsg = {key: pr.id, value: {timestamp: pr.created_at}} |
| 2492 | + var isAuthor = (self.myId == pr.author) || (self.myId == repo.feed) |
| 2493 | + return cat([ |
| 2494 | + readOnce(function (cb) { |
| 2495 | + cb(null, |
| 2496 | + '<section class="collapse">' + |
| 2497 | + authorLink + ' · ' + msgTimeLink + |
| 2498 | + markdown(pr.text, repo) + '</section>') |
| 2499 | + }), |
| 2500 | + |
| 2501 | + pull( |
| 2502 | + many([ |
| 2503 | + self.ssb.links({ |
| 2504 | + dest: pr.id, |
| 2505 | + values: true |
2485 | 2506 | }), |
2486 | | - sortMsgs(), |
2487 | | - pull.map(function (item) { |
2488 | | - if (item.value.content.type == 'git-update') |
2489 | | - return renderBranchUpdate(req, pr, item) |
2490 | | - return renderIssueActivityMsg(req, repo, pr, |
2491 | | - req._t('pull request'), postId, item) |
| 2507 | + readNext(function (cb) { |
| 2508 | + cb(null, pull( |
| 2509 | + self.ssb.links({ |
| 2510 | + dest: headRepo.id, |
| 2511 | + source: headRepo.feed, |
| 2512 | + rel: 'repo', |
| 2513 | + values: true, |
| 2514 | + reverse: true |
| 2515 | + }), |
| 2516 | + pull.take(function (link) { |
| 2517 | + return link.value.timestamp > pr.created_at |
| 2518 | + }), |
| 2519 | + pull.filter(function (link) { |
| 2520 | + return link.value.content.type == 'git-update' |
| 2521 | + && ('refs/heads/' + pr.headBranch) in link.value.content.refs |
| 2522 | + }) |
| 2523 | + )) |
2492 | 2524 | }) |
2493 | | - ), |
2494 | | - !isPublic && isAuthor && pr.open && pull.once( |
2495 | | - '<section class="merge-instructions">' + |
2496 | | - '<input type="checkbox" class="toggle" id="merge-instructions"/>' + |
2497 | | - '<h4><label for="merge-instructions" class="toggle-link"><a>' + |
2498 | | - req._t('mergeInstructions.MergeViaCmdLine') + |
2499 | | - '</a></label></h4>' + |
2500 | | - '<div class="contents">' + |
2501 | | - '<p>' + req._t('mergeInstructions.CheckOut') + '</p>' + |
2502 | | - '<pre>' + |
2503 | | - 'git fetch ssb://' + escapeHTML(pr.headRepo) + ' ' + |
2504 | | - escapeHTML(pr.headBranch) + '\n' + |
2505 | | - 'git checkout -b ' + escapeHTML(pr.headBranch) + ' FETCH_HEAD' + |
2506 | | - '</pre>' + |
2507 | | - '<p>' + req._t('mergeInstructions.MergeAndPush') + '</p>' + |
2508 | | - '<pre>' + |
2509 | | - 'git checkout ' + escapeHTML(pr.baseBranch) + '\n' + |
2510 | | - 'git merge ' + escapeHTML(pr.headBranch) + '\n' + |
2511 | | - 'git push ssb ' + escapeHTML(pr.baseBranch) + |
2512 | | - '</pre>' + |
2513 | | - '</div></section>'), |
2514 | | - !isPublic && readOnce(function (cb) { |
2515 | | - cb(null, renderIssueCommentForm(req, pr, repo, newestMsg.key, isAuthor, |
2516 | | - req._t('pull request'))) |
| 2525 | + ]), |
| 2526 | + self.addAuthorName(), |
| 2527 | + pull.unique('key'), |
| 2528 | + pull.through(function (msg) { |
| 2529 | + if (msg.value.timestamp > newestMsg.value.timestamp) |
| 2530 | + newestMsg = msg |
| 2531 | + }), |
| 2532 | + sortMsgs(), |
| 2533 | + pull.map(function (item) { |
| 2534 | + if (item.value.content.type == 'git-update') |
| 2535 | + return self.renderBranchUpdate(req, pr, item) |
| 2536 | + return self.renderIssueActivityMsg(req, repo, pr, |
| 2537 | + req._t('pull request'), postId, item) |
2517 | 2538 | }) |
2518 | | - ]) |
2519 | | - } |
| 2539 | + ), |
| 2540 | + !self.isPublic && isAuthor && pr.open && pull.once( |
| 2541 | + '<section class="merge-instructions">' + |
| 2542 | + '<input type="checkbox" class="toggle" id="merge-instructions"/>' + |
| 2543 | + '<h4><label for="merge-instructions" class="toggle-link"><a>' + |
| 2544 | + req._t('mergeInstructions.MergeViaCmdLine') + |
| 2545 | + '</a></label></h4>' + |
| 2546 | + '<div class="contents">' + |
| 2547 | + '<p>' + req._t('mergeInstructions.CheckOut') + '</p>' + |
| 2548 | + '<pre>' + |
| 2549 | + 'git fetch ssb://' + escapeHTML(pr.headRepo) + ' ' + |
| 2550 | + escapeHTML(pr.headBranch) + '\n' + |
| 2551 | + 'git checkout -b ' + escapeHTML(pr.headBranch) + ' FETCH_HEAD' + |
| 2552 | + '</pre>' + |
| 2553 | + '<p>' + req._t('mergeInstructions.MergeAndPush') + '</p>' + |
| 2554 | + '<pre>' + |
| 2555 | + 'git checkout ' + escapeHTML(pr.baseBranch) + '\n' + |
| 2556 | + 'git merge ' + escapeHTML(pr.headBranch) + '\n' + |
| 2557 | + 'git push ssb ' + escapeHTML(pr.baseBranch) + |
| 2558 | + '</pre>' + |
| 2559 | + '</div></section>'), |
| 2560 | + !self.isPublic && readOnce(function (cb) { |
| 2561 | + cb(null, renderIssueCommentForm(req, pr, repo, newestMsg.key, isAuthor, |
| 2562 | + req._t('pull request'))) |
| 2563 | + }) |
| 2564 | + ]) |
| 2565 | +} |
2520 | 2566 | |
2521 | | - function renderBranchUpdate(req, pr, msg) { |
2522 | | - var authorLink = link([msg.value.author], msg.authorName) |
2523 | | - var msgLink = link([msg.key], |
2524 | | - new Date(msg.value.timestamp).toLocaleString(req._locale)) |
2525 | | - var rev = msg.value.content.refs['refs/heads/' + pr.headBranch] |
2526 | | - if (!rev) |
2527 | | - return '<section class="collapse">' + |
2528 | | - req._t('NameDeletedBranch', { |
2529 | | - name: authorLink, |
2530 | | - branch: '<code>' + pr.headBranch + '</code>' |
2531 | | - }) + ' · ' + msgLink + |
2532 | | - '</section>' |
2533 | | - |
2534 | | - var revLink = link([pr.headRepo, 'commit', rev], rev.substr(0, 8)) |
| 2567 | +G.renderBranchUpdate = function (req, pr, msg) { |
| 2568 | + var authorLink = link([msg.value.author], msg.authorName) |
| 2569 | + var msgLink = link([msg.key], |
| 2570 | + new Date(msg.value.timestamp).toLocaleString(req._locale)) |
| 2571 | + var rev = msg.value.content.refs['refs/heads/' + pr.headBranch] |
| 2572 | + if (!rev) |
2535 | 2573 | return '<section class="collapse">' + |
2536 | | - req._t('NameUpdatedBranch', { |
| 2574 | + req._t('NameDeletedBranch', { |
2537 | 2575 | name: authorLink, |
2538 | | - rev: '<code>' + revLink + '</code>' |
| 2576 | + branch: '<code>' + pr.headBranch + '</code>' |
2539 | 2577 | }) + ' · ' + msgLink + |
2540 | 2578 | '</section>' |
2541 | | - } |
2542 | 2579 | |
2543 | | - |
| 2580 | + var revLink = link([pr.headRepo, 'commit', rev], rev.substr(0, 8)) |
| 2581 | + return '<section class="collapse">' + |
| 2582 | + req._t('NameUpdatedBranch', { |
| 2583 | + name: authorLink, |
| 2584 | + rev: '<code>' + revLink + '</code>' |
| 2585 | + }) + ' · ' + msgLink + |
| 2586 | + '</section>' |
| 2587 | +} |
2544 | 2588 | |
2545 | | - function serveRepoCompare(req, repo) { |
2546 | | - var query = req._u.query |
2547 | | - var base |
2548 | | - var count = 0 |
2549 | | - var title = req._t('CompareChanges') + ' · %{author}/%{repo}' |
| 2589 | + |
2550 | 2590 | |
2551 | | - return renderRepoPage(req, repo, 'pulls', null, title, cat([ |
2552 | | - pull.once('<h3>' + req._t('CompareChanges') + '</h3>' + |
2553 | | - '<form action="' + encodeLink(repo.id) + '/comparing" method="get">' + |
2554 | | - '<section>'), |
2555 | | - pull.once(req._t('BaseBranch') + ': '), |
2556 | | - readNext(function (cb) { |
2557 | | - if (query.base) gotBase(null, query.base) |
2558 | | - else repo.getSymRef('HEAD', true, gotBase) |
2559 | | - function gotBase(err, ref) { |
| 2591 | +G.serveRepoCompare = function (req, repo) { |
| 2592 | + var self = this |
| 2593 | + var query = req._u.query |
| 2594 | + var base |
| 2595 | + var count = 0 |
| 2596 | + var title = req._t('CompareChanges') + ' · %{author}/%{repo}' |
| 2597 | + |
| 2598 | + return self.renderRepoPage(req, repo, 'pulls', null, title, cat([ |
| 2599 | + pull.once('<h3>' + req._t('CompareChanges') + '</h3>' + |
| 2600 | + '<form action="' + encodeLink(repo.id) + '/comparing" method="get">' + |
| 2601 | + '<section>'), |
| 2602 | + pull.once(req._t('BaseBranch') + ': '), |
| 2603 | + readNext(function (cb) { |
| 2604 | + if (query.base) gotBase(null, query.base) |
| 2605 | + else repo.getSymRef('HEAD', true, gotBase) |
| 2606 | + function gotBase(err, ref) { |
| 2607 | + if (err) return cb(err) |
| 2608 | + cb(null, branchMenu(repo, 'base', base = ref || 'HEAD')) |
| 2609 | + } |
| 2610 | + }), |
| 2611 | + pull.once('<br/>' + req._t('ComparisonRepoBranch') + ':'), |
| 2612 | + pull( |
| 2613 | + self.getForks(repo, true), |
| 2614 | + pull.asyncMap(function (msg, cb) { |
| 2615 | + self.getRepo(msg.key, function (err, repo) { |
2560 | 2616 | if (err) return cb(err) |
2561 | | - cb(null, branchMenu(repo, 'base', base = ref || 'HEAD')) |
| 2617 | + cb(null, { |
| 2618 | + msg: msg, |
| 2619 | + repo: repo |
| 2620 | + }) |
| 2621 | + }) |
| 2622 | + }), |
| 2623 | + pull.map(renderFork), |
| 2624 | + pull.flatten() |
| 2625 | + ), |
| 2626 | + pull.once('</section>'), |
| 2627 | + readOnce(function (cb) { |
| 2628 | + cb(null, count == 0 ? req._t('NoBranches') : |
| 2629 | + '<button type="submit" class="btn">' + |
| 2630 | + req._t('Compare') + '</button>') |
| 2631 | + }), |
| 2632 | + pull.once('</form>') |
| 2633 | + ])) |
| 2634 | + |
| 2635 | + function renderFork(fork) { |
| 2636 | + return pull( |
| 2637 | + fork.repo.refs(), |
| 2638 | + pull.map(function (ref) { |
| 2639 | + var m = /^refs\/([^\/]*)\/(.*)$/.exec(ref.name) || [,ref.name] |
| 2640 | + return { |
| 2641 | + type: m[1], |
| 2642 | + name: m[2], |
| 2643 | + value: ref.value |
2562 | 2644 | } |
2563 | 2645 | }), |
2564 | | - pull.once('<br/>' + req._t('ComparisonRepoBranch') + ':'), |
2565 | | - pull( |
2566 | | - getForks(repo, true), |
2567 | | - pull.asyncMap(function (msg, cb) { |
2568 | | - getRepo(msg.key, function (err, repo) { |
2569 | | - if (err) return cb(err) |
2570 | | - cb(null, { |
2571 | | - msg: msg, |
2572 | | - repo: repo |
2573 | | - }) |
2574 | | - }) |
2575 | | - }), |
2576 | | - pull.map(renderFork), |
2577 | | - pull.flatten() |
2578 | | - ), |
2579 | | - pull.once('</section>'), |
2580 | | - readOnce(function (cb) { |
2581 | | - cb(null, count == 0 ? req._t('NoBranches') : |
2582 | | - '<button type="submit" class="btn">' + |
2583 | | - req._t('Compare') + '</button>') |
| 2646 | + pull.filter(function (ref) { |
| 2647 | + return ref.type == 'heads' |
| 2648 | + && !(ref.name == base && fork.msg.key == repo.id) |
2584 | 2649 | }), |
2585 | | - pull.once('</form>') |
2586 | | - ])) |
2587 | | - |
2588 | | - function renderFork(fork) { |
2589 | | - return pull( |
2590 | | - fork.repo.refs(), |
2591 | | - pull.map(function (ref) { |
2592 | | - var m = /^refs\/([^\/]*)\/(.*)$/.exec(ref.name) || [,ref.name] |
2593 | | - return { |
2594 | | - type: m[1], |
2595 | | - name: m[2], |
2596 | | - value: ref.value |
2597 | | - } |
2598 | | - }), |
2599 | | - pull.filter(function (ref) { |
2600 | | - return ref.type == 'heads' |
2601 | | - && !(ref.name == base && fork.msg.key == repo.id) |
2602 | | - }), |
2603 | | - pull.map(function (ref) { |
2604 | | - var branchLink = link([fork.msg.key, 'tree', ref.name], ref.name) |
2605 | | - var authorLink = link([fork.msg.value.author], fork.msg.authorName) |
2606 | | - var repoLink = link([fork.msg.key], fork.msg.repoName) |
2607 | | - var value = fork.msg.key + ':' + ref.name |
2608 | | - count++ |
2609 | | - return '<div class="bgslash">' + |
2610 | | - '<input type="radio" name="head"' + |
2611 | | - ' value="' + escapeHTML(value) + '"' + |
2612 | | - (query.head == value ? ' checked="checked"' : '') + '> ' + |
2613 | | - authorLink + ' / ' + repoLink + ' / ' + branchLink + '</div>' |
2614 | | - }) |
2615 | | - ) |
2616 | | - } |
| 2650 | + pull.map(function (ref) { |
| 2651 | + var branchLink = link([fork.msg.key, 'tree', ref.name], ref.name) |
| 2652 | + var authorLink = link([fork.msg.value.author], fork.msg.authorName) |
| 2653 | + var repoLink = link([fork.msg.key], fork.msg.repoName) |
| 2654 | + var value = fork.msg.key + ':' + ref.name |
| 2655 | + count++ |
| 2656 | + return '<div class="bgslash">' + |
| 2657 | + '<input type="radio" name="head"' + |
| 2658 | + ' value="' + escapeHTML(value) + '"' + |
| 2659 | + (query.head == value ? ' checked="checked"' : '') + '> ' + |
| 2660 | + authorLink + ' / ' + repoLink + ' / ' + branchLink + '</div>' |
| 2661 | + }) |
| 2662 | + ) |
2617 | 2663 | } |
| 2664 | +} |
2618 | 2665 | |
2619 | | - function serveRepoComparing(req, repo) { |
2620 | | - var query = req._u.query |
2621 | | - var baseBranch = query.base |
2622 | | - var s = (query.head || '').split(':') |
| 2666 | +G.serveRepoComparing = function (req, repo) { |
| 2667 | + var self = this |
| 2668 | + var query = req._u.query |
| 2669 | + var baseBranch = query.base |
| 2670 | + var s = (query.head || '').split(':') |
2623 | 2671 | |
2624 | | - if (!s || !baseBranch) |
2625 | | - return serveRedirect(req, encodeLink([repo.id, 'compare'])) |
| 2672 | + if (!s || !baseBranch) |
| 2673 | + return self.serveRedirect(req, encodeLink([repo.id, 'compare'])) |
2626 | 2674 | |
2627 | | - var headRepoId = s[0] |
2628 | | - var headBranch = s[1] |
2629 | | - var baseLink = link([repo.id, 'tree', baseBranch]) |
2630 | | - var headBranchLink = link([headRepoId, 'tree', headBranch]) |
2631 | | - var backHref = encodeLink([repo.id, 'compare']) + req._u.search |
2632 | | - var title = req._t(query.expand ? 'OpenPullRequest': 'ComparingChanges') |
2633 | | - var pageTitle = title + ' · %{author}/%{repo}' |
| 2675 | + var headRepoId = s[0] |
| 2676 | + var headBranch = s[1] |
| 2677 | + var baseLink = link([repo.id, 'tree', baseBranch]) |
| 2678 | + var headBranchLink = link([headRepoId, 'tree', headBranch]) |
| 2679 | + var backHref = encodeLink([repo.id, 'compare']) + req._u.search |
| 2680 | + var title = req._t(query.expand ? 'OpenPullRequest': 'ComparingChanges') |
| 2681 | + var pageTitle = title + ' · %{author}/%{repo}' |
2634 | 2682 | |
2635 | | - return renderRepoPage(req, repo, 'pulls', null, pageTitle, cat([ |
2636 | | - pull.once('<h3>' + title + '</h3>'), |
2637 | | - readNext(function (cb) { |
2638 | | - getRepo(headRepoId, function (err, headRepo) { |
2639 | | - if (err) return cb(err) |
2640 | | - getRepoFullName(about, headRepo.feed, headRepo.id, |
2641 | | - function (err, repoName, authorName) { |
2642 | | - if (err) return cb(err) |
2643 | | - cb(null, renderRepoInfo(Repo(headRepo), repoName, authorName)) |
2644 | | - } |
2645 | | - ) |
2646 | | - }) |
| 2683 | + return self.renderRepoPage(req, repo, 'pulls', null, pageTitle, cat([ |
| 2684 | + pull.once('<h3>' + title + '</h3>'), |
| 2685 | + readNext(function (cb) { |
| 2686 | + self.getRepo(headRepoId, function (err, headRepo) { |
| 2687 | + if (err) return cb(err) |
| 2688 | + self.getRepoFullName(headRepo.feed, headRepo.id, |
| 2689 | + function (err, repoName, authorName) { |
| 2690 | + if (err) return cb(err) |
| 2691 | + cb(null, renderRepoInfo(Repo(headRepo), repoName, authorName)) |
| 2692 | + } |
| 2693 | + ) |
2647 | 2694 | }) |
2648 | | - ])) |
| 2695 | + }) |
| 2696 | + ])) |
2649 | 2697 | |
2650 | | - function renderRepoInfo(headRepo, headRepoName, headRepoAuthorName) { |
2651 | | - var authorLink = link([headRepo.feed], headRepoAuthorName) |
2652 | | - var repoLink = link([headRepoId], headRepoName) |
2653 | | - return cat([ |
2654 | | - pull.once('<section>' + |
2655 | | - req._t('Base') + ': ' + baseLink + '<br/>' + |
2656 | | - req._t('Head') + ': ' + |
2657 | | - '<span class="bgslash">' + authorLink + ' / ' + repoLink + |
2658 | | - ' / ' + headBranchLink + '</span>' + |
2659 | | - '</section>' + |
2660 | | - (query.expand ? '<section><form method="post" action="">' + |
2661 | | - hiddenInputs({ |
2662 | | - action: 'new-pull', |
2663 | | - branch: baseBranch, |
2664 | | - head_repo: headRepoId, |
2665 | | - head_branch: headBranch |
2666 | | - }) + |
2667 | | - '<input class="wide-input" name="title"' + |
2668 | | - ' placeholder="' + req._t('Title') + '" size="77"/>' + |
2669 | | - renderPostForm(req, repo, req._t('Description'), 8) + |
2670 | | - '<button type="submit" class="btn open">' + |
2671 | | - req._t('Create') + '</button>' + |
2672 | | - '</form></section>' |
2673 | | - : '<section><form method="get" action="">' + |
2674 | | - hiddenInputs({ |
2675 | | - base: baseBranch, |
2676 | | - head: query.head |
2677 | | - }) + |
2678 | | - '<button class="btn open" type="submit" name="expand" value="1">' + |
2679 | | - '<i>⎇</i> ' + req._t('CreatePullRequest') + '</button> ' + |
2680 | | - '<a href="' + backHref + '">' + req._t('Back') + '</a>' + |
2681 | | - '</form></section>') + |
2682 | | - '<div id="commits"></div>' + |
2683 | | - '<div class="tab-links">' + |
2684 | | - '<a href="#" id="files-link">' + req._t('FilesChanged') + '</a> ' + |
2685 | | - '<a href="#commits" id="commits-link">' + |
2686 | | - req._t('Commits') + '</a>' + |
2687 | | - '</div>' + |
2688 | | - '<section id="files-tab">'), |
2689 | | - renderDiffStat(req, [repo, headRepo], [baseBranch, headBranch]), |
2690 | | - pull.once('</section>' + |
2691 | | - '<section id="commits-tab">'), |
2692 | | - renderCommitLog(req, repo, baseBranch, headRepo, headBranch), |
2693 | | - pull.once('</section>') |
2694 | | - ]) |
2695 | | - } |
2696 | | - } |
2697 | | - |
2698 | | - function renderCommitLog(req, baseRepo, baseBranch, headRepo, headBranch) { |
| 2698 | + function renderRepoInfo(headRepo, headRepoName, headRepoAuthorName) { |
| 2699 | + var authorLink = link([headRepo.feed], headRepoAuthorName) |
| 2700 | + var repoLink = link([headRepoId], headRepoName) |
2699 | 2701 | return cat([ |
2700 | | - pull.once('<table class="compare-commits">'), |
2701 | | - readNext(function (cb) { |
2702 | | - baseRepo.resolveRef(baseBranch, function (err, baseBranchRev) { |
2703 | | - if (err) return cb(err) |
2704 | | - var currentDay |
2705 | | - return cb(null, pull( |
2706 | | - headRepo.readLog(headBranch), |
2707 | | - pull.take(function (rev) { return rev != baseBranchRev }), |
2708 | | - pullReverse(), |
2709 | | - paramap(headRepo.getCommitParsed.bind(headRepo), 8), |
2710 | | - pull.map(function (commit) { |
2711 | | - var commitPath = [headRepo.id, 'commit', commit.id] |
2712 | | - var commitIdShort = '<tt>' + commit.id.substr(0, 8) + '</tt>' |
2713 | | - var day = Math.floor(commit.author.date / 86400000) |
2714 | | - var dateRow = day == currentDay ? '' : |
2715 | | - '<tr><th colspan=3 class="date-info">' + |
2716 | | - commit.author.date.toLocaleDateString(req._locale) + |
2717 | | - '</th><tr>' |
2718 | | - currentDay = day |
2719 | | - return dateRow + '<tr>' + |
2720 | | - '<td>' + escapeHTML(commit.author.name) + '</td>' + |
2721 | | - '<td>' + link(commitPath, commit.title) + '</td>' + |
2722 | | - '<td>' + link(commitPath, commitIdShort, true) + '</td>' + |
2723 | | - '</tr>' |
2724 | | - }) |
2725 | | - )) |
2726 | | - }) |
2727 | | - }), |
2728 | | - pull.once('</table>') |
| 2702 | + pull.once('<section>' + |
| 2703 | + req._t('Base') + ': ' + baseLink + '<br/>' + |
| 2704 | + req._t('Head') + ': ' + |
| 2705 | + '<span class="bgslash">' + authorLink + ' / ' + repoLink + |
| 2706 | + ' / ' + headBranchLink + '</span>' + |
| 2707 | + '</section>' + |
| 2708 | + (query.expand ? '<section><form method="post" action="">' + |
| 2709 | + hiddenInputs({ |
| 2710 | + action: 'new-pull', |
| 2711 | + branch: baseBranch, |
| 2712 | + head_repo: headRepoId, |
| 2713 | + head_branch: headBranch |
| 2714 | + }) + |
| 2715 | + '<input class="wide-input" name="title"' + |
| 2716 | + ' placeholder="' + req._t('Title') + '" size="77"/>' + |
| 2717 | + renderPostForm(req, repo, req._t('Description'), 8) + |
| 2718 | + '<button type="submit" class="btn open">' + |
| 2719 | + req._t('Create') + '</button>' + |
| 2720 | + '</form></section>' |
| 2721 | + : '<section><form method="get" action="">' + |
| 2722 | + hiddenInputs({ |
| 2723 | + base: baseBranch, |
| 2724 | + head: query.head |
| 2725 | + }) + |
| 2726 | + '<button class="btn open" type="submit" name="expand" value="1">' + |
| 2727 | + '<i>⎇</i> ' + req._t('CreatePullRequest') + '</button> ' + |
| 2728 | + '<a href="' + backHref + '">' + req._t('Back') + '</a>' + |
| 2729 | + '</form></section>') + |
| 2730 | + '<div id="commits"></div>' + |
| 2731 | + '<div class="tab-links">' + |
| 2732 | + '<a href="#" id="files-link">' + req._t('FilesChanged') + '</a> ' + |
| 2733 | + '<a href="#commits" id="commits-link">' + |
| 2734 | + req._t('Commits') + '</a>' + |
| 2735 | + '</div>' + |
| 2736 | + '<section id="files-tab">'), |
| 2737 | + renderDiffStat(req, [repo, headRepo], [baseBranch, headBranch]), |
| 2738 | + pull.once('</section>' + |
| 2739 | + '<section id="commits-tab">'), |
| 2740 | + self.renderCommitLog(req, repo, baseBranch, headRepo, headBranch), |
| 2741 | + pull.once('</section>') |
2729 | 2742 | ]) |
2730 | 2743 | } |
2731 | 2744 | } |
| 2745 | + |
| 2746 | +G.renderCommitLog = function (req, baseRepo, baseBranch, headRepo, headBranch) { |
| 2747 | + return cat([ |
| 2748 | + pull.once('<table class="compare-commits">'), |
| 2749 | + readNext(function (cb) { |
| 2750 | + baseRepo.resolveRef(baseBranch, function (err, baseBranchRev) { |
| 2751 | + if (err) return cb(err) |
| 2752 | + var currentDay |
| 2753 | + return cb(null, pull( |
| 2754 | + headRepo.readLog(headBranch), |
| 2755 | + pull.take(function (rev) { return rev != baseBranchRev }), |
| 2756 | + pullReverse(), |
| 2757 | + paramap(headRepo.getCommitParsed.bind(headRepo), 8), |
| 2758 | + pull.map(function (commit) { |
| 2759 | + var commitPath = [headRepo.id, 'commit', commit.id] |
| 2760 | + var commitIdShort = '<tt>' + commit.id.substr(0, 8) + '</tt>' |
| 2761 | + var day = Math.floor(commit.author.date / 86400000) |
| 2762 | + var dateRow = day == currentDay ? '' : |
| 2763 | + '<tr><th colspan=3 class="date-info">' + |
| 2764 | + commit.author.date.toLocaleDateString(req._locale) + |
| 2765 | + '</th><tr>' |
| 2766 | + currentDay = day |
| 2767 | + return dateRow + '<tr>' + |
| 2768 | + '<td>' + escapeHTML(commit.author.name) + '</td>' + |
| 2769 | + '<td>' + link(commitPath, commit.title) + '</td>' + |
| 2770 | + '<td>' + link(commitPath, commitIdShort, true) + '</td>' + |
| 2771 | + '</tr>' |
| 2772 | + }) |
| 2773 | + )) |
| 2774 | + }) |
| 2775 | + }), |
| 2776 | + pull.once('</table>') |
| 2777 | + ]) |
| 2778 | +} |