changeset 2434:f29469677319 codereview

Added basic models for saving open pull requests - added pull-request models - added pull-requests notifications into inbox
author Marcin Kuzminski <marcin@python-works.com>
date Sun, 10 Jun 2012 02:08:10 +0200
parents 74f2910f7ad9
children 4ab0e4d478b6
files rhodecode/config/routing.py rhodecode/controllers/compare.py rhodecode/controllers/pullrequests.py rhodecode/model/comment.py rhodecode/model/db.py rhodecode/model/notification.py rhodecode/model/pull_request.py rhodecode/templates/compare/compare_cs.html rhodecode/templates/compare/compare_diff.html rhodecode/templates/pullrequests/pullrequest.html rhodecode/templates/pullrequests/pullrequest_show.html rhodecode/tests/functional/test_compare.py rhodecode/tests/functional/test_pullrequests.py rhodecode/tests/test_models.py
diffstat 14 files changed, 341 insertions(+), 65 deletions(-) [+]
line wrap: on
line diff
--- a/rhodecode/config/routing.py	Sun Jun 10 00:08:29 2012 +0200
+++ b/rhodecode/config/routing.py	Sun Jun 10 02:08:10 2012 +0200
@@ -436,9 +436,20 @@
                                    other_ref_type='(branch|book|tag)'))
 
     rmap.connect('pullrequest_home',
-                 '/{repo_name:.*}/pull-request/new',
-                 controller='pullrequests', action='index',
-                 conditions=dict(function=check_repo))
+                 '/{repo_name:.*}/pull-request/new', controller='pullrequests',
+                 action='index', conditions=dict(function=check_repo,
+                                                 method=["GET"]))
+
+    rmap.connect('pullrequest',
+                 '/{repo_name:.*}/pull-request/new', controller='pullrequests',
+                 action='create', conditions=dict(function=check_repo,
+                                                  method=["POST"]))
+
+    rmap.connect('pullrequest_show',
+                 '/{repo_name:.*}/pull-request/{pull_request_id}',
+                 controller='pullrequests',
+                 action='show', conditions=dict(function=check_repo,
+                                                method=["GET"]))
 
     rmap.connect('summary_home', '/{repo_name:.*}/summary',
                 controller='summary', conditions=dict(function=check_repo))
--- a/rhodecode/controllers/compare.py	Sun Jun 10 00:08:29 2012 +0200
+++ b/rhodecode/controllers/compare.py	Sun Jun 10 02:08:10 2012 +0200
@@ -120,6 +120,8 @@
 
         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')
 
--- a/rhodecode/controllers/pullrequests.py	Sun Jun 10 00:08:29 2012 +0200
+++ b/rhodecode/controllers/pullrequests.py	Sun Jun 10 02:08:10 2012 +0200
@@ -31,7 +31,10 @@
 
 from rhodecode.lib.base import BaseRepoController, render
 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
-from rhodecode.model.db import User
+from rhodecode.lib import helpers as h
+from rhodecode.model.db import User, PullRequest
+from rhodecode.model.pull_request import PullRequestModel
+from rhodecode.model.meta import Session
 
 log = logging.getLogger(__name__)
 
@@ -71,20 +74,60 @@
 
         c.other_refs = c.org_refs
         c.other_repos.extend(c.org_repos)
-
+        c.default_pull_request = org_repo.repo_name
         #gather forks and add to this list
         for fork in org_repo.forks:
             c.other_repos.append((fork.repo_name, '%s/%s' % (
                                     fork.user.username, fork.repo_name))
                                  )
         #add parents of this fork also
-        c.other_repos.append((org_repo.parent.repo_name, '%s/%s' % (
-                                    org_repo.parent.user.username, 
-                                    org_repo.parent.repo_name))
-                                 )
+        if org_repo.parent:
+            c.default_pull_request = org_repo.parent.repo_name
+            c.other_repos.append((org_repo.parent.repo_name, '%s/%s' % (
+                                        org_repo.parent.user.username,
+                                        org_repo.parent.repo_name))
+                                     )
 
         #TODO: maybe the owner should be default ?
         c.review_members = []
-        c.available_members = [(x.user_id, x.username) for x in
-                        User.query().filter(User.username != 'default').all()]
+        c.available_members = []
+        for u in User.query().filter(User.username != 'default').all():
+            uname = u.username
+            if org_repo.user == u:
+                uname = _('%s (owner)' % u.username)
+                # auto add owner to pull-request recipients
+                c.review_members.append([u.user_id, uname])
+            c.available_members.append([u.user_id, uname])
         return render('/pullrequests/pullrequest.html')
+
+    def create(self, repo_name):
+        req_p = request.POST
+        org_repo = req_p['org_repo']
+        org_ref = req_p['org_ref']
+        other_repo = req_p['other_repo']
+        other_ref = req_p['other_ref']
+        revisions = req_p.getall('revisions')
+        reviewers = req_p.getall('review_members')
+        #TODO: wrap this into a FORM !!!
+
+        title = req_p['pullrequest_title']
+        description = req_p['pullrequest_desc']
+
+        try:
+            model = PullRequestModel()
+            model.create(self.rhodecode_user.user_id, org_repo,
+                         org_ref, other_repo, other_ref, revisions,
+                         reviewers, title, description)
+            Session.commit()
+            h.flash(_('Pull request send'), category='success')
+        except Exception:
+            raise
+            h.flash(_('Error occured during sending pull request'),
+                    category='error')
+            log.error(traceback.format_exc())
+
+        return redirect(url('changelog_home', repo_name=repo_name))
+
+    def show(self, repo_name, pull_request_id):
+        c.pull_request = PullRequest.get(pull_request_id)
+        return render('/pullrequests/pullrequest_show.html')
--- a/rhodecode/model/comment.py	Sun Jun 10 00:08:29 2012 +0200
+++ b/rhodecode/model/comment.py	Sun Jun 10 02:08:10 2012 +0200
@@ -118,7 +118,8 @@
                 NotificationModel().create(
                     created_by=user_id, subject=subj, body=body,
                     recipients=mention_recipients,
-                    type_=Notification.TYPE_CHANGESET_COMMENT
+                    type_=Notification.TYPE_CHANGESET_COMMENT,
+                    email_kwargs={'status_change': status_change}
                 )
 
             return comment
--- a/rhodecode/model/db.py	Sun Jun 10 00:08:29 2012 +0200
+++ b/rhodecode/model/db.py	Sun Jun 10 02:08:10 2012 +0200
@@ -1371,9 +1371,12 @@
     changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
     modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
     version = Column('version', Integer(), nullable=False, default=0)
+    pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
+
     author = relationship('User', lazy='joined')
     repo = relationship('Repository')
     comment = relationship('ChangesetComment', lazy='joined')
+    pull_request = relationship('PullRequest', lazy='joined')
 
     @classmethod
     def get_status_lbl(cls, value):
@@ -1384,6 +1387,59 @@
         return ChangesetStatus.get_status_lbl(self.status)
 
 
+class PullRequest(Base, BaseModel):
+    __tablename__ = 'pull_requests'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8'},
+    )
+
+    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)
+    _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)
+    other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+    other_ref = Column('other_ref', Unicode(256), nullable=False)
+
+    @hybrid_property
+    def revisions(self):
+        return self._revisions.split(':')
+
+    @revisions.setter
+    def revisions(self, val):
+        self._revisions = ':'.join(val)
+
+    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')
+
+    def __json__(self):
+        return dict(
+          revisions=self.revisions
+        )
+
+
+class PullRequestReviewers(Base, BaseModel):
+    __tablename__ = 'pull_request_reviewers'
+    __table_args__ = (
+        {'extend_existing': True, 'mysql_engine': 'InnoDB',
+         'mysql_charset': 'utf8'},
+    )
+
+    def __init__(self, user=None, pull_request=None):
+        self.user = user
+        self.pull_request = pull_request
+
+    pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
+    pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
+
+    user = relationship('User')
+    pull_request = relationship('PullRequest')
+
+
 class Notification(Base, BaseModel):
     __tablename__ = 'notifications'
     __table_args__ = (
--- a/rhodecode/model/notification.py	Sun Jun 10 00:08:29 2012 +0200
+++ b/rhodecode/model/notification.py	Sun Jun 10 02:08:10 2012 +0200
@@ -226,6 +226,7 @@
     TYPE_CHANGESET_COMMENT = Notification.TYPE_CHANGESET_COMMENT
     TYPE_PASSWORD_RESET = 'passoword_link'
     TYPE_REGISTRATION = Notification.TYPE_REGISTRATION
+    TYPE_PULL_REQUEST = Notification.TYPE_PULL_REQUEST
     TYPE_DEFAULT = 'default'
 
     def __init__(self):
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/model/pull_request.py	Sun Jun 10 02:08:10 2012 +0200
@@ -0,0 +1,79 @@
+# -*- coding: utf-8 -*-
+"""
+    rhodecode.model.pull_reuquest
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    pull request model for RhodeCode
+
+    :created_on: Jun 6, 2012
+    :author: marcink
+    :copyright: (C) 2012-2012 Marcin Kuzminski <marcin@python-works.com>
+    :license: GPLv3, see COPYING for more details.
+"""
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import logging
+from pylons.i18n.translation import _
+
+from rhodecode.lib import helpers as h
+from rhodecode.model import BaseModel
+from rhodecode.model.db import PullRequest, PullRequestReviewers, Notification
+from rhodecode.model.notification import NotificationModel
+from rhodecode.lib.utils2 import safe_unicode
+
+log = logging.getLogger(__name__)
+
+
+class PullRequestModel(BaseModel):
+
+    def create(self, created_by, org_repo, org_ref, other_repo,
+               other_ref, revisions, reviewers, title, description=None):
+
+        new = PullRequest()
+        new.org_repo = self._get_repo(org_repo)
+        new.org_ref = org_ref
+        new.other_repo = self._get_repo(other_repo)
+        new.other_ref = other_ref
+        new.revisions = revisions
+        new.title = title
+        new.description = description
+
+        self.sa.add(new)
+
+        #members
+        for member in reviewers:
+            _usr = self._get_user(member)
+            reviewer = PullRequestReviewers(_usr, new)
+            self.sa.add(reviewer)
+
+        #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') % \
+                {'user': created_by_user.username,
+                 'pr_id': new.pull_request_id},
+              h.url('pullrequest_show', repo_name=other_repo,
+                    pull_request_id=new.pull_request_id,
+                    qualified=True,
+              )
+            )
+        )
+        body = description
+        notif.create(created_by=created_by, subject=subject, body=body,
+                     recipients=reviewers,
+                     type_=Notification.TYPE_PULL_REQUEST,)
+
+        return new
--- a/rhodecode/templates/compare/compare_cs.html	Sun Jun 10 00:08:29 2012 +0200
+++ b/rhodecode/templates/compare/compare_cs.html	Sun Jun 10 02:08:10 2012 +0200
@@ -11,8 +11,12 @@
           %if cs.raw_id in c.statuses:
             <div title="${c.statuses[cs.raw_id][1]}" class="changeset-status-ico"><img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses[cs.raw_id][0])}" /></div>
           %endif
-        </td>                
-        <td>${h.link_to('r%s:%s' % (cs.revision,h.short_id(cs.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</td>
+        </td>
+        <td>${h.link_to('r%s:%s' % (cs.revision,h.short_id(cs.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}
+        %if c.as_form:
+          ${h.hidden('revisions',cs.raw_id)}
+        %endif
+        </td>
         <td><div class="author">${h.person(cs.author)}</div></td>
         <td><span class="tooltip" title="${h.age(cs.date)}">${cs.date}</span></td>
         <td><div class="message">${h.urlify_commit(h.wrap_paragraphs(cs.message),c.repo_name)}</div></td>
@@ -20,4 +24,4 @@
     %endfor
   %endif
   </table>
-</div>
\ No newline at end of file
+</div>
--- a/rhodecode/templates/compare/compare_diff.html	Sun Jun 10 00:08:29 2012 +0200
+++ b/rhodecode/templates/compare/compare_diff.html	Sun Jun 10 02:08:10 2012 +0200
@@ -34,13 +34,13 @@
         </div>
         <div id="changeset_compare_view_content">
             ##CS
-            <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Changesets')}</div>
+            <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Outgoing changesets')}</div>
             <%include file="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:              
+              %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>
@@ -49,13 +49,13 @@
             </div>
         </div>
     </div>
-    
+
     ## 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
-    
+
      <script type="text/javascript">
 
       YUE.onDOMReady(function(){
--- a/rhodecode/templates/pullrequests/pullrequest.html	Sun Jun 10 00:08:29 2012 +0200
+++ b/rhodecode/templates/pullrequests/pullrequest.html	Sun Jun 10 02:08:10 2012 +0200
@@ -1,7 +1,7 @@
 <%inherit file="/base/base.html"/>
 
 <%def name="title()">
-    ${c.repo_name} ${_('Pull request')}
+    ${c.repo_name} ${_('New pull request')}
 </%def>
 
 <%def name="breadcrumbs_links()">
@@ -9,7 +9,7 @@
     &raquo;
     ${h.link_to(c.repo_name,h.url('changelog_home',repo_name=c.repo_name))}
     &raquo;
-    ${_('Pull request')}
+    ${_('New pull request')}
 </%def>
 
 <%def name="main()">
@@ -19,8 +19,16 @@
     <div class="title">
         ${self.breadcrumbs()}
     </div>
-    ${h.form(url('#'),method='put', id='pull_request_form')}
-    <div style="float:left;padding:30px">
+    ${h.form(url('pullrequest', repo_name=c.repo_name), method='post', id='pull_request_form')}
+    <div style="float:left;padding:0px 30px 30px 30px">
+       <div style="padding:0px 5px 5px 5px">
+         <span>
+           <a id="refresh" href="#">
+             <img class="icon" title="${_('Refresh')}" alt="${_('Refresh')}" src="${h.url('/images/icons/arrow_refresh.png')}"/>
+             ${_('refresh overview')}
+           </a>
+         </span>
+       </div>
         ##ORG
         <div style="float:left">
             <div class="fork_user">
@@ -37,7 +45,7 @@
           <div style="float:left;font-size:24px;padding:0px 20px">
           <img height=32 width=32 src="${h.url('/images/arrow_right_64.png')}"/>
           </div>
-        
+
         ##OTHER, most Probably the PARENT OF THIS FORK
         <div style="float:left">
             <div class="fork_user">
@@ -45,26 +53,18 @@
                     <img alt="gravatar" src="${h.gravatar_url(c.rhodecode_db_repo.user.email,24)}"/>
                 </div>
                 <span style="font-size: 20px">
-                ${h.select('other_repo','',c.other_repos,class_='refs')}:${h.select('other_ref','',c.other_refs,class_='refs')}
+                ${h.select('other_repo',c.default_pull_request ,c.other_repos,class_='refs')}:${h.select('other_ref','',c.other_refs,class_='refs')}
                 </span>
                  <div style="padding:5px 3px 3px 42px;">${c.rhodecode_db_repo.description}</div>
             </div>
             <div style="clear:both;padding-top: 10px"></div>
         </div>
-       <div style="float:left;padding:5px 5px 5px 15px">
-         <span>
-           <a id="refresh" href="#">
-             <img class="icon" title="${_('Refresh')}" alt="${_('Refresh')}" src="${h.url('/images/icons/arrow_refresh.png')}"/>
-             ${_('refresh overview')}
-           </a>
-         </span>       
-       </div>
-       <div style="clear:both;padding-top: 10px"></div>       
-       <div style="float:left" id="pull_request_overview">
-       </div>
+       <div style="clear:both;padding-top: 10px"></div>
+       ## overview pulled by ajax
+       <div style="float:left" id="pull_request_overview"></div>
        <div style="float:left;clear:both;padding:10px 10px 10px 0px;display:none">
             <a id="pull_request_overview_url" href="#">${_('Detailed compare view')}</a>
-       </div>               
+       </div>
      </div>
     <div style="float:left; border-left:1px dashed #eee">
         <h4>${_('Pull request reviewers')}</h4>
@@ -75,7 +75,7 @@
                       <td>
                           <div>
                               <div style="float:left">
-                                  <div class="text" style="padding: 0px 0px 6px;">${_('Choosen reviewers')}</div>
+                                  <div class="text" style="padding: 0px 0px 6px;">${_('Chosen reviewers')}</div>
                                   ${h.select('review_members',[x[0] for x in c.review_members],c.review_members,multiple=True,size=8,style="min-width:210px")}
                                  <div  id="remove_all_elements" style="cursor:pointer;text-align:center">
                                      ${_('Remove all elements')}
@@ -102,11 +102,11 @@
                           </div>
                       </td>
                   </tr>
-          </table>        
+          </table>
         </div>
-    </div> 
+    </div>
     <h3>${_('Create new pull request')}</h3>
-    
+
     <div class="form">
         <!-- fields -->
 
@@ -136,23 +136,24 @@
            </div>
         </div>
     </div>
-    ${h.end_form()}     
-     
+    ${h.end_form()}
+
 </div>
 
 <script type="text/javascript">
   MultiSelectWidget('review_members','available_members','pull_request_form');
-  
+
   var loadPreview = function(){
 	  YUD.setStyle(YUD.get('pull_request_overview_url').parentElement,'display','none');
-      var url = "${h.url('compare_url', 
+      var url = "${h.url('compare_url',
           repo_name='org_repo',
           org_ref_type='branch', org_ref='org_ref',
           other_ref_type='branch', other_ref='other_ref',
-          repo='other_repo')}";
-  
+          repo='other_repo',
+          as_form=True)}";
+
       var select_refs = YUQ('#pull_request_form select.refs')
-    
+
       for(var i=0;i<select_refs.length;i++){
         var select_ref = select_refs[i];
         var select_ref_data = select_ref.value.split(':');
@@ -162,31 +163,30 @@
           key = select_ref.name+"_type";
           val = select_ref_data[0];
           url = url.replace(key,val);
-    
+
           key = select_ref.name;
           val = select_ref_data[1];
           url = url.replace(key,val);
-    
+
         }else{
           key = select_ref.name;
           val = select_ref.value;
           url = url.replace(key,val);
         }
       }
-      
+
       ypjax(url,'pull_request_overview', function(data){
     	  YUD.get('pull_request_overview_url').href = url;
     	  YUD.setStyle(YUD.get('pull_request_overview_url').parentElement,'display','');
-      })	  
+      })
   }
   YUE.on('refresh','click',function(e){
      loadPreview()
   })
-  
-  //lazy load after 0.5
-  
-  setTimeout(loadPreview,500)
-  
+
+  //lazy load overview after 0.5s
+  setTimeout(loadPreview, 500)
+
 </script>
 
 </%def>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/pullrequests/pullrequest_show.html	Sun Jun 10 02:08:10 2012 +0200
@@ -0,0 +1,32 @@
+<%inherit file="/base/base.html"/>
+
+<%def name="title()">
+    ${c.repo_name} ${_('Pull request #%s') % c.pull_request.pull_request_id}
+</%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;
+    ${_('Pull request #%s') % c.pull_request.pull_request_id}
+</%def>
+
+<%def name="main()">
+
+<div class="box">
+    <!-- box / title -->
+    <div class="title">
+        ${self.breadcrumbs()}
+    </div>
+    
+    pull request ${c.pull_request} overview...
+
+</div>
+
+<script type="text/javascript">
+
+
+</script>
+
+</%def>
--- a/rhodecode/tests/functional/test_compare.py	Sun Jun 10 00:08:29 2012 +0200
+++ b/rhodecode/tests/functional/test_compare.py	Sun Jun 10 02:08:10 2012 +0200
@@ -1,7 +1,52 @@
 from rhodecode.tests import *
 
+
 class TestCompareController(TestController):
 
-    def test_index(self):
-        response = self.app.get(url(controller='compare', action='index'))
-        # Test response...
+    def test_index_tag(self):
+        self.log_user()
+        tag1='0.1.3'
+        tag2='0.1.2'
+        response = self.app.get(url(controller='compare', action='index',
+                                    repo_name=HG_REPO,
+                                    org_ref_type="tag",
+                                    org_ref=tag1,
+                                    other_ref_type="tag",
+                                    other_ref=tag2,
+                                    ))
+        response.mustcontain('%s@%s -> %s@%s' % (HG_REPO, tag1, HG_REPO, tag2))
+        ## outgoing changesets between tags
+        response.mustcontain('''<a href="/%s/changeset/17544fbfcd33ffb439e2b728b5d526b1ef30bfcf">r120:17544fbfcd33</a>''' % HG_REPO)
+        response.mustcontain('''<a href="/%s/changeset/36e0fc9d2808c5022a24f49d6658330383ed8666">r119:36e0fc9d2808</a>''' % HG_REPO)
+        response.mustcontain('''<a href="/%s/changeset/bb1a3ab98cc45cb934a77dcabf87a5a598b59e97">r118:bb1a3ab98cc4</a>''' % HG_REPO)
+        response.mustcontain('''<a href="/%s/changeset/41fda979f02fda216374bf8edac4e83f69e7581c">r117:41fda979f02f</a>''' % HG_REPO)
+        response.mustcontain('''<a href="/%s/changeset/9749bfbfc0d2eba208d7947de266303b67c87cda">r116:9749bfbfc0d2</a>''' % HG_REPO)
+        response.mustcontain('''<a href="/%s/changeset/70d4cef8a37657ee4cf5aabb3bd9f68879769816">r115:70d4cef8a376</a>''' % HG_REPO)
+        response.mustcontain('''<a href="/%s/changeset/c5ddebc06eaaba3010c2d66ea6ec9d074eb0f678">r112:c5ddebc06eaa</a>''' % HG_REPO)
+        
+        ## files diff
+        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--1c5cf9e91c12">docs/api/utils/index.rst</a></div>''' % (HG_REPO, tag1,  tag2))
+        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--e3305437df55">test_and_report.sh</a></div>''' % (HG_REPO, tag1,  tag2))
+        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--c8e92ef85cd1">.hgignore</a></div>''' % (HG_REPO, tag1,  tag2))
+        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--6e08b694d687">.hgtags</a></div>''' % (HG_REPO, tag1,  tag2))
+        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--2c14b00f3393">docs/api/index.rst</a></div>''' % (HG_REPO, tag1,  tag2))
+        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--430ccbc82bdf">vcs/__init__.py</a></div>''' % (HG_REPO, tag1,  tag2))
+        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--9c390eb52cd6">vcs/backends/hg.py</a></div>''' % (HG_REPO, tag1,  tag2))
+        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--ebb592c595c0">vcs/utils/__init__.py</a></div>''' % (HG_REPO, tag1,  tag2))
+        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--7abc741b5052">vcs/utils/annotate.py</a></div>''' % (HG_REPO, tag1,  tag2))
+        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--2ef0ef106c56">vcs/utils/diffs.py</a></div>''' % (HG_REPO, tag1,  tag2))
+        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--3150cb87d4b7">vcs/utils/lazy.py</a></div>''' % (HG_REPO, tag1,  tag2))
+
+    def test_index_branch(self):
+        self.log_user()
+        response = self.app.get(url(controller='compare', action='index',
+                                    repo_name=HG_REPO,
+                                    org_ref_type="branch",
+                                    org_ref='default',
+                                    other_ref_type="branch",
+                                    other_ref='default',
+                                    ))
+
+        response.mustcontain('%s@default -> %s@default' % (HG_REPO, HG_REPO))
+        # branch are equal
+        response.mustcontain('<tr><td>No changesets</td></tr>')
--- a/rhodecode/tests/functional/test_pullrequests.py	Sun Jun 10 00:08:29 2012 +0200
+++ b/rhodecode/tests/functional/test_pullrequests.py	Sun Jun 10 02:08:10 2012 +0200
@@ -1,7 +1,9 @@
 from rhodecode.tests import *
 
+
 class TestPullrequestsController(TestController):
 
     def test_index(self):
-        response = self.app.get(url(controller='pullrequests', action='index'))
-        # Test response...
+        self.log_user()
+        response = self.app.get(url(controller='pullrequests', action='index',
+                                    repo_name=HG_REPO))
--- a/rhodecode/tests/test_models.py	Sun Jun 10 00:08:29 2012 +0200
+++ b/rhodecode/tests/test_models.py	Sun Jun 10 02:08:10 2012 +0200
@@ -181,7 +181,7 @@
         super(TestUser, self).__init__(methodName=methodName)
 
     def test_create_and_remove(self):
-        usr = UserModel().create_or_update(username=u'test_user', 
+        usr = UserModel().create_or_update(username=u'test_user',
                                            password=u'qweqwe',
                                      email=u'u232@rhodecode.org',
                                      name=u'u1', lastname=u'u1')
@@ -203,7 +203,7 @@
         self.assertEqual(UsersGroupMember.query().all(), [])
 
     def test_additonal_email_as_main(self):
-        usr = UserModel().create_or_update(username=u'test_user', 
+        usr = UserModel().create_or_update(username=u'test_user',
                                            password=u'qweqwe',
                                      email=u'main_email@rhodecode.org',
                                      name=u'u1', lastname=u'u1')
@@ -221,7 +221,7 @@
         Session.commit()
 
     def test_extra_email_map(self):
-        usr = UserModel().create_or_update(username=u'test_user', 
+        usr = UserModel().create_or_update(username=u'test_user',
                                            password=u'qweqwe',
                                      email=u'main_email@rhodecode.org',
                                      name=u'u1', lastname=u'u1')