changeset 2440:1bc579bcd67a codereview

- pull request generates overview based on it's params - added page to show all pull-requests for a repository - db schema changes to support comments and inline comments for pull-requests
author Marcin Kuzminski <marcin@python-works.com>
date Sun, 10 Jun 2012 18:15:00 +0200
parents ad19dfcdb1cc
children bb8f97bee137
files rhodecode/config/routing.py rhodecode/controllers/changeset.py rhodecode/controllers/pullrequests.py rhodecode/lib/base.py rhodecode/model/changeset_status.py rhodecode/model/comment.py rhodecode/model/db.py rhodecode/model/pull_request.py rhodecode/model/scm.py rhodecode/templates/base/base.html rhodecode/templates/pullrequests/pullrequest_show.html rhodecode/templates/pullrequests/pullrequest_show_all.html
diffstat 12 files changed, 299 insertions(+), 44 deletions(-) [+]
line wrap: on
line diff
--- a/rhodecode/config/routing.py	Sun Jun 10 16:44:06 2012 +0200
+++ b/rhodecode/config/routing.py	Sun Jun 10 18:15:00 2012 +0200
@@ -451,6 +451,12 @@
                  action='show', conditions=dict(function=check_repo,
                                                 method=["GET"]))
 
+    rmap.connect('pullrequest_show_all',
+                 '/{repo_name:.*}/pull-request',
+                 controller='pullrequests',
+                 action='show_all', conditions=dict(function=check_repo,
+                                                method=["GET"]))
+
     rmap.connect('summary_home', '/{repo_name:.*}/summary',
                 controller='summary', conditions=dict(function=check_repo))
 
--- a/rhodecode/controllers/changeset.py	Sun Jun 10 16:44:06 2012 +0200
+++ b/rhodecode/controllers/changeset.py	Sun Jun 10 18:15:00 2012 +0200
@@ -295,7 +295,7 @@
                 )
 
         # count inline comments
-        for _, lines in c.inline_comments:
+        for __, lines in c.inline_comments:
             for comments in lines.values():
                 c.inline_cnt += len(comments)
 
--- a/rhodecode/controllers/pullrequests.py	Sun Jun 10 16:44:06 2012 +0200
+++ b/rhodecode/controllers/pullrequests.py	Sun Jun 10 18:15:00 2012 +0200
@@ -24,6 +24,9 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import logging
 import traceback
+import binascii
+
+from webob.exc import HTTPNotFound
 
 from pylons import request, response, session, tmpl_context as c, url
 from pylons.controllers.util import abort, redirect
@@ -32,9 +35,13 @@
 from rhodecode.lib.base import BaseRepoController, render
 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
 from rhodecode.lib import helpers as h
-from rhodecode.model.db import User, PullRequest
+from rhodecode.lib import diffs
+from rhodecode.model.db import User, PullRequest, Repository, ChangesetStatus
 from rhodecode.model.pull_request import PullRequestModel
 from rhodecode.model.meta import Session
+from rhodecode.model.repo import RepoModel
+from rhodecode.model.comment import ChangesetCommentsModel
+from rhodecode.model.changeset_status import ChangesetStatusModel
 
 log = logging.getLogger(__name__)
 
@@ -50,12 +57,12 @@
     def _get_repo_refs(self, repo):
         hist_l = []
 
-        branches_group = ([('branch:' + k, k) for k in repo.branches.keys()],
-                          _("Branches"))
-        bookmarks_group = ([('book:' + k, k) for k in repo.bookmarks.keys()],
-                           _("Bookmarks"))
-        tags_group = ([('tag:' + k, k) for k in repo.tags.keys()],
-                      _("Tags"))
+        branches_group = ([('branch:%s:%s' % (k, v), k) for
+                         k, v in repo.branches.iteritems()], _("Branches"))
+        bookmarks_group = ([('book:%s:%s' % (k, v), k) for
+                         k, v in repo.bookmarks.iteritems()], _("Bookmarks"))
+        tags_group = ([('tag:%s:%s' % (k, v), k) for 
+                         k, v in repo.tags.iteritems()], _("Tags"))
 
         hist_l.append(bookmarks_group)
         hist_l.append(branches_group)
@@ -63,6 +70,11 @@
 
         return hist_l
 
+    def show_all(self, repo_name):
+        c.pull_requests = PullRequestModel().get_all(repo_name)
+        c.repo_name = repo_name
+        return render('/pullrequests/pullrequest_show_all.html')
+
     def index(self):
         org_repo = c.rhodecode_db_repo
         c.org_refs = self._get_repo_refs(c.rhodecode_repo)
@@ -128,6 +140,118 @@
 
         return redirect(url('changelog_home', repo_name=repo_name))
 
+    def _get_changesets(self, org_repo, org_ref, other_repo, other_ref, tmp):
+        changesets = []
+        #case two independent repos
+        if org_repo != other_repo:
+            common, incoming, rheads = tmp
+
+            if not incoming:
+                revs = []
+            else:
+                revs = org_repo._repo.changelog.findmissing(common, rheads)
+
+            for cs in reversed(map(binascii.hexlify, revs)):
+                changesets.append(org_repo.get_changeset(cs))
+        else:
+            revs = ['ancestors(%s) and not ancestors(%s)' % (org_ref[1],
+                                                             other_ref[1])]
+            from mercurial import scmutil
+            out = scmutil.revrange(org_repo._repo, revs)
+            for cs in reversed(out):
+                changesets.append(org_repo.get_changeset(cs))
+
+        return changesets
+
+    def _get_discovery(self, org_repo, org_ref, other_repo, other_ref):
+        from mercurial import discovery
+        other = org_repo._repo
+        repo = other_repo._repo
+        tip = other[org_ref[1]]
+        log.debug('Doing discovery for %s@%s vs %s@%s' % (
+                        org_repo, org_ref, other_repo, other_ref)
+        )
+        log.debug('Filter heads are %s[%s]' % (tip, org_ref[1]))
+        tmp = discovery.findcommonincoming(
+                  repo=repo,  # other_repo we check for incoming
+                  remote=other,  # org_repo source for incoming
+                  heads=[tip.node()],
+                  force=False
+        )
+        return tmp
+
+    def _compare(self, pull_request):
+
+        org_repo = pull_request.org_repo
+        org_ref_type, org_ref_, org_ref = pull_request.org_ref.split(':')
+        other_repo = pull_request.other_repo
+        other_ref_type, other_ref, other_ref_ = pull_request.other_ref.split(':')
+
+        org_ref = (org_ref_type, org_ref)
+        other_ref = (other_ref_type, other_ref)
+
+        c.org_repo = org_repo
+        c.other_repo = other_repo
+
+        discovery_data = self._get_discovery(org_repo.scm_instance,
+                                           org_ref,
+                                           other_repo.scm_instance,
+                                           other_ref)
+        c.cs_ranges = self._get_changesets(org_repo.scm_instance,
+                                           org_ref,
+                                           other_repo.scm_instance,
+                                           other_ref,
+                                           discovery_data)
+
+        c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
+                                                   c.cs_ranges])
+        # defines that we need hidden inputs with changesets
+        c.as_form = request.GET.get('as_form', False)
+        if request.environ.get('HTTP_X_PARTIAL_XHR'):
+            return render('compare/compare_cs.html')
+
+        c.org_ref = org_ref[1]
+        c.other_ref = other_ref[1]
+        # diff needs to have swapped org with other to generate proper diff
+        _diff = diffs.differ(other_repo, other_ref, org_repo, org_ref,
+                             discovery_data)
+        diff_processor = diffs.DiffProcessor(_diff, format='gitdiff')
+        _parsed = diff_processor.prepare()
+
+        c.files = []
+        c.changes = {}
+
+        for f in _parsed:
+            fid = h.FID('', f['filename'])
+            c.files.append([fid, f['operation'], f['filename'], f['stats']])
+            diff = diff_processor.as_html(enable_comments=False, diff_lines=[f])
+            c.changes[fid] = [f['operation'], f['filename'], diff]
+
     def show(self, repo_name, pull_request_id):
+        repo_model = RepoModel()
+        c.users_array = repo_model.get_users_js()
+        c.users_groups_array = repo_model.get_users_groups_js()
         c.pull_request = PullRequest.get(pull_request_id)
+        ##TODO: need more generic solution
+        self._compare(c.pull_request)
+
+        # inline comments
+        c.inline_cnt = 0
+        c.inline_comments = ChangesetCommentsModel()\
+                            .get_inline_comments(c.rhodecode_db_repo.repo_id,
+                                                 pull_request=pull_request_id)
+        # count inline comments
+        for __, lines in c.inline_comments:
+            for comments in lines.values():
+                c.inline_cnt += len(comments)
+        # comments
+        c.comments = ChangesetCommentsModel()\
+                          .get_comments(c.rhodecode_db_repo.repo_id,
+                                        pull_request=pull_request_id)
+
+        # changeset(pull-request) statuse
+        c.current_changeset_status = ChangesetStatusModel()\
+                              .get_status(c.rhodecode_db_repo.repo_id,
+                                          pull_request=pull_request_id)
+        c.changeset_statuses = ChangesetStatus.STATUSES
         return render('/pullrequests/pullrequest_show.html')
--- a/rhodecode/lib/base.py	Sun Jun 10 16:44:06 2012 +0200
+++ b/rhodecode/lib/base.py	Sun Jun 10 18:15:00 2012 +0200
@@ -204,7 +204,7 @@
         super(BaseRepoController, self).__before__()
         if c.repo_name:
 
-            c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
+            dbr = c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
             c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
 
             if c.rhodecode_repo is None:
@@ -213,5 +213,7 @@
 
                 redirect(url('home'))
 
-            c.repository_followers = self.scm_model.get_followers(c.repo_name)
-            c.repository_forks = self.scm_model.get_forks(c.repo_name)
+            # some globals counter for menu
+            c.repository_followers = self.scm_model.get_followers(dbr)
+            c.repository_forks = self.scm_model.get_forks(dbr)
+            c.repository_pull_requests = self.scm_model.get_pull_requests(dbr)
\ No newline at end of file
--- a/rhodecode/model/changeset_status.py	Sun Jun 10 16:44:06 2012 +0200
+++ b/rhodecode/model/changeset_status.py	Sun Jun 10 18:15:00 2012 +0200
@@ -26,7 +26,7 @@
 import logging
 
 from rhodecode.model import BaseModel
-from rhodecode.model.db import ChangesetStatus
+from rhodecode.model.db import ChangesetStatus, PullRequest
 
 log = logging.getLogger(__name__)
 
@@ -36,23 +36,37 @@
     def __get_changeset_status(self, changeset_status):
         return self._get_instance(ChangesetStatus, changeset_status)
 
-    def get_status(self, repo, revision):
+    def __get_pull_request(self, pull_request):
+        return self._get_instance(PullRequest, pull_request)
+
+    def get_status(self, repo, revision=None, pull_request=None):
         """
-        Returns status of changeset for given revision and version 0
-        versioning makes a history of statuses, and version == 0 is always the
-        current one
+        Returns latest status of changeset for given revision or for given
+        pull request. Statuses are versioned inside a table itself and
+        version == 0 is always the current one
 
         :param repo:
         :type repo:
-        :param revision: 40char hash
+        :param revision: 40char hash or None
         :type revision: str
+        :param pull_request: pull_request reference
+        :type:
         """
         repo = self._get_repo(repo)
 
-        status = ChangesetStatus.query()\
+        q = ChangesetStatus.query()\
             .filter(ChangesetStatus.repo == repo)\
-            .filter(ChangesetStatus.revision == revision)\
-            .filter(ChangesetStatus.version == 0).scalar()
+            .filter(ChangesetStatus.version == 0)
+
+        if revision:
+            q = q.filter(ChangesetStatus.revision == revision)
+        elif pull_request:
+            pull_request = self.__get_pull_request(pull_request)
+            q = q.filter(ChangesetStatus.pull_request == pull_request)
+        else:
+            raise Exception('Please specify revision or pull_request')
+
+        status = q.scalar()
         status = status.status if status else status
         st = status or ChangesetStatus.DEFAULT
         return str(st)
--- a/rhodecode/model/comment.py	Sun Jun 10 16:44:06 2012 +0200
+++ b/rhodecode/model/comment.py	Sun Jun 10 18:15:00 2012 +0200
@@ -32,7 +32,8 @@
 from rhodecode.lib.utils2 import extract_mentioned_users, safe_unicode
 from rhodecode.lib import helpers as h
 from rhodecode.model import BaseModel
-from rhodecode.model.db import ChangesetComment, User, Repository, Notification
+from rhodecode.model.db import ChangesetComment, User, Repository, \
+    Notification, PullRequest
 from rhodecode.model.notification import NotificationModel
 
 log = logging.getLogger(__name__)
@@ -43,6 +44,9 @@
     def __get_changeset_comment(self, changeset_comment):
         return self._get_instance(ChangesetComment, changeset_comment)
 
+    def __get_pull_request(self, pull_request):
+        return self._get_instance(PullRequest, pull_request)
+
     def _extract_mentions(self, s):
         user_objects = []
         for username in extract_mentioned_users(s):
@@ -135,7 +139,7 @@
 
         return comment
 
-    def get_comments(self, repo_id, revision=None, pull_request_id=None):
+    def get_comments(self, repo_id, revision=None, pull_request=None):
         """
         Get's main comments based on revision or pull_request_id
 
@@ -143,22 +147,24 @@
         :type repo_id:
         :param revision:
         :type revision:
-        :param pull_request_id:
-        :type pull_request_id:
+        :param pull_request:
+        :type pull_request:
         """
+
         q = ChangesetComment.query()\
                 .filter(ChangesetComment.repo_id == repo_id)\
                 .filter(ChangesetComment.line_no == None)\
                 .filter(ChangesetComment.f_path == None)
         if revision:
             q = q.filter(ChangesetComment.revision == revision)
-        elif pull_request_id:
-            q = q.filter(ChangesetComment.pull_request_id == pull_request_id)
+        elif pull_request:
+            pull_request = self.__get_pull_request(pull_request)
+            q = q.filter(ChangesetComment.pull_request == pull_request)
         else:
-            raise Exception('Please specify revision or pull_request_id')
+            raise Exception('Please specify revision or pull_request')
         return q.all()
 
-    def get_inline_comments(self, repo_id, revision=None, pull_request_id=None):
+    def get_inline_comments(self, repo_id, revision=None, pull_request=None):
         q = self.sa.query(ChangesetComment)\
             .filter(ChangesetComment.repo_id == repo_id)\
             .filter(ChangesetComment.line_no != None)\
@@ -167,8 +173,9 @@
 
         if revision:
             q = q.filter(ChangesetComment.revision == revision)
-        elif pull_request_id:
-            q = q.filter(ChangesetComment.pull_request_id == pull_request_id)
+        elif pull_request:
+            pull_request = self.__get_pull_request(pull_request)
+            q = q.filter(ChangesetComment.pull_request == pull_request)
         else:
             raise Exception('Please specify revision or pull_request_id')
 
--- a/rhodecode/model/db.py	Sun Jun 10 16:44:06 2012 +0200
+++ b/rhodecode/model/db.py	Sun Jun 10 18:15:00 2012 +0200
@@ -1322,7 +1322,8 @@
     )
     comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
     repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
-    revision = Column('revision', String(40), nullable=False)
+    revision = Column('revision', String(40), nullable=True)
+    pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
     line_no = Column('line_no', Unicode(10), nullable=True)
     f_path = Column('f_path', Unicode(1000), nullable=True)
     user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
@@ -1332,6 +1333,7 @@
     author = relationship('User', lazy='joined')
     repo = relationship('Repository')
     status_change = relationship('ChangesetStatus', uselist=False)
+    pull_request = relationship('PullRequest', lazy='joined')
 
     @classmethod
     def get_users(cls, revision):
@@ -1397,6 +1399,8 @@
     pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
     title = Column('title', Unicode(256), nullable=True)
     description = Column('description', Unicode(10240), nullable=True)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
     _revisions = Column('revisions', UnicodeText(20500))  # 500 revisions max
     org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
     org_ref = Column('org_ref', Unicode(256), nullable=False)
@@ -1411,6 +1415,7 @@
     def revisions(self, val):
         self._revisions = ':'.join(val)
 
+    author = relationship('User', lazy='joined')
     reviewers = relationship('PullRequestReviewers')
     org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
     other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
--- a/rhodecode/model/pull_request.py	Sun Jun 10 16:44:06 2012 +0200
+++ b/rhodecode/model/pull_request.py	Sun Jun 10 18:15:00 2012 +0200
@@ -37,8 +37,13 @@
 
 class PullRequestModel(BaseModel):
 
+    def get_all(self, repo):
+        repo = self._get_repo(repo)
+        return PullRequest.query().filter(PullRequest.other_repo == repo).all()
+
     def create(self, created_by, org_repo, org_ref, other_repo,
                other_ref, revisions, reviewers, title, description=None):
+        created_by_user = self._get_user(created_by)
 
         new = PullRequest()
         new.org_repo = self._get_repo(org_repo)
@@ -48,7 +53,7 @@
         new.revisions = revisions
         new.title = title
         new.description = description
-
+        new.author = created_by_user
         self.sa.add(new)
 
         #members
@@ -59,7 +64,7 @@
 
         #notification to reviewers
         notif = NotificationModel()
-        created_by_user = self._get_user(created_by)
+
         subject = safe_unicode(
             h.link_to(
               _('%(user)s wants you to review pull request #%(pr_id)s') % \
--- a/rhodecode/model/scm.py	Sun Jun 10 16:44:06 2012 +0200
+++ b/rhodecode/model/scm.py	Sun Jun 10 18:15:00 2012 +0200
@@ -43,7 +43,7 @@
     action_logger, EmptyChangeset, REMOVED_REPO_PAT
 from rhodecode.model import BaseModel
 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
-    UserFollowing, UserLog, User, RepoGroup
+    UserFollowing, UserLog, User, RepoGroup, PullRequest
 
 log = logging.getLogger(__name__)
 
@@ -320,19 +320,21 @@
 
         return f is not None
 
-    def get_followers(self, repo_id):
-        if not isinstance(repo_id, int):
-            repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
+    def get_followers(self, repo):
+        repo = self._get_repo(repo)
 
         return self.sa.query(UserFollowing)\
-                .filter(UserFollowing.follows_repo_id == repo_id).count()
+                .filter(UserFollowing.follows_repository == repo).count()
 
-    def get_forks(self, repo_id):
-        if not isinstance(repo_id, int):
-            repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
+    def get_forks(self, repo):
+        repo = self._get_repo(repo)
+        return self.sa.query(Repository)\
+                .filter(Repository.fork == repo).count()
 
-        return self.sa.query(Repository)\
-                .filter(Repository.fork_id == repo_id).count()
+    def get_pull_requests(self, repo):
+        repo = self._get_repo(repo)
+        return self.sa.query(PullRequest)\
+                .filter(PullRequest.other_repo == repo).count()
 
     def mark_as_fork(self, repo, fork, user):
         repo = self.__get_repo(repo)
--- a/rhodecode/templates/base/base.html	Sun Jun 10 16:44:06 2012 +0200
+++ b/rhodecode/templates/base/base.html	Sun Jun 10 18:15:00 2012 +0200
@@ -247,6 +247,14 @@
                     <span class="short">${c.repository_forks}</span>
                     </a>
                 </li>
+                <li>
+                    <a class="menu_link" title="${_('Pull requests')}" href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}">
+                    <span class="icon_short">
+                        <img src="${h.url('/images/icons/arrow_join.png')}" alt="${_('Pull requests')}" />
+                    </span>
+                    <span class="short">${c.repository_pull_requests}</span>
+                    </a>
+                </li>
                 ${usermenu()}
 	        </ul>
             <script type="text/javascript">
--- a/rhodecode/templates/pullrequests/pullrequest_show.html	Sun Jun 10 16:44:06 2012 +0200
+++ b/rhodecode/templates/pullrequests/pullrequest_show.html	Sun Jun 10 18:15:00 2012 +0200
@@ -20,8 +20,59 @@
         ${self.breadcrumbs()}
     </div>
     
-    pull request ${c.pull_request} overview...
+    <h3>${_('Title')}: ${c.pull_request.title}</h3>
+    <div class="changeset-status-container" style="float:left;padding:0px 20px 20px 20px">
+        %if c.current_changeset_status:
+          <div title="${_('Changeset status')}" class="changeset-status-lbl">[${h.changeset_status_lbl(c.current_changeset_status)}]</div>
+          <div class="changeset-status-ico"><img src="${h.url('/images/icons/flag_status_%s.png' % c.current_changeset_status)}" /></div>
+        %endif
+    </div>    
+    <div style="padding:4px">
+      <div>${h.fmt_date(c.pull_request.created_on)}</div>
+    </div>
+    
+    ##DIFF
+    
+    <div class="table">
+        <div id="body" class="diffblock">
+            <div style="white-space:pre-wrap;padding:5px">${h.literal(c.pull_request.description)}</div>
+        </div>
+        <div id="changeset_compare_view_content">
+            ##CS
+            <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Incoming changesets')}</div>
+            <%include file="/compare/compare_cs.html" />
 
+            ## FILES
+            <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Files affected')}</div>
+            <div class="cs_files">
+              %for fid, change, f, stat in c.files:
+                  <div class="cs_${change}">
+                    <div class="node">${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid))}</div>
+                    <div class="changes">${h.fancy_file_stats(stat)}</div>
+                  </div>
+              %endfor
+            </div>
+        </div>
+    </div>
+    <script>
+    var _USERS_AC_DATA = ${c.users_array|n};
+    var _GROUPS_AC_DATA = ${c.users_groups_array|n};
+    </script>
+
+    ## diff block
+    <%namespace name="diff_block" file="/changeset/diff_block.html"/>
+    %for fid, change, f, stat in c.files:
+      ${diff_block.diff_block_simple([c.changes[fid]])}
+    %endfor
+
+    ## template for inline comment form
+    <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
+    ##${comment.comment_inline_form(c.changeset)}
+
+    ## render comments main comments form and it status
+    ##${comment.comments(h.url('pull_request_comment', repo_name=c.repo_name, pull_request_id=c.pull_request.pull_request_id),
+    ##                   c.current_changeset_status)}
+    
 </div>
 
 <script type="text/javascript">
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/pullrequests/pullrequest_show_all.html	Sun Jun 10 18:15:00 2012 +0200
@@ -0,0 +1,31 @@
+<%inherit file="/base/base.html"/>
+
+<%def name="title()">
+    ${c.repo_name} ${_('All pull requests')}
+</%def>
+
+<%def name="breadcrumbs_links()">
+    ${h.link_to(u'Home',h.url('/'))}
+    &raquo;
+    ${h.link_to(c.repo_name,h.url('changelog_home',repo_name=c.repo_name))}
+    &raquo;
+    ${_('All pull requests')}
+</%def>
+
+<%def name="main()">
+
+<div class="box">
+    <!-- box / title -->
+    <div class="title">
+        ${self.breadcrumbs()}
+    </div>
+    
+    %for pr in c.pull_requests:
+        <a href="${h.url('pullrequest_show',repo_name=c.repo_name,pull_request_id=pr.pull_request_id)}">#${pr.pull_request_id}</a>
+    %endfor
+    
+</div>
+
+<script type="text/javascript"></script>
+
+</%def>