Changeset - fd0a822481ec
[Not reviewed]
codereview
0 9 0
Marcin Kuzminski - 13 years ago 2012-06-11 00:58:24
marcin@python-works.com
- added commenting to pull requests
- it's now possible to change pull request status via changeset status option
- status flags with associated pull requests are links to pull request they been
changed in
9 files changed with 197 insertions and 71 deletions:
0 comments (0 inline, 0 general)
rhodecode/config/routing.py
Show inline comments
 
@@ -364,189 +364,195 @@ def make_map(config):
 
    rmap.connect('public_journal_atom_old',
 
                 '%s/public_journal_atom' % ADMIN_PREFIX, controller='journal',
 
                 action="public_journal_atom")
 

	
 
    rmap.connect('toggle_following', '%s/toggle_following' % ADMIN_PREFIX,
 
                 controller='journal', action='toggle_following',
 
                 conditions=dict(method=["POST"]))
 

	
 
    #SEARCH
 
    rmap.connect('search', '%s/search' % ADMIN_PREFIX, controller='search',)
 
    rmap.connect('search_repo', '%s/search/{search_repo:.*}' % ADMIN_PREFIX,
 
                  controller='search')
 

	
 
    #LOGIN/LOGOUT/REGISTER/SIGN IN
 
    rmap.connect('login_home', '%s/login' % ADMIN_PREFIX, controller='login')
 
    rmap.connect('logout_home', '%s/logout' % ADMIN_PREFIX, controller='login',
 
                 action='logout')
 

	
 
    rmap.connect('register', '%s/register' % ADMIN_PREFIX, controller='login',
 
                 action='register')
 

	
 
    rmap.connect('reset_password', '%s/password_reset' % ADMIN_PREFIX,
 
                 controller='login', action='password_reset')
 

	
 
    rmap.connect('reset_password_confirmation',
 
                 '%s/password_reset_confirmation' % ADMIN_PREFIX,
 
                 controller='login', action='password_reset_confirmation')
 

	
 
    #FEEDS
 
    rmap.connect('rss_feed_home', '/{repo_name:.*}/feed/rss',
 
                controller='feed', action='rss',
 
                conditions=dict(function=check_repo))
 

	
 
    rmap.connect('atom_feed_home', '/{repo_name:.*}/feed/atom',
 
                controller='feed', action='atom',
 
                conditions=dict(function=check_repo))
 

	
 
    #==========================================================================
 
    # REPOSITORY ROUTES
 
    #==========================================================================
 
    rmap.connect('summary_home', '/{repo_name:.*}',
 
                controller='summary',
 
                conditions=dict(function=check_repo))
 

	
 
    rmap.connect('repos_group_home', '/{group_name:.*}',
 
                controller='admin/repos_groups', action="show_by_name",
 
                conditions=dict(function=check_group))
 

	
 
    rmap.connect('changeset_home', '/{repo_name:.*}/changeset/{revision}',
 
                controller='changeset', revision='tip',
 
                conditions=dict(function=check_repo))
 

	
 
    rmap.connect('changeset_comment',
 
                 '/{repo_name:.*}/changeset/{revision}/comment',
 
                controller='changeset', revision='tip', action='comment',
 
                conditions=dict(function=check_repo))
 

	
 
    rmap.connect('changeset_comment_delete',
 
                 '/{repo_name:.*}/changeset/comment/{comment_id}/delete',
 
                controller='changeset', action='delete_comment',
 
                conditions=dict(function=check_repo, method=["DELETE"]))
 

	
 
    rmap.connect('raw_changeset_home',
 
                 '/{repo_name:.*}/raw-changeset/{revision}',
 
                 controller='changeset', action='raw_changeset',
 
                 revision='tip', conditions=dict(function=check_repo))
 

	
 
    rmap.connect('compare_url',
 
                 '/{repo_name:.*}/compare/{org_ref_type}@{org_ref}...{other_ref_type}@{other_ref}',
 
                 controller='compare', action='index',
 
                 conditions=dict(function=check_repo),
 
                 requirements=dict(org_ref_type='(branch|book|tag)',
 
                                   other_ref_type='(branch|book|tag)'))
 

	
 
    rmap.connect('pullrequest_home',
 
                 '/{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('pullrequest_show_all',
 
                 '/{repo_name:.*}/pull-request',
 
                 controller='pullrequests',
 
                 action='show_all', conditions=dict(function=check_repo,
 
                                                method=["GET"]))
 

	
 
    rmap.connect('pullrequest_comment',
 
                 '/{repo_name:.*}/pull-request-comment/{pull_request_id}',
 
                 controller='pullrequests',
 
                 action='comment', conditions=dict(function=check_repo,
 
                                                method=["POST"]))
 

	
 
    rmap.connect('summary_home', '/{repo_name:.*}/summary',
 
                controller='summary', conditions=dict(function=check_repo))
 

	
 
    rmap.connect('shortlog_home', '/{repo_name:.*}/shortlog',
 
                controller='shortlog', conditions=dict(function=check_repo))
 

	
 
    rmap.connect('branches_home', '/{repo_name:.*}/branches',
 
                controller='branches', conditions=dict(function=check_repo))
 

	
 
    rmap.connect('tags_home', '/{repo_name:.*}/tags',
 
                controller='tags', conditions=dict(function=check_repo))
 

	
 
    rmap.connect('bookmarks_home', '/{repo_name:.*}/bookmarks',
 
                controller='bookmarks', conditions=dict(function=check_repo))
 

	
 
    rmap.connect('changelog_home', '/{repo_name:.*}/changelog',
 
                controller='changelog', conditions=dict(function=check_repo))
 

	
 
    rmap.connect('changelog_details', '/{repo_name:.*}/changelog_details/{cs}',
 
                controller='changelog', action='changelog_details',
 
                conditions=dict(function=check_repo))
 

	
 
    rmap.connect('files_home', '/{repo_name:.*}/files/{revision}/{f_path:.*}',
 
                controller='files', revision='tip', f_path='',
 
                conditions=dict(function=check_repo))
 

	
 
    rmap.connect('files_diff_home', '/{repo_name:.*}/diff/{f_path:.*}',
 
                controller='files', action='diff', revision='tip', f_path='',
 
                conditions=dict(function=check_repo))
 

	
 
    rmap.connect('files_rawfile_home',
 
                 '/{repo_name:.*}/rawfile/{revision}/{f_path:.*}',
 
                 controller='files', action='rawfile', revision='tip',
 
                 f_path='', conditions=dict(function=check_repo))
 

	
 
    rmap.connect('files_raw_home',
 
                 '/{repo_name:.*}/raw/{revision}/{f_path:.*}',
 
                 controller='files', action='raw', revision='tip', f_path='',
 
                 conditions=dict(function=check_repo))
 

	
 
    rmap.connect('files_annotate_home',
 
                 '/{repo_name:.*}/annotate/{revision}/{f_path:.*}',
 
                 controller='files', action='index', revision='tip',
 
                 f_path='', annotate=True, conditions=dict(function=check_repo))
 

	
 
    rmap.connect('files_edit_home',
 
                 '/{repo_name:.*}/edit/{revision}/{f_path:.*}',
 
                 controller='files', action='edit', revision='tip',
 
                 f_path='', conditions=dict(function=check_repo))
 

	
 
    rmap.connect('files_add_home',
 
                 '/{repo_name:.*}/add/{revision}/{f_path:.*}',
 
                 controller='files', action='add', revision='tip',
 
                 f_path='', conditions=dict(function=check_repo))
 

	
 
    rmap.connect('files_archive_home', '/{repo_name:.*}/archive/{fname}',
 
                controller='files', action='archivefile',
 
                conditions=dict(function=check_repo))
 

	
 
    rmap.connect('files_nodelist_home',
 
                 '/{repo_name:.*}/nodelist/{revision}/{f_path:.*}',
 
                controller='files', action='nodelist',
 
                conditions=dict(function=check_repo))
 

	
 
    rmap.connect('repo_settings_delete', '/{repo_name:.*}/settings',
 
                controller='settings', action="delete",
 
                conditions=dict(method=["DELETE"], function=check_repo))
 

	
 
    rmap.connect('repo_settings_update', '/{repo_name:.*}/settings',
 
                controller='settings', action="update",
 
                conditions=dict(method=["PUT"], function=check_repo))
 

	
 
    rmap.connect('repo_settings_home', '/{repo_name:.*}/settings',
 
                controller='settings', action='index',
 
                conditions=dict(function=check_repo))
 

	
 
    rmap.connect('repo_fork_create_home', '/{repo_name:.*}/fork',
 
                controller='forks', action='fork_create',
 
                conditions=dict(function=check_repo, method=["POST"]))
 

	
 
    rmap.connect('repo_fork_home', '/{repo_name:.*}/fork',
 
                controller='forks', action='fork',
 
                conditions=dict(function=check_repo))
 

	
 
    rmap.connect('repo_forks_home', '/{repo_name:.*}/forks',
 
                 controller='forks', action='forks',
 
                 conditions=dict(function=check_repo))
 

	
 
    rmap.connect('repo_followers_home', '/{repo_name:.*}/followers',
 
                 controller='followers', action='followers',
 
                 conditions=dict(function=check_repo))
 

	
 
    return rmap
rhodecode/controllers/changeset.py
Show inline comments
 
@@ -297,132 +297,132 @@ class ChangesetController(BaseRepoContro
 
        # count inline comments
 
        for __, lines in c.inline_comments:
 
            for comments in lines.values():
 
                c.inline_cnt += len(comments)
 

	
 
        if len(c.cs_ranges) == 1:
 
            c.changeset = c.cs_ranges[0]
 
            c.changes = c.changes[c.changeset.raw_id]
 

	
 
            return render('changeset/changeset.html')
 
        else:
 
            return render('changeset/changeset_range.html')
 

	
 
    def raw_changeset(self, revision):
 

	
 
        method = request.GET.get('diff', 'show')
 
        ignore_whitespace = request.GET.get('ignorews') == '1'
 
        line_context = request.GET.get('context', 3)
 
        try:
 
            c.scm_type = c.rhodecode_repo.alias
 
            c.changeset = c.rhodecode_repo.get_changeset(revision)
 
        except RepositoryError:
 
            log.error(traceback.format_exc())
 
            return redirect(url('home'))
 
        else:
 
            try:
 
                c.changeset_parent = c.changeset.parents[0]
 
            except IndexError:
 
                c.changeset_parent = None
 
            c.changes = []
 

	
 
            for node in c.changeset.added:
 
                filenode_old = FileNode(node.path, '')
 
                if filenode_old.is_binary or node.is_binary:
 
                    diff = _('binary file') + '\n'
 
                else:
 
                    f_gitdiff = diffs.get_gitdiff(filenode_old, node,
 
                                           ignore_whitespace=ignore_whitespace,
 
                                           context=line_context)
 
                    diff = diffs.DiffProcessor(f_gitdiff,
 
                                                format='gitdiff').raw_diff()
 

	
 
                cs1 = None
 
                cs2 = node.changeset.raw_id
 
                c.changes.append(('added', node, diff, cs1, cs2))
 

	
 
            for node in c.changeset.changed:
 
                filenode_old = c.changeset_parent.get_node(node.path)
 
                if filenode_old.is_binary or node.is_binary:
 
                    diff = _('binary file')
 
                else:
 
                    f_gitdiff = diffs.get_gitdiff(filenode_old, node,
 
                                           ignore_whitespace=ignore_whitespace,
 
                                           context=line_context)
 
                    diff = diffs.DiffProcessor(f_gitdiff,
 
                                                format='gitdiff').raw_diff()
 

	
 
                cs1 = filenode_old.changeset.raw_id
 
                cs2 = node.changeset.raw_id
 
                c.changes.append(('changed', node, diff, cs1, cs2))
 

	
 
        response.content_type = 'text/plain'
 

	
 
        if method == 'download':
 
            response.content_disposition = 'attachment; filename=%s.patch' \
 
                                            % revision
 

	
 
        c.parent_tmpl = ''.join(['# Parent  %s\n' % x.raw_id
 
                                 for x in c.changeset.parents])
 

	
 
        c.diffs = ''
 
        for x in c.changes:
 
            c.diffs += x[2]
 

	
 
        return render('changeset/raw_changeset.html')
 

	
 
    @jsonify
 
    def comment(self, repo_name, revision):
 
        status = request.POST.get('changeset_status')
 
        change_status = request.POST.get('change_changeset_status')
 

	
 
        comm = ChangesetCommentsModel().create(
 
            text=request.POST.get('text'),
 
            repo_id=c.rhodecode_db_repo.repo_id,
 
            user_id=c.rhodecode_user.user_id,
 
            revision=revision,
 
            f_path=request.POST.get('f_path'),
 
            line_no=request.POST.get('line'),
 
            status_change=(ChangesetStatus.get_status_lbl(status) 
 
                           if status and change_status else None)
 
        )
 

	
 
        # get status if set !
 
        if status and change_status:
 
            ChangesetStatusModel().set_status(
 
                c.rhodecode_db_repo.repo_id,
 
                revision,
 
                status,
 
                c.rhodecode_user.user_id,
 
                comm,
 
                revision=revision,
 
            )
 
        action_logger(self.rhodecode_user,
 
                      'user_commented_revision:%s' % revision,
 
                      c.rhodecode_db_repo, self.ip_addr, self.sa)
 

	
 
        Session.commit()
 

	
 
        if not request.environ.get('HTTP_X_PARTIAL_XHR'):
 
            return redirect(h.url('changeset_home', repo_name=repo_name,
 
                                  revision=revision))
 

	
 
        data = {
 
           'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
 
        }
 
        if comm:
 
            c.co = comm
 
            data.update(comm.get_dict())
 
            data.update({'rendered_text':
 
                         render('changeset/changeset_comment_block.html')})
 

	
 
        return data
 

	
 
    @jsonify
 
    def delete_comment(self, repo_name, comment_id):
 
        co = ChangesetComment.get(comment_id)
 
        owner = lambda: co.author.user_id == c.rhodecode_user.user_id
 
        if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
 
            ChangesetCommentsModel().delete(comment=co)
 
            Session.commit()
 
            return True
 
        else:
 
            raise HTTPForbidden()
rhodecode/controllers/pullrequests.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
"""
 
    rhodecode.controllers.pullrequests
 
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
    pull requests controller for rhodecode for initializing pull requests
 

	
 
    :created_on: May 7, 2012
 
    :author: marcink
 
    :copyright: (C) 2010-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
 
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
 
from pylons.i18n.translation import _
 
from pylons.decorators import jsonify
 

	
 
from rhodecode.lib.base import BaseRepoController, render
 
from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
 
from rhodecode.lib import helpers as h
 
from rhodecode.lib import diffs
 
from rhodecode.model.db import User, PullRequest, Repository, ChangesetStatus
 
from rhodecode.lib.utils import action_logger
 
from rhodecode.model.db import User, PullRequest, 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__)
 

	
 

	
 
class PullrequestsController(BaseRepoController):
 

	
 
    @LoginRequired()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def __before__(self):
 
        super(PullrequestsController, self).__before__()
 

	
 
    def _get_repo_refs(self, repo):
 
        hist_l = []
 

	
 
        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)
 
        hist_l.append(tags_group)
 

	
 
        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)
 
        c.org_repos = []
 
        c.other_repos = []
 
        c.org_repos.append((org_repo.repo_name, '%s/%s' % (
 
                                org_repo.user.username, c.repo_name))
 
                           )
 

	
 
        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
 
        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 = []
 
        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 _load_compare_data(self, pull_request):
 
        """
 
        Load context data needed for generating compare diff
 

	
 
        :param pull_request:
 
        :type 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
 

	
 
        c.cs_ranges, discovery_data = PullRequestModel().get_compare_data(
 
                                       org_repo, org_ref, other_repo, other_ref
 
                                      )
 

	
 
        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)
 

	
 
        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)
 

	
 
        # load compare data into template context
 
        self._load_compare_data(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
 
        # changeset(pull-request) status
 
        c.current_changeset_status = ChangesetStatusModel()\
 
                              .get_status(c.rhodecode_db_repo.repo_id,
 
                                          pull_request=pull_request_id)
 
                              .get_status(c.pull_request.org_repo,
 
                                          pull_request=c.pull_request)
 
        c.changeset_statuses = ChangesetStatus.STATUSES
 
        return render('/pullrequests/pullrequest_show.html')
 

	
 
    @jsonify
 
    def comment(self, repo_name, pull_request_id):
 

	
 
        status = request.POST.get('changeset_status')
 
        change_status = request.POST.get('change_changeset_status')
 

	
 
        comm = ChangesetCommentsModel().create(
 
            text=request.POST.get('text'),
 
            repo_id=c.rhodecode_db_repo.repo_id,
 
            user_id=c.rhodecode_user.user_id,
 
            pull_request=pull_request_id,
 
            f_path=request.POST.get('f_path'),
 
            line_no=request.POST.get('line'),
 
            status_change=(ChangesetStatus.get_status_lbl(status) 
 
                           if status and change_status else None)
 
        )
 

	
 
        # get status if set !
 
        if status and change_status:
 
            ChangesetStatusModel().set_status(
 
                c.rhodecode_db_repo.repo_id,
 
                status,
 
                c.rhodecode_user.user_id,
 
                comm,
 
                pull_request=pull_request_id
 
            )
 
        action_logger(self.rhodecode_user,
 
                      'user_commented_pull_request:%s' % pull_request_id,
 
                      c.rhodecode_db_repo, self.ip_addr, self.sa)
 

	
 
        Session.commit()
 

	
 
        if not request.environ.get('HTTP_X_PARTIAL_XHR'):
 
            return redirect(h.url('pullrequest_show', repo_name=repo_name,
 
                                  pull_request_id=pull_request_id))
 

	
 
        data = {
 
           'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
 
        }
 
        if comm:
 
            c.co = comm
 
            data.update(comm.get_dict())
 
            data.update({'rendered_text':
 
                         render('changeset/changeset_comment_block.html')})
 

	
 
        return data
 
\ No newline at end of file
rhodecode/model/changeset_status.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
"""
 
    rhodecode.model.changeset_status
 
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 

	
 
    :created_on: Apr 30, 2012
 
    :author: marcink
 
    :copyright: (C) 2011-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 rhodecode.model import BaseModel
 
from rhodecode.model.db import ChangesetStatus, PullRequest
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class ChangesetStatusModel(BaseModel):
 

	
 
    def __get_changeset_status(self, changeset_status):
 
        return self._get_instance(ChangesetStatus, changeset_status)
 

	
 
    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 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 or None
 
        :type revision: str
 
        :param pull_request: pull_request reference
 
        :type:
 
        """
 
        repo = self._get_repo(repo)
 

	
 
        q = ChangesetStatus.query()\
 
            .filter(ChangesetStatus.repo == repo)\
 
            .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()
 
        # need to use first here since there can be multiple statuses
 
        # returned from pull_request
 
        status = q.first()
 
        status = status.status if status else status
 
        st = status or ChangesetStatus.DEFAULT
 
        return str(st)
 

	
 
    def set_status(self, repo, revision, status, user, comment):
 
    def set_status(self, repo, status, user, comment, revision=None,
 
                   pull_request=None):
 
        """
 
        Creates new status for changeset or updates the old ones bumping their
 
        version, leaving the current status at
 

	
 
        :param repo:
 
        :type repo:
 
        :param revision:
 
        :type revision:
 
        :param status:
 
        :type status:
 
        :param user:
 
        :type user:
 
        :param comment:
 
        :type comment:
 
        """
 
        repo = self._get_repo(repo)
 

	
 
        cur_statuses = ChangesetStatus.query()\
 
            .filter(ChangesetStatus.repo == repo)\
 
            .filter(ChangesetStatus.revision == revision)\
 
            .all()
 
        q = ChangesetStatus.query()
 

	
 
        if revision:
 
            q = q.filter(ChangesetStatus.repo == repo)
 
            q = q.filter(ChangesetStatus.revision == revision)
 
        elif pull_request:
 
            pull_request = self.__get_pull_request(pull_request)
 
            q = q.filter(ChangesetStatus.repo == pull_request.org_repo)
 
            q = q.filter(ChangesetStatus.pull_request == pull_request)
 
        cur_statuses = q.all()
 

	
 
        if cur_statuses:
 
            for st in cur_statuses:
 
                st.version += 1
 
                self.sa.add(st)
 
        new_status = ChangesetStatus()
 
        new_status.author = self._get_user(user)
 
        new_status.repo = self._get_repo(repo)
 
        new_status.status = status
 
        new_status.revision = revision
 
        new_status.comment = comment
 
        self.sa.add(new_status)
 
        return new_status
 

	
 
        def _create_status(user, repo, status, comment, revision, pull_request):
 
            new_status = ChangesetStatus()
 
            new_status.author = self._get_user(user)
 
            new_status.repo = self._get_repo(repo)
 
            new_status.status = status
 
            new_status.comment = comment
 
            new_status.revision = revision
 
            new_status.pull_request = pull_request
 
            return new_status
 

	
 
        if revision:
 
            new_status = _create_status(user=user, repo=repo, status=status,
 
                           comment=comment, revision=revision, 
 
                           pull_request=None)
 
            self.sa.add(new_status)
 
            return new_status
 
        elif pull_request:
 
            #pull request can have more than one revision associated to it
 
            #we need to create new version for each one
 
            new_statuses = []
 
            repo = pull_request.org_repo
 
            for rev in pull_request.revisions:
 
                new_status = _create_status(user=user, repo=repo,
 
                                            status=status, comment=comment,
 
                                            revision=rev,
 
                                            pull_request=pull_request)
 
                new_statuses.append(new_status)
 
                self.sa.add(new_status)
 
            return new_statuses
rhodecode/model/comment.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
"""
 
    rhodecode.model.comment
 
    ~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
    comments model for RhodeCode
 

	
 
    :created_on: Nov 11, 2011
 
    :author: marcink
 
    :copyright: (C) 2011-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
 
import traceback
 

	
 
from pylons.i18n.translation import _
 
from sqlalchemy.util.compat import defaultdict
 

	
 
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, PullRequest
 
from rhodecode.model.notification import NotificationModel
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class ChangesetCommentsModel(BaseModel):
 

	
 
    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):
 
            user_obj = User.get_by_username(username, case_insensitive=True)
 
            if user_obj:
 
                user_objects.append(user_obj)
 
        return user_objects
 

	
 
    def create(self, text, repo_id, user_id, revision, f_path=None,
 
               line_no=None, status_change=None):
 
    def create(self, text, repo_id, user_id, revision=None, pull_request=None,
 
               f_path=None, line_no=None, status_change=None):
 
        """
 
        Creates new comment for changeset. IF status_change is not none
 
        this comment is associated with a status change of changeset
 
        Creates new comment for changeset or pull request.
 
        IF status_change is not none this comment is associated with a 
 
        status change of changeset or changesets associated with pull request
 

	
 
        :param text:
 
        :param repo_id:
 
        :param user_id:
 
        :param revision:
 
        :param pull_request:
 
        :param f_path:
 
        :param line_no:
 
        :param status_change:
 
        """
 
        if not text:
 
            return
 

	
 
        if text:
 
            repo = Repository.get(repo_id)
 
        repo = Repository.get(repo_id)
 
        comment = ChangesetComment()
 
        comment.repo = repo
 
        comment.user_id = user_id
 
        comment.text = text
 
        comment.f_path = f_path
 
        comment.line_no = line_no
 

	
 
        if revision:
 
            cs = repo.scm_instance.get_changeset(revision)
 
            desc = "%s - %s" % (cs.short_id, h.shorter(cs.message, 256))
 
            author_email = cs.author_email
 
            comment = ChangesetComment()
 
            comment.repo = repo
 
            comment.user_id = user_id
 
            comment.revision = revision
 
            comment.text = text
 
            comment.f_path = f_path
 
            comment.line_no = line_no
 
        elif pull_request:
 
            pull_request = self.__get_pull_request(pull_request)
 
            comment.pull_request = pull_request
 
            desc = ''
 
        else:
 
            raise Exception('Please specify revision or pull_request_id')
 

	
 
            self.sa.add(comment)
 
            self.sa.flush()
 
            # make notification
 
            line = ''
 
        self.sa.add(comment)
 
        self.sa.flush()
 

	
 
        # make notification
 
        line = ''
 
        body = text
 

	
 
        #changeset
 
        if revision:
 
            if line_no:
 
                line = _('on line %s') % line_no
 
            subj = safe_unicode(
 
                h.link_to('Re commit: %(commit_desc)s %(line)s' % \
 
                          {'commit_desc': desc, 'line': line},
 
                          h.url('changeset_home', repo_name=repo.repo_name,
 
                                revision=revision,
 
                                anchor='comment-%s' % comment.comment_id,
 
                                qualified=True,
 
                          )
 
                )
 
            )
 

	
 
            body = text
 

	
 
            notification_type = Notification.TYPE_CHANGESET_COMMENT
 
            # get the current participants of this changeset
 
            recipients = ChangesetComment.get_users(revision=revision)
 

	
 
            # add changeset author if it's in rhodecode system
 
            recipients += [User.get_by_email(author_email)]
 
        #pull request
 
        elif pull_request:
 
            #TODO: make this something usefull
 
            subj = 'commented on pull request something...'
 
            notification_type = Notification.TYPE_PULL_REQUEST_COMMENT
 
            # get the current participants of this pull request
 
            recipients = ChangesetComment.get_users(pull_request_id=
 
                                                pull_request.pull_request_id)
 
            # add pull request author
 
            recipients += [pull_request.author]
 

	
 
            # create notification objects, and emails
 
        # create notification objects, and emails
 
        NotificationModel().create(
 
          created_by=user_id, subject=subj, body=body,
 
          recipients=recipients, type_=notification_type,
 
          email_kwargs={'status_change': status_change}
 
        )
 

	
 
        mention_recipients = set(self._extract_mentions(body))\
 
                                .difference(recipients)
 
        if mention_recipients:
 
            subj = _('[Mention]') + ' ' + subj
 
            NotificationModel().create(
 
              created_by=user_id, subject=subj, body=body,
 
              recipients=recipients, type_=Notification.TYPE_CHANGESET_COMMENT,
 
              email_kwargs={'status_change': status_change}
 
                created_by=user_id, subject=subj, body=body,
 
                recipients=mention_recipients,
 
                type_=notification_type,
 
                email_kwargs={'status_change': status_change}
 
            )
 

	
 
            mention_recipients = set(self._extract_mentions(body))\
 
                                    .difference(recipients)
 
            if mention_recipients:
 
                subj = _('[Mention]') + ' ' + subj
 
                NotificationModel().create(
 
                    created_by=user_id, subject=subj, body=body,
 
                    recipients=mention_recipients,
 
                    type_=Notification.TYPE_CHANGESET_COMMENT,
 
                    email_kwargs={'status_change': status_change}
 
                )
 

	
 
            return comment
 
        return comment
 

	
 
    def delete(self, comment):
 
        """
 
        Deletes given comment
 

	
 
        :param comment_id:
 
        """
 
        comment = self.__get_changeset_comment(comment)
 
        self.sa.delete(comment)
 

	
 
        return comment
 

	
 
    def get_comments(self, repo_id, revision=None, pull_request=None):
 
        """
 
        Get's main comments based on revision or pull_request_id
 

	
 
        :param repo_id:
 
        :type repo_id:
 
        :param revision:
 
        :type revision:
 
        :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:
 
            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')
 
        return q.all()
 

	
 
    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)\
 
            .filter(ChangesetComment.f_path != None)\
 
            .order_by(ChangesetComment.comment_id.asc())\
 

	
 
        if revision:
 
            q = q.filter(ChangesetComment.revision == revision)
 
        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')
 

	
 
        comments = q.all()
 

	
 
        paths = defaultdict(lambda: defaultdict(list))
 

	
 
        for co in comments:
 
            paths[co.f_path][co.line_no].append(co)
 
        return paths.items()
rhodecode/model/db.py
Show inline comments
 
@@ -673,193 +673,198 @@ class Repository(Base, BaseModel):
 
    def get_new_name(self, repo_name):
 
        """
 
        returns new full repository name based on assigned group and new new
 

	
 
        :param group_name:
 
        """
 
        path_prefix = self.group.full_path_splitted if self.group else []
 
        return Repository.url_sep().join(path_prefix + [repo_name])
 

	
 
    @property
 
    def _ui(self):
 
        """
 
        Creates an db based ui object for this repository
 
        """
 
        from mercurial import ui
 
        from mercurial import config
 
        baseui = ui.ui()
 

	
 
        #clean the baseui object
 
        baseui._ocfg = config.config()
 
        baseui._ucfg = config.config()
 
        baseui._tcfg = config.config()
 

	
 
        ret = RhodeCodeUi.query()\
 
            .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
 

	
 
        hg_ui = ret
 
        for ui_ in hg_ui:
 
            if ui_.ui_active:
 
                log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
 
                          ui_.ui_key, ui_.ui_value)
 
                baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
 

	
 
        return baseui
 

	
 
    @classmethod
 
    def is_valid(cls, repo_name):
 
        """
 
        returns True if given repo name is a valid filesystem repository
 

	
 
        :param cls:
 
        :param repo_name:
 
        """
 
        from rhodecode.lib.utils import is_valid_repo
 

	
 
        return is_valid_repo(repo_name, cls.base_path())
 

	
 
    #==========================================================================
 
    # SCM PROPERTIES
 
    #==========================================================================
 

	
 
    def get_changeset(self, rev=None):
 
        return get_changeset_safe(self.scm_instance, rev)
 

	
 
    @property
 
    def tip(self):
 
        return self.get_changeset('tip')
 

	
 
    @property
 
    def author(self):
 
        return self.tip.author
 

	
 
    @property
 
    def last_change(self):
 
        return self.scm_instance.last_change
 

	
 
    def comments(self, revisions=None):
 
        """
 
        Returns comments for this repository grouped by revisions
 

	
 
        :param revisions: filter query by revisions only
 
        """
 
        cmts = ChangesetComment.query()\
 
            .filter(ChangesetComment.repo == self)
 
        if revisions:
 
            cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
 
        grouped = defaultdict(list)
 
        for cmt in cmts.all():
 
            grouped[cmt.revision].append(cmt)
 
        return grouped
 

	
 
    def statuses(self, revisions=None):
 
        """
 
        Returns statuses for this repository
 

	
 
        :param revisions: list of revisions to get statuses for
 
        :type revisions: list
 
        """
 

	
 
        statuses = ChangesetStatus.query()\
 
            .filter(ChangesetStatus.repo == self)\
 
            .filter(ChangesetStatus.version == 0)
 
        if revisions:
 
            statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
 
        grouped = {}
 
        for stat in statuses.all():
 
            grouped[stat.revision] = [str(stat.status), stat.status_lbl]
 
            pr_id = pr_repo = None
 
            if stat.pull_request:
 
                pr_id = stat.pull_request.pull_request_id
 
                pr_repo = stat.pull_request.other_repo.repo_name
 
            grouped[stat.revision] = [str(stat.status), stat.status_lbl,
 
                                      pr_id, pr_repo]
 
        return grouped
 

	
 
    #==========================================================================
 
    # SCM CACHE INSTANCE
 
    #==========================================================================
 

	
 
    @property
 
    def invalidate(self):
 
        return CacheInvalidation.invalidate(self.repo_name)
 

	
 
    def set_invalidate(self):
 
        """
 
        set a cache for invalidation for this instance
 
        """
 
        CacheInvalidation.set_invalidate(self.repo_name)
 

	
 
    @LazyProperty
 
    def scm_instance(self):
 
        return self.__get_instance()
 

	
 
    def scm_instance_cached(self, cache_map=None):
 
        @cache_region('long_term')
 
        def _c(repo_name):
 
            return self.__get_instance()
 
        rn = self.repo_name
 
        log.debug('Getting cached instance of repo')
 

	
 
        if cache_map:
 
            # get using prefilled cache_map
 
            invalidate_repo = cache_map[self.repo_name]
 
            if invalidate_repo:
 
                invalidate_repo = (None if invalidate_repo.cache_active
 
                                   else invalidate_repo)
 
        else:
 
            # get from invalidate
 
            invalidate_repo = self.invalidate
 

	
 
        if invalidate_repo is not None:
 
            region_invalidate(_c, None, rn)
 
            # update our cache
 
            CacheInvalidation.set_valid(invalidate_repo.cache_key)
 
        return _c(rn)
 

	
 
    def __get_instance(self):
 
        repo_full_path = self.repo_full_path
 
        try:
 
            alias = get_scm(repo_full_path)[0]
 
            log.debug('Creating instance of %s repository' % alias)
 
            backend = get_backend(alias)
 
        except VCSError:
 
            log.error(traceback.format_exc())
 
            log.error('Perhaps this repository is in db and not in '
 
                      'filesystem run rescan repositories with '
 
                      '"destroy old data " option from admin panel')
 
            return
 

	
 
        if alias == 'hg':
 

	
 
            repo = backend(safe_str(repo_full_path), create=False,
 
                           baseui=self._ui)
 
            # skip hidden web repository
 
            if repo._get_hidden():
 
                return
 
        else:
 
            repo = backend(repo_full_path, create=False)
 

	
 
        return repo
 

	
 

	
 
class RepoGroup(Base, BaseModel):
 
    __tablename__ = 'groups'
 
    __table_args__ = (
 
        UniqueConstraint('group_name', 'group_parent_id'),
 
        CheckConstraint('group_id != group_parent_id'),
 
        {'extend_existing': True, 'mysql_engine': 'InnoDB',
 
         'mysql_charset': 'utf8'},
 
    )
 
    __mapper_args__ = {'order_by': 'group_name'}
 

	
 
    group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
 
    group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
 
    group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
 
    group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 

	
 
    repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
 
    users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
 

	
 
    parent_group = relationship('RepoGroup', remote_side=group_id)
 

	
 
    def __init__(self, group_name='', parent_group=None):
 
        self.group_name = group_name
 
        self.parent_group = parent_group
 

	
 
    def __unicode__(self):
 
        return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
 
                                  self.group_name)
 
@@ -1243,290 +1248,295 @@ class CacheInvalidation(Base, BaseModel)
 

	
 
        if inv and inv.cache_active is False:
 
            return inv
 

	
 
    @classmethod
 
    def set_invalidate(cls, key):
 
        """
 
        Mark this Cache key for invalidation
 

	
 
        :param key:
 
        """
 

	
 
        key, _prefix, _org_key = cls._get_key(key)
 
        inv_objs = Session.query(cls).filter(cls.cache_args == _org_key).all()
 
        log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
 
                                                             _org_key))
 
        try:
 
            for inv_obj in inv_objs:
 
                if inv_obj:
 
                    inv_obj.cache_active = False
 

	
 
                Session.add(inv_obj)
 
            Session.commit()
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            Session.rollback()
 

	
 
    @classmethod
 
    def set_valid(cls, key):
 
        """
 
        Mark this cache key as active and currently cached
 

	
 
        :param key:
 
        """
 
        inv_obj = cls.get_by_key(key)
 
        inv_obj.cache_active = True
 
        Session.add(inv_obj)
 
        Session.commit()
 

	
 
    @classmethod
 
    def get_cache_map(cls):
 

	
 
        class cachemapdict(dict):
 

	
 
            def __init__(self, *args, **kwargs):
 
                fixkey = kwargs.get('fixkey')
 
                if fixkey:
 
                    del kwargs['fixkey']
 
                self.fixkey = fixkey
 
                super(cachemapdict, self).__init__(*args, **kwargs)
 

	
 
            def __getattr__(self, name):
 
                key = name
 
                if self.fixkey:
 
                    key, _prefix, _org_key = cls._get_key(key)
 
                if key in self.__dict__:
 
                    return self.__dict__[key]
 
                else:
 
                    return self[key]
 

	
 
            def __getitem__(self, key):
 
                if self.fixkey:
 
                    key, _prefix, _org_key = cls._get_key(key)
 
                try:
 
                    return super(cachemapdict, self).__getitem__(key)
 
                except KeyError:
 
                    return
 

	
 
        cache_map = cachemapdict(fixkey=True)
 
        for obj in cls.query().all():
 
            cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
 
        return cache_map
 

	
 

	
 
class ChangesetComment(Base, BaseModel):
 
    __tablename__ = 'changeset_comments'
 
    __table_args__ = (
 
        {'extend_existing': True, 'mysql_engine': 'InnoDB',
 
         'mysql_charset': 'utf8'},
 
    )
 
    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=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)
 
    text = Column('text', Unicode(25000), nullable=False)
 
    modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
 

	
 
    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):
 
    def get_users(cls, revision=None, pull_request_id=None):
 
        """
 
        Returns user associated with this changesetComment. ie those
 
        Returns user associated with this ChangesetComment. ie those
 
        who actually commented
 

	
 
        :param cls:
 
        :param revision:
 
        """
 
        return Session.query(User)\
 
                .filter(cls.revision == revision)\
 
                .join(ChangesetComment.author).all()
 
        q = Session.query(User)\
 
                .join(ChangesetComment.author)
 
        if revision:
 
            q = q.filter(cls.revision == revision)
 
        elif pull_request_id:
 
            q = q.filter(cls.pull_request_id == pull_request_id)
 
        return q.all()
 

	
 

	
 
class ChangesetStatus(Base, BaseModel):
 
    __tablename__ = 'changeset_statuses'
 
    __table_args__ = (
 
        UniqueConstraint('repo_id', 'revision', 'version'),
 
        {'extend_existing': True, 'mysql_engine': 'InnoDB',
 
         'mysql_charset': 'utf8'}
 
    )
 

	
 
    STATUSES = [
 
        ('not_reviewed', _("Not Reviewed")),  # (no icon) and default
 
        ('approved', _("Approved")),
 
        ('rejected', _("Rejected")),
 
        ('under_review', _("Under Review")),
 
    ]
 
    DEFAULT = STATUSES[0][0]
 

	
 
    changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
 
    repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
 
    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
 
    revision = Column('revision', String(40), nullable=False)
 
    status = Column('status', String(128), nullable=False, default=DEFAULT)
 
    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):
 
        return dict(cls.STATUSES).get(value)
 

	
 
    @property
 
    def status_lbl(self):
 
        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)
 
    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)
 
    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)
 

	
 
    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')
 

	
 
    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__ = (
 
        {'extend_existing': True, 'mysql_engine': 'InnoDB',
 
         'mysql_charset': 'utf8'},
 
    )
 

	
 
    TYPE_CHANGESET_COMMENT = u'cs_comment'
 
    TYPE_MESSAGE = u'message'
 
    TYPE_MENTION = u'mention'
 
    TYPE_REGISTRATION = u'registration'
 
    TYPE_PULL_REQUEST = u'pull_request'
 
    TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
 

	
 
    notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
 
    subject = Column('subject', Unicode(512), nullable=True)
 
    body = Column('body', Unicode(50000), nullable=True)
 
    created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
 
    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
 
    type_ = Column('type', Unicode(256))
 

	
 
    created_by_user = relationship('User')
 
    notifications_to_users = relationship('UserNotification', lazy='joined',
 
                                          cascade="all, delete, delete-orphan")
 

	
 
    @property
 
    def recipients(self):
 
        return [x.user for x in UserNotification.query()\
 
                .filter(UserNotification.notification == self)\
 
                .order_by(UserNotification.user).all()]
 

	
 
    @classmethod
 
    def create(cls, created_by, subject, body, recipients, type_=None):
 
        if type_ is None:
 
            type_ = Notification.TYPE_MESSAGE
 

	
 
        notification = cls()
 
        notification.created_by_user = created_by
 
        notification.subject = subject
 
        notification.body = body
 
        notification.type_ = type_
 
        notification.created_on = datetime.datetime.now()
 

	
 
        for u in recipients:
 
            assoc = UserNotification()
 
            assoc.notification = notification
 
            u.notifications.append(assoc)
 
        Session.add(notification)
 
        return notification
 

	
 
    @property
 
    def description(self):
 
        from rhodecode.model.notification import NotificationModel
 
        return NotificationModel().make_description(self)
 

	
 

	
 
class UserNotification(Base, BaseModel):
 
    __tablename__ = 'user_to_notification'
 
    __table_args__ = (
 
        UniqueConstraint('user_id', 'notification_id'),
 
        {'extend_existing': True, 'mysql_engine': 'InnoDB',
 
         'mysql_charset': 'utf8'}
 
    )
 
    user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
 
    notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
 
    read = Column('read', Boolean, default=False)
 
    sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
 

	
 
    user = relationship('User', lazy="joined")
 
    notification = relationship('Notification', lazy="joined",
 
                                order_by=lambda: Notification.created_on.desc(),)
 

	
 
    def mark_as_read(self):
 
        self.read = True
 
        Session.add(self)
 

	
 

	
 
class DbMigrateVersion(Base, BaseModel):
 
    __tablename__ = 'db_migrate_version'
 
    __table_args__ = (
 
        {'extend_existing': True, 'mysql_engine': 'InnoDB',
 
         'mysql_charset': 'utf8'},
 
    )
 
    repository_id = Column('repository_id', String(250), primary_key=True)
 
    repository_path = Column('repository_path', Text)
 
    version = Column('version', Integer)
rhodecode/model/notification.py
Show inline comments
 
@@ -105,152 +105,154 @@ class NotificationModel(BaseModel):
 
        rec_objs = set(recipients_objs).difference(set([created_by_obj]))
 

	
 
        # send email with notification to all other participants
 
        for rec in rec_objs:
 
            email_subject = NotificationModel().make_description(notif, False)
 
            type_ = type_
 
            email_body = body
 
            ## this is passed into template
 
            kwargs = {'subject': subject, 'body': h.rst_w_mentions(body)}
 
            kwargs.update(email_kwargs)
 
            email_body_html = EmailNotificationModel()\
 
                                .get_email_tmpl(type_, **kwargs)
 

	
 
            run_task(tasks.send_email, rec.email, email_subject, email_body,
 
                     email_body_html)
 

	
 
        return notif
 

	
 
    def delete(self, user, notification):
 
        # we don't want to remove actual notification just the assignment
 
        try:
 
            notification = self.__get_notification(notification)
 
            user = self._get_user(user)
 
            if notification and user:
 
                obj = UserNotification.query()\
 
                        .filter(UserNotification.user == user)\
 
                        .filter(UserNotification.notification
 
                                == notification)\
 
                        .one()
 
                self.sa.delete(obj)
 
                return True
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            raise
 

	
 
    def get_for_user(self, user, filter_=None):
 
        """
 
        Get mentions for given user, filter them if filter dict is given
 

	
 
        :param user:
 
        :type user:
 
        :param filter:
 
        """
 
        user = self._get_user(user)
 

	
 
        q = UserNotification.query()\
 
            .filter(UserNotification.user == user)\
 
            .join((Notification, UserNotification.notification_id ==
 
                                 Notification.notification_id))
 

	
 
        if filter_:
 
            q = q.filter(Notification.type_ == filter_.get('type'))
 

	
 
        return q.all()
 

	
 
    def mark_all_read_for_user(self, user, filter_=None):
 
        user = self._get_user(user)
 
        q = UserNotification.query()\
 
            .filter(UserNotification.user == user)\
 
            .filter(UserNotification.read == False)\
 
            .join((Notification, UserNotification.notification_id ==
 
                                 Notification.notification_id))
 
        if filter_:
 
            q = q.filter(Notification.type_ == filter_.get('type'))
 

	
 
        # this is a little inefficient but sqlalchemy doesn't support
 
        # update on joined tables :(
 
        for obj in q.all():
 
            obj.read = True
 
            self.sa.add(obj)
 

	
 
    def get_unread_cnt_for_user(self, user):
 
        user = self._get_user(user)
 
        return UserNotification.query()\
 
                .filter(UserNotification.read == False)\
 
                .filter(UserNotification.user == user).count()
 

	
 
    def get_unread_for_user(self, user):
 
        user = self._get_user(user)
 
        return [x.notification for x in UserNotification.query()\
 
                .filter(UserNotification.read == False)\
 
                .filter(UserNotification.user == user).all()]
 

	
 
    def get_user_notification(self, user, notification):
 
        user = self._get_user(user)
 
        notification = self.__get_notification(notification)
 

	
 
        return UserNotification.query()\
 
            .filter(UserNotification.notification == notification)\
 
            .filter(UserNotification.user == user).scalar()
 

	
 
    def make_description(self, notification, show_age=True):
 
        """
 
        Creates a human readable description based on properties
 
        of notification object
 
        """
 

	
 
        #alias
 
        _n = notification
 
        _map = {
 
            notification.TYPE_CHANGESET_COMMENT: _('commented on commit'),
 
            notification.TYPE_MESSAGE: _('sent message'),
 
            notification.TYPE_MENTION: _('mentioned you'),
 
            notification.TYPE_REGISTRATION: _('registered in RhodeCode'),
 
            notification.TYPE_PULL_REQUEST: _('opened new pull request')
 
            _n.TYPE_CHANGESET_COMMENT: _('commented on commit'),
 
            _n.TYPE_MESSAGE: _('sent message'),
 
            _n.TYPE_MENTION: _('mentioned you'),
 
            _n.TYPE_REGISTRATION: _('registered in RhodeCode'),
 
            _n.TYPE_PULL_REQUEST: _('opened new pull request'),
 
            _n.TYPE_PULL_REQUEST_COMMENT: _('commented on pull request')
 
        }
 

	
 
        tmpl = "%(user)s %(action)s %(when)s"
 
        if show_age:
 
            when = h.age(notification.created_on)
 
        else:
 
            DTF = lambda d: datetime.datetime.strftime(d, DATETIME_FORMAT)
 
            when = DTF(notification.created_on)
 

	
 
        data = dict(
 
            user=notification.created_by_user.username,
 
            action=_map[notification.type_], when=when,
 
        )
 
        return tmpl % data
 

	
 

	
 
class EmailNotificationModel(BaseModel):
 

	
 
    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):
 
        self._template_root = rhodecode.CONFIG['pylons.paths']['templates'][0]
 
        self._tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
 

	
 
        self.email_types = {
 
         self.TYPE_CHANGESET_COMMENT: 'email_templates/changeset_comment.html',
 
         self.TYPE_PASSWORD_RESET: 'email_templates/password_reset.html',
 
         self.TYPE_REGISTRATION: 'email_templates/registration.html',
 
         self.TYPE_DEFAULT: 'email_templates/default.html'
 
        }
 

	
 
    def get_email_tmpl(self, type_, **kwargs):
 
        """
 
        return generated template for email based on given type
 

	
 
        :param type_:
 
        """
 

	
 
        base = self.email_types.get(type_, self.email_types[self.TYPE_DEFAULT])
 
        email_template = self._tmpl_lookup.get_template(base)
 
        # translator inject
 
        _kwargs = {'_': _}
 
        _kwargs.update(kwargs)
 
        log.debug('rendering tmpl %s with kwargs %s' % (base, _kwargs))
 
        return email_template.render(**_kwargs)
rhodecode/templates/changelog/changelog.html
Show inline comments
 
## -*- coding: utf-8 -*-
 

	
 
<%inherit file="/base/base.html"/>
 

	
 
<%def name="title()">
 
${_('%s Changelog') % c.repo_name} - ${c.rhodecode_name}
 
</%def>
 

	
 
<%def name="breadcrumbs_links()">
 
    ${h.link_to(u'Home',h.url('/'))}
 
    &raquo;
 
    ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
 
    &raquo;
 
    <% size = c.size if c.size <= c.total_cs else c.total_cs %>
 
    ${_('Changelog')} - ${ungettext('showing %d out of %d revision', 'showing %d out of %d revisions', size) % (size, c.total_cs)}
 
</%def>
 

	
 
<%def name="page_nav()">
 
	${self.menu('changelog')}
 
</%def>
 

	
 
<%def name="main()">
 
<div class="box">
 
    <!-- box / title -->
 
    <div class="title">
 
        ${self.breadcrumbs()}
 
    </div>
 
    <div class="table">
 
		% if c.pagination:
 
			<div id="graph">
 
				<div id="graph_nodes">
 
					<canvas id="graph_canvas"></canvas>
 
				</div>
 
				<div id="graph_content">
 
                    <div class="info_box" style="clear: both;padding: 10px 6px;vertical-align: right;text-align: right;">
 
                    %if c.rhodecode_db_repo.fork:
 
                        <a title="${_('compare fork with %s' % c.rhodecode_db_repo.fork.repo_name)}" href="${h.url('compare_url',repo_name=c.repo_name,org_ref_type='branch',org_ref='default',other_ref_type='branch',other_ref='default',repo=c.rhodecode_db_repo.fork.repo_name)}" class="ui-btn small">${_('Compare fork')}</a>
 
                    %endif
 
                    <a href="${h.url('pullrequest_home',repo_name=c.repo_name)}" class="ui-btn small">${_('Open new pull request')}</a>
 
                    </div>
 
					<div class="container_header">
 
				        ${h.form(h.url.current(),method='get')}
 
				        <div class="info_box" style="float:left">
 
				          ${h.submit('set',_('Show'),class_="ui-btn")}
 
				          ${h.text('size',size=1,value=c.size)}
 
				          ${_('revisions')}
 
				        </div>
 
				        ${h.end_form()}
 
					<div id="rev_range_container" style="display:none"></div>
 
                    <div style="float:right">${h.select('branch_filter',c.branch_name,c.branch_filters)}</div>
 
					</div>
 

	
 
				%for cnt,cs in enumerate(c.pagination):
 
					<div id="chg_${cnt+1}" class="container ${'tablerow%s' % (cnt%2)}">
 
						<div class="left">
 
							<div>
 
							${h.checkbox(cs.short_id,class_="changeset_range")}
 
							<span class="tooltip" title="${h.tooltip(h.age(cs.date))}"><a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id)}"><span class="changeset_id">${cs.revision}:<span class="changeset_hash">${h.short_id(cs.raw_id)}</span></span></a></span>
 
							</div>
 
							<div class="author">
 
								<div class="gravatar">
 
									<img alt="gravatar" src="${h.gravatar_url(h.email(cs.author),16)}"/>
 
								</div>
 
								<div title="${cs.author}" class="user">${h.shorter(h.person(cs.author),22)}</div>
 
							</div>
 
                            <div class="date">${h.fmt_date(cs.date)}</div>
 
						</div>
 
						<div class="mid">
 
                            <div class="message">${h.urlify_commit(cs.message, c.repo_name,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</div>
 
                            <div class="expand"><span class="expandtext">&darr; ${_('show more')} &darr;</span></div>
 
						</div>
 
						<div class="right">
 
									<div class="changes">
 
                                        <div id="${cs.raw_id}"  style="float:right;" class="changed_total tooltip" title="${h.tooltip(_('Affected number of files, click to show more details'))}">${len(cs.affected_files)}</div>
 
                                        <div class="comments-container">
 
                                        %if len(c.comments.get(cs.raw_id,[])) > 0:
 
                                            <div class="comments-cnt" title="${('comments')}">
 
                                              <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
 
                                               <div class="comments-cnt">${len(c.comments[cs.raw_id])}</div>
 
                                               <img src="${h.url('/images/icons/comments.png')}">
 
                                              </a>
 
                                            </div>
 
                                        %endif
 
                                        </div>
 
                                        <div class="changeset-status-container">
 
                                            %if c.statuses.get(cs.raw_id):
 
                                              <div title="${_('Changeset status')}" class="changeset-status-lbl">${c.statuses.get(cs.raw_id)[1]}</div>
 
                                              <div class="changeset-status-ico"><img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses.get(cs.raw_id)[0])}" /></div>
 
                                              <div class="changeset-status-ico">
 
                                              %if c.statuses.get(cs.raw_id)[2]:
 
                                                <a class="tooltip" title="${_('Click to open associated pull request')}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}"><img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses.get(cs.raw_id)[0])}" /></a>
 
                                              %else:
 
                                                <img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses.get(cs.raw_id)[0])}" />
 
                                              %endif
 
                                              </div>
 
                                            %endif
 
                                        </div>
 
									</div>
 
								   %if cs.parents:
 
									%for p_cs in reversed(cs.parents):
 
										<div class="parent">${_('Parent')}
 
											<span class="changeset_id">${p_cs.revision}:<span class="changeset_hash">${h.link_to(h.short_id(p_cs.raw_id),
 
											h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}</span></span>
 
										</div>
 
									%endfor
 
								   %else:
 
                                        <div class="parent">${_('No parents')}</div>
 
                                   %endif
 

	
 
								<span class="logtags">
 
									%if len(cs.parents)>1:
 
									<span class="merge">${_('merge')}</span>
 
									%endif
 
									%if cs.branch:
 
									<span class="branchtag" title="${'%s %s' % (_('branch'),cs.branch)}">
 
									   ${h.link_to(h.shorter(cs.branch),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
 
                                    </span>
 
									%endif
 
                                    %if h.is_hg(c.rhodecode_repo):
 
                                      %for book in cs.bookmarks:
 
                                      <span class="bookbook" title="${'%s %s' % (_('bookmark'),book)}">
 
                                         ${h.link_to(h.shorter(book),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
 
                                      </span>
 
                                      %endfor
 
                                    %endif
 
									%for tag in cs.tags:
 
										<span class="tagtag"  title="${'%s %s' % (_('tag'),tag)}">
 
										${h.link_to(h.shorter(tag),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}</span>
 
									%endfor
 
								</span>
 
						</div>
 
					</div>
 

	
 
				%endfor
 
				<div class="pagination-wh pagination-left">
 
					${c.pagination.pager('$link_previous ~2~ $link_next')}
 
				</div>
 
				</div>
 
			</div>
 

	
 
			<script type="text/javascript" src="${h.url('/js/graph.js')}"></script>
 
			<script type="text/javascript">
 
				YAHOO.util.Event.onDOMReady(function(){
 

	
 
                    //Monitor range checkboxes and build a link to changesets
 
                    //ranges
 
                    var checkboxes = YUD.getElementsByClassName('changeset_range');
 
                    var url_tmpl = "${h.url('changeset_home',repo_name=c.repo_name,revision='__REVRANGE__')}";
 
                    YUE.on(checkboxes,'click',function(e){
 
                        var checked_checkboxes = [];
 
                        for (pos in checkboxes){
 
                            if(checkboxes[pos].checked){
 
                                checked_checkboxes.push(checkboxes[pos]);
 
                            }
 
                        }
 
                        if(checked_checkboxes.length>1){
 
                        	var rev_end = checked_checkboxes[0].name;
 
                        	var rev_start = checked_checkboxes[checked_checkboxes.length-1].name;
 

	
 
                            var url = url_tmpl.replace('__REVRANGE__',
 
                            		rev_start+'...'+rev_end);
 

	
 
                        var link = "<a href="+url+">${_('Show selected changes __S -> __E')}</a>"
 
                        link = link.replace('__S',rev_start);
 
                        link = link.replace('__E',rev_end);
 
                        YUD.get('rev_range_container').innerHTML = link;
 
                        YUD.setStyle('rev_range_container','display','');
 
                        }
 
                        else{
 
                        	YUD.setStyle('rev_range_container','display','none');
 

	
 
                        }
 
                    });
 

	
 
                    var msgs = YUQ('.message');
 
                    // get first element height
 
                    var el = YUQ('#graph_content .container')[0];
 
                    var row_h = el.clientHeight;
 
                    for(var i=0;i<msgs.length;i++){
 
                    	var m = msgs[i];
 

	
 
                    	var h = m.clientHeight;
 
                    	var pad = YUD.getStyle(m,'padding');
 
                    	if(h > row_h){
 
                    		var offset = row_h - (h+12);
 
                    		YUD.setStyle(m.nextElementSibling,'display','block');
 
                    		YUD.setStyle(m.nextElementSibling,'margin-top',offset+'px');
 
                    	};
 
                    }
 
                    YUE.on(YUQ('.expand'),'click',function(e){
 
                    	var elem = e.currentTarget.parentNode.parentNode;
rhodecode/templates/pullrequests/pullrequest_show.html
Show inline comments
 
<%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>
 
    
 
    <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)}
 
    ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name, pull_request_id=c.pull_request.pull_request_id),
 
                       c.current_changeset_status)}
 
    
 
</div>
 

	
 
<script type="text/javascript">
 

	
 

	
 
</script>
 

	
 
</%def>
0 comments (0 inline, 0 general)