Changeset - 7ec5f9c1df45
[Not reviewed]
beta
0 4 0
Marcin Kuzminski - 13 years ago 2013-01-19 20:57:19
marcin@python-works.com
show comments from pull requests into associated changesets
4 files changed with 31 insertions and 12 deletions:
0 comments (0 inline, 0 general)
rhodecode/controllers/changeset.py
Show inline comments
 
@@ -32,359 +32,374 @@ from pylons import tmpl_context as c, ur
 
from pylons.i18n.translation import _
 
from pylons.controllers.util import redirect
 
from rhodecode.lib.utils import jsonify
 

	
 
from rhodecode.lib.vcs.exceptions import RepositoryError, \
 
    ChangesetDoesNotExistError
 

	
 
import rhodecode.lib.helpers as h
 
from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
 
from rhodecode.lib.base import BaseRepoController, render
 
from rhodecode.lib.utils import action_logger
 
from rhodecode.lib.compat import OrderedDict
 
from rhodecode.lib import diffs
 
from rhodecode.model.db import ChangesetComment, ChangesetStatus
 
from rhodecode.model.comment import ChangesetCommentsModel
 
from rhodecode.model.changeset_status import ChangesetStatusModel
 
from rhodecode.model.meta import Session
 
from rhodecode.model.repo import RepoModel
 
from rhodecode.lib.diffs import LimitedDiffContainer
 
from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
 
from rhodecode.lib.vcs.backends.base import EmptyChangeset
 
from rhodecode.lib.utils2 import safe_unicode
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
def _update_with_GET(params, GET):
 
    for k in ['diff1', 'diff2', 'diff']:
 
        params[k] += GET.getall(k)
 

	
 

	
 
def anchor_url(revision, path, GET):
 
    fid = h.FID(revision, path)
 
    return h.url.current(anchor=fid, **dict(GET))
 

	
 

	
 
def get_ignore_ws(fid, GET):
 
    ig_ws_global = GET.get('ignorews')
 
    ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
 
    if ig_ws:
 
        try:
 
            return int(ig_ws[0].split(':')[-1])
 
        except:
 
            pass
 
    return ig_ws_global
 

	
 

	
 
def _ignorews_url(GET, fileid=None):
 
    fileid = str(fileid) if fileid else None
 
    params = defaultdict(list)
 
    _update_with_GET(params, GET)
 
    lbl = _('show white space')
 
    ig_ws = get_ignore_ws(fileid, GET)
 
    ln_ctx = get_line_ctx(fileid, GET)
 
    # global option
 
    if fileid is None:
 
        if ig_ws is None:
 
            params['ignorews'] += [1]
 
            lbl = _('ignore white space')
 
        ctx_key = 'context'
 
        ctx_val = ln_ctx
 
    # per file options
 
    else:
 
        if ig_ws is None:
 
            params[fileid] += ['WS:1']
 
            lbl = _('ignore white space')
 

	
 
        ctx_key = fileid
 
        ctx_val = 'C:%s' % ln_ctx
 
    # if we have passed in ln_ctx pass it along to our params
 
    if ln_ctx:
 
        params[ctx_key] += [ctx_val]
 

	
 
    params['anchor'] = fileid
 
    img = h.image(h.url('/images/icons/text_strikethrough.png'), lbl, class_='icon')
 
    return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
 

	
 

	
 
def get_line_ctx(fid, GET):
 
    ln_ctx_global = GET.get('context')
 
    if fid:
 
        ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
 
    else:
 
        _ln_ctx = filter(lambda k: k.startswith('C'), GET)
 
        ln_ctx = GET.get(_ln_ctx[0]) if _ln_ctx  else ln_ctx_global
 
        if ln_ctx:
 
            ln_ctx = [ln_ctx]
 

	
 
    if ln_ctx:
 
        retval = ln_ctx[0].split(':')[-1]
 
    else:
 
        retval = ln_ctx_global
 

	
 
    try:
 
        return int(retval)
 
    except:
 
        return 3
 

	
 

	
 
def _context_url(GET, fileid=None):
 
    """
 
    Generates url for context lines
 

	
 
    :param fileid:
 
    """
 

	
 
    fileid = str(fileid) if fileid else None
 
    ig_ws = get_ignore_ws(fileid, GET)
 
    ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2
 

	
 
    params = defaultdict(list)
 
    _update_with_GET(params, GET)
 

	
 
    # global option
 
    if fileid is None:
 
        if ln_ctx > 0:
 
            params['context'] += [ln_ctx]
 

	
 
        if ig_ws:
 
            ig_ws_key = 'ignorews'
 
            ig_ws_val = 1
 

	
 
    # per file option
 
    else:
 
        params[fileid] += ['C:%s' % ln_ctx]
 
        ig_ws_key = fileid
 
        ig_ws_val = 'WS:%s' % 1
 

	
 
    if ig_ws:
 
        params[ig_ws_key] += [ig_ws_val]
 

	
 
    lbl = _('%s line context') % ln_ctx
 

	
 
    params['anchor'] = fileid
 
    img = h.image(h.url('/images/icons/table_add.png'), lbl, class_='icon')
 
    return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
 

	
 

	
 
class ChangesetController(BaseRepoController):
 

	
 
    @LoginRequired()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def __before__(self):
 
        super(ChangesetController, self).__before__()
 
        c.affected_files_cut_off = 60
 
        repo_model = RepoModel()
 
        c.users_array = repo_model.get_users_js()
 
        c.users_groups_array = repo_model.get_users_groups_js()
 

	
 
    def index(self, revision, method='show'):
 
        c.anchor_url = anchor_url
 
        c.ignorews_url = _ignorews_url
 
        c.context_url = _context_url
 
        c.fulldiff = fulldiff = request.GET.get('fulldiff')
 
        #get ranges of revisions if preset
 
        rev_range = revision.split('...')[:2]
 
        enable_comments = True
 
        try:
 
            if len(rev_range) == 2:
 
                enable_comments = False
 
                rev_start = rev_range[0]
 
                rev_end = rev_range[1]
 
                rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
 
                                                             end=rev_end)
 
            else:
 
                rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
 

	
 
            c.cs_ranges = list(rev_ranges)
 
            if not c.cs_ranges:
 
                raise RepositoryError('Changeset range returned empty result')
 

	
 
        except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
 
            log.error(traceback.format_exc())
 
            h.flash(str(e), category='warning')
 
            return redirect(url('home'))
 

	
 
        c.changes = OrderedDict()
 

	
 
        c.lines_added = 0  # count of lines added
 
        c.lines_deleted = 0  # count of lines removes
 

	
 
        c.changeset_statuses = ChangesetStatus.STATUSES
 
        c.comments = []
 
        c.statuses = []
 
        c.inline_comments = []
 
        c.inline_cnt = 0
 

	
 
        # Iterate over ranges (default changeset view is always one changeset)
 
        for changeset in c.cs_ranges:
 
            inlines = []
 
            if method == 'show':
 
                c.statuses.extend([ChangesetStatusModel()\
 
                                  .get_status(c.rhodecode_db_repo.repo_id,
 
                                              changeset.raw_id)])
 
                c.statuses.extend([ChangesetStatusModel().get_status(
 
                            c.rhodecode_db_repo.repo_id, changeset.raw_id)])
 

	
 
                c.comments.extend(ChangesetCommentsModel()\
 
                                  .get_comments(c.rhodecode_db_repo.repo_id,
 
                                                revision=changeset.raw_id))
 

	
 
                #comments from PR
 
                st = ChangesetStatusModel().get_statuses(
 
                            c.rhodecode_db_repo.repo_id, changeset.raw_id,
 
                            with_revisions=True)
 
                # from associated statuses, check the pull requests, and
 
                # show comments from them
 

	
 
                prs = set([x.pull_request for x in
 
                           filter(lambda x: x.pull_request != None, st)])
 

	
 
                for pr in prs:
 
                    c.comments.extend(pr.comments)
 
                inlines = ChangesetCommentsModel()\
 
                            .get_inline_comments(c.rhodecode_db_repo.repo_id,
 
                                                 revision=changeset.raw_id)
 
                c.inline_comments.extend(inlines)
 

	
 
            c.changes[changeset.raw_id] = []
 

	
 
            cs2 = changeset.raw_id
 
            cs1 = changeset.parents[0].raw_id if changeset.parents else EmptyChangeset()
 
            context_lcl = get_line_ctx('', request.GET)
 
            ign_whitespace_lcl = ign_whitespace_lcl = get_ignore_ws('', request.GET)
 

	
 
            _diff = c.rhodecode_repo.get_diff(cs1, cs2,
 
                ignore_whitespace=ign_whitespace_lcl, context=context_lcl)
 
            diff_limit = self.cut_off_limit if not fulldiff else None
 
            diff_processor = diffs.DiffProcessor(_diff,
 
                                                 vcs=c.rhodecode_repo.alias,
 
                                                 format='gitdiff',
 
                                                 diff_limit=diff_limit)
 
            cs_changes = OrderedDict()
 
            if method == 'show':
 
                _parsed = diff_processor.prepare()
 
                c.limited_diff = False
 
                if isinstance(_parsed, LimitedDiffContainer):
 
                    c.limited_diff = True
 
                for f in _parsed:
 
                    st = f['stats']
 
                    if st[0] != 'b':
 
                        c.lines_added += st[0]
 
                        c.lines_deleted += st[1]
 
                    fid = h.FID(changeset.raw_id, f['filename'])
 
                    diff = diff_processor.as_html(enable_comments=enable_comments,
 
                                                  parsed_lines=[f])
 
                    cs_changes[fid] = [cs1, cs2, f['operation'], f['filename'],
 
                                       diff, st]
 
            else:
 
                # downloads/raw we only need RAW diff nothing else
 
                diff = diff_processor.as_raw()
 
                cs_changes[''] = [None, None, None, None, diff, None]
 
            c.changes[changeset.raw_id] = cs_changes
 

	
 
        #sort comments by how they were generated
 
        c.comments = sorted(c.comments, key=lambda x: x.comment_id)
 

	
 
        # 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.parent_tmpl = ''.join(['# Parent  %s\n' % x.raw_id
 
                                     for x in c.changeset.parents])
 
        if method == 'download':
 
            response.content_type = 'text/plain'
 
            response.content_disposition = 'attachment; filename=%s.diff' \
 
                                            % revision[:12]
 
            return diff
 
        elif method == 'patch':
 
            response.content_type = 'text/plain'
 
            c.diff = safe_unicode(diff)
 
            return render('changeset/patch_changeset.html')
 
        elif method == 'raw':
 
            response.content_type = 'text/plain'
 
            return diff
 
        elif method == 'show':
 
            if len(c.cs_ranges) == 1:
 
                return render('changeset/changeset.html')
 
            else:
 
                return render('changeset/changeset_range.html')
 

	
 
    def changeset_raw(self, revision):
 
        return self.index(revision, method='raw')
 

	
 
    def changeset_patch(self, revision):
 
        return self.index(revision, method='patch')
 

	
 
    def changeset_download(self, revision):
 
        return self.index(revision, method='download')
 

	
 
    @jsonify
 
    def comment(self, repo_name, revision):
 
        status = request.POST.get('changeset_status')
 
        change_status = request.POST.get('change_changeset_status')
 
        text = request.POST.get('text')
 
        if status and change_status:
 
            text = text or (_('Status change -> %s')
 
                            % ChangesetStatus.get_status_lbl(status))
 

	
 
        comm = ChangesetCommentsModel().create(
 
            text=text,
 
            repo=c.rhodecode_db_repo.repo_id,
 
            user=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:
 
            # if latest status was from pull request and it's closed
 
            # disallow changing status !
 
            # dont_allow_on_closed_pull_request = True !
 

	
 
            try:
 
                ChangesetStatusModel().set_status(
 
                    c.rhodecode_db_repo.repo_id,
 
                    status,
 
                    c.rhodecode_user.user_id,
 
                    comm,
 
                    revision=revision,
 
                    dont_allow_on_closed_pull_request=True
 
                )
 
            except StatusChangeOnClosedPullRequestError:
 
                log.error(traceback.format_exc())
 
                msg = _('Changing status on a changeset associated with'
 
                msg = _('Changing status on a changeset associated with '
 
                        'a closed pull request is not allowed')
 
                h.flash(msg, category='warning')
 
                return redirect(h.url('changeset_home', repo_name=repo_name,
 
                                      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 = 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()
 

	
 
    @jsonify
 
    def changeset_info(self, repo_name, revision):
 
        if request.is_xhr:
 
            try:
 
                return c.rhodecode_repo.get_changeset(revision)
 
            except ChangesetDoesNotExistError, e:
 
                return EmptyChangeset(message=str(e))
 
        else:
 
            raise HTTPBadRequest()
rhodecode/controllers/pullrequests.py
Show inline comments
 
@@ -35,385 +35,384 @@ from pylons.controllers.util import abor
 
from pylons.i18n.translation import _
 

	
 
from rhodecode.lib.compat import json
 
from rhodecode.lib.base import BaseRepoController, render
 
from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
 
    NotAnonymous
 
from rhodecode.lib import helpers as h
 
from rhodecode.lib import diffs
 
from rhodecode.lib.utils import action_logger, jsonify
 
from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
 
from rhodecode.lib.vcs.backends.base import EmptyChangeset
 
from rhodecode.lib.diffs import LimitedDiffContainer
 
from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
 
    ChangesetComment
 
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
 
from rhodecode.model.forms import PullRequestForm
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class PullrequestsController(BaseRepoController):
 

	
 
    @LoginRequired()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def __before__(self):
 
        super(PullrequestsController, self).__before__()
 
        repo_model = RepoModel()
 
        c.users_array = repo_model.get_users_js()
 
        c.users_groups_array = repo_model.get_users_groups_js()
 

	
 
    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 _get_default_rev(self, repo):
 
        """
 
        Get's default revision to do compare on pull request
 

	
 
        :param repo:
 
        """
 
        repo = repo.scm_instance
 
        if 'default' in repo.branches:
 
            return 'default'
 
        else:
 
            #if repo doesn't have default branch return first found
 
            return repo.branches.keys()[0]
 

	
 
    def _get_is_allowed_change_status(self, pull_request):
 
        owner = self.rhodecode_user.user_id == pull_request.user_id
 
        reviewer = self.rhodecode_user.user_id in [x.user_id for x in
 
                                                   pull_request.reviewers]
 
        return (self.rhodecode_user.admin or owner or reviewer)
 

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

	
 
    @NotAnonymous()
 
    def index(self):
 
        org_repo = c.rhodecode_db_repo
 

	
 
        if org_repo.scm_instance.alias != 'hg':
 
            log.error('Review not available for GIT REPOS')
 
            raise HTTPNotFound
 

	
 
        try:
 
            org_repo.scm_instance.get_changeset()
 
        except EmptyRepositoryError, e:
 
            h.flash(h.literal(_('There are no changesets yet')),
 
                    category='warning')
 
            redirect(url('summary_home', repo_name=org_repo.repo_name))
 

	
 
        other_repos_info = {}
 

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

	
 
        # add org repo to other so we can open pull request agains itself
 
        c.other_repos.extend(c.org_repos)
 

	
 
        c.default_pull_request = org_repo.repo_name  # repo name pre-selected
 
        c.default_pull_request_rev = self._get_default_rev(org_repo)  # revision pre-selected
 
        c.default_revs = self._get_repo_refs(org_repo.scm_instance)
 
        #add orginal repo
 
        other_repos_info[org_repo.repo_name] = {
 
            'gravatar': h.gravatar_url(org_repo.user.email, 24),
 
            'description': org_repo.description,
 
            'revs': h.select('other_ref', '', c.default_revs, class_='refs')
 
        }
 

	
 
        #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))
 
                                 )
 
            other_repos_info[fork.repo_name] = {
 
                'gravatar': h.gravatar_url(fork.user.email, 24),
 
                'description': fork.description,
 
                'revs': h.select('other_ref', '',
 
                                 self._get_repo_refs(fork.scm_instance),
 
                                 class_='refs')
 
            }
 
        #add parents of this fork also, but only if it's not empty
 
        if org_repo.parent and org_repo.parent.scm_instance.revisions:
 
            c.default_pull_request = org_repo.parent.repo_name
 
            c.default_pull_request_rev = self._get_default_rev(org_repo.parent)
 
            c.default_revs = self._get_repo_refs(org_repo.parent.scm_instance)
 
            c.other_repos.append((org_repo.parent.repo_name, '%s/%s' % (
 
                                        org_repo.parent.user.username,
 
                                        org_repo.parent.repo_name))
 
                                     )
 
            other_repos_info[org_repo.parent.repo_name] = {
 
                'gravatar': h.gravatar_url(org_repo.parent.user.email, 24),
 
                'description': org_repo.parent.description,
 
                'revs': h.select('other_ref', '',
 
                                 self._get_repo_refs(org_repo.parent.scm_instance),
 
                                 class_='refs')
 
            }
 

	
 
        c.other_repos_info = json.dumps(other_repos_info)
 
        c.review_members = [org_repo.user]
 
        return render('/pullrequests/pullrequest.html')
 

	
 
    @NotAnonymous()
 
    def create(self, repo_name):
 
        repo = RepoModel()._get_repo(repo_name)
 
        try:
 
            _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
 
        except formencode.Invalid, errors:
 
            log.error(traceback.format_exc())
 
            if errors.error_dict.get('revisions'):
 
                msg = 'Revisions: %s' % errors.error_dict['revisions']
 
            elif errors.error_dict.get('pullrequest_title'):
 
                msg = _('Pull request requires a title with min. 3 chars')
 
            else:
 
                msg = _('error during creation of pull request')
 

	
 
            h.flash(msg, 'error')
 
            return redirect(url('pullrequest_home', repo_name=repo_name))
 

	
 
        org_repo = _form['org_repo']
 
        org_ref = _form['org_ref']
 
        other_repo = _form['other_repo']
 
        other_ref = _form['other_ref']
 
        revisions = _form['revisions']
 
        reviewers = _form['review_members']
 

	
 
        # if we have cherry picked pull request we don't care what is in
 
        # org_ref/other_ref
 
        rev_start = request.POST.get('rev_start')
 
        rev_end = request.POST.get('rev_end')
 

	
 
        if rev_start and rev_end:
 
            # this is swapped to simulate that rev_end is a revision from
 
            # parent of the fork
 
            org_ref = 'rev:%s:%s' % (rev_end, rev_end)
 
            other_ref = 'rev:%s:%s' % (rev_start, rev_start)
 

	
 
        title = _form['pullrequest_title']
 
        description = _form['pullrequest_desc']
 

	
 
        try:
 
            pull_request = PullRequestModel().create(
 
                self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
 
                other_ref, revisions, reviewers, title, description
 
            )
 
            Session().commit()
 
            h.flash(_('Successfully opened new pull request'),
 
                    category='success')
 
        except Exception:
 
            raise
 
            h.flash(_('Error occurred during sending pull request'),
 
                    category='error')
 
            log.error(traceback.format_exc())
 
            return redirect(url('pullrequest_home', repo_name=repo_name))
 

	
 
        return redirect(url('pullrequest_show', repo_name=other_repo,
 
                            pull_request_id=pull_request.pull_request_id))
 

	
 
    @NotAnonymous()
 
    @jsonify
 
    def update(self, repo_name, pull_request_id):
 
        pull_request = PullRequest.get_or_404(pull_request_id)
 
        if pull_request.is_closed():
 
            raise HTTPForbidden()
 
        #only owner or admin can update it
 
        owner = pull_request.author.user_id == c.rhodecode_user.user_id
 
        if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
 
            reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
 
                       request.POST.get('reviewers_ids', '').split(',')))
 

	
 
            PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
 
            Session().commit()
 
            return True
 
        raise HTTPForbidden()
 

	
 
    @NotAnonymous()
 
    @jsonify
 
    def delete(self, repo_name, pull_request_id):
 
        pull_request = PullRequest.get_or_404(pull_request_id)
 
        #only owner can delete it !
 
        if pull_request.author.user_id == c.rhodecode_user.user_id:
 
            PullRequestModel().delete(pull_request)
 
            Session().commit()
 
            h.flash(_('Successfully deleted pull request'),
 
                    category='success')
 
            return redirect(url('admin_settings_my_account', anchor='pullrequests'))
 
        raise HTTPForbidden()
 

	
 
    def _load_compare_data(self, pull_request, enable_comments=True):
 
        """
 
        Load context data needed for generating compare diff
 

	
 
        :param pull_request:
 
        :type pull_request:
 
        """
 
        rev_start = request.GET.get('rev_start')
 
        rev_end = request.GET.get('rev_end')
 

	
 
        org_repo = pull_request.org_repo
 
        (org_ref_type,
 
         org_ref_name,
 
         org_ref_rev) = pull_request.org_ref.split(':')
 

	
 
        other_repo = org_repo
 
        (other_ref_type,
 
         other_ref_name,
 
         other_ref_rev) = pull_request.other_ref.split(':')
 

	
 
        # despite opening revisions for bookmarks/branches/tags, we always
 
        # convert this to rev to prevent changes after book or branch change
 
        org_ref = ('rev', org_ref_rev)
 
        other_ref = ('rev', other_ref_rev)
 

	
 
        c.org_repo = org_repo
 
        c.other_repo = other_repo
 

	
 
        c.fulldiff = fulldiff = request.GET.get('fulldiff')
 

	
 
        c.cs_ranges = [org_repo.get_changeset(x) for x in pull_request.revisions]
 

	
 
        other_ref = ('rev', getattr(c.cs_ranges[0].parents[0]
 
                                  if c.cs_ranges[0].parents
 
                                  else EmptyChangeset(), 'raw_id'))
 

	
 
        c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
 
        c.target_repo = other_repo.repo_name
 
        # 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_limit = self.cut_off_limit if not fulldiff else None
 

	
 
        #we swap org/other ref since we run a simple diff on one repo
 
        _diff = diffs.differ(org_repo, other_ref, other_repo, org_ref)
 

	
 
        diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
 
                                             diff_limit=diff_limit)
 
        _parsed = diff_processor.prepare()
 

	
 
        c.limited_diff = False
 
        if isinstance(_parsed, LimitedDiffContainer):
 
            c.limited_diff = True
 

	
 
        c.files = []
 
        c.changes = {}
 
        c.lines_added = 0
 
        c.lines_deleted = 0
 
        for f in _parsed:
 
            st = f['stats']
 
            if st[0] != 'b':
 
                c.lines_added += st[0]
 
                c.lines_deleted += st[1]
 
            fid = h.FID('', f['filename'])
 
            c.files.append([fid, f['operation'], f['filename'], f['stats']])
 
            diff = diff_processor.as_html(enable_comments=enable_comments,
 
                                          parsed_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_or_404(pull_request_id)
 
        c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request)
 
        cc_model = ChangesetCommentsModel()
 
        cs_model = ChangesetStatusModel()
 
        _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
 
                                            pull_request=c.pull_request,
 
                                            with_revisions=True)
 

	
 
        cs_statuses = defaultdict(list)
 
        for st in _cs_statuses:
 
            cs_statuses[st.author.username] += [st]
 

	
 
        c.pull_request_reviewers = []
 
        c.pull_request_pending_reviewers = []
 
        for o in c.pull_request.reviewers:
 
            st = cs_statuses.get(o.user.username, None)
 
            if st:
 
                sorter = lambda k: k.version
 
                st = [(x, list(y)[0])
 
                      for x, y in (groupby(sorted(st, key=sorter), sorter))]
 
            else:
 
                c.pull_request_pending_reviewers.append(o.user)
 
            c.pull_request_reviewers.append([o.user, st])
 

	
 
        # pull_requests repo_name we opened it against
 
        # ie. other_repo must match
 
        if repo_name != c.pull_request.other_repo.repo_name:
 
            raise HTTPNotFound
 

	
 
        # load compare data into template context
 
        enable_comments = not c.pull_request.is_closed()
 
        self._load_compare_data(c.pull_request, enable_comments=enable_comments)
 

	
 
        # inline comments
 
        c.inline_cnt = 0
 
        c.inline_comments = cc_model.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 = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
 
                                           pull_request=pull_request_id)
 

	
 
        try:
 
            cur_status = c.statuses[c.pull_request.revisions[0]][0]
 
        except:
 
            log.error(traceback.format_exc())
 
            cur_status = 'undefined'
 
        if c.pull_request.is_closed() and 0:
 
            c.current_changeset_status = cur_status
 
        else:
 
            # changeset(pull-request) status calulation based on reviewers
 
            c.current_changeset_status = cs_model.calculate_status(
 
                                            c.pull_request_reviewers,
 
                                         )
 
        c.changeset_statuses = ChangesetStatus.STATUSES
 

	
 
        return render('/pullrequests/pullrequest_show.html')
 

	
 
    @NotAnonymous()
 
    @jsonify
 
    def comment(self, repo_name, pull_request_id):
 
        pull_request = PullRequest.get_or_404(pull_request_id)
 
        if pull_request.is_closed():
 
            raise HTTPForbidden()
 

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

	
 
        allowed_to_change_status = self._get_is_allowed_change_status(pull_request)
 
        if status and change_status and allowed_to_change_status:
 
            text = text or (_('Status change -> %s')
 
                            % ChangesetStatus.get_status_lbl(status))
 
        comm = ChangesetCommentsModel().create(
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 collections import  defaultdict
 

	
 
from rhodecode.model import BaseModel
 
from rhodecode.model.db import ChangesetStatus, PullRequest
 
from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class ChangesetStatusModel(BaseModel):
 

	
 
    cls = ChangesetStatus
 

	
 
    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_query(self, repo, revision, pull_request,
 
                          with_revisions=False):
 
        repo = self._get_repo(repo)
 

	
 
        q = ChangesetStatus.query()\
 
            .filter(ChangesetStatus.repo == repo)
 
        if not with_revisions:
 
            q = q.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')
 
        q.order_by(ChangesetStatus.version.asc())
 
        return q
 

	
 
    def calculate_status(self, statuses_by_reviewers):
 
        """
 
        leading one wins, if number of occurrences are equal than weaker wins
 

	
 
        :param statuses_by_reviewers:
 
        """
 
        status = None
 
        votes = defaultdict(int)
 
        reviewers_number = len(statuses_by_reviewers)
 
        for user, statuses in statuses_by_reviewers:
 
            if statuses:
 
                ver, latest = statuses[0]
 
                votes[latest.status] += 1
 
            else:
 
                votes[ChangesetStatus.DEFAULT] += 1
 

	
 
        if votes.get(ChangesetStatus.STATUS_APPROVED) == reviewers_number:
 
            return ChangesetStatus.STATUS_APPROVED
 
        else:
 
            return ChangesetStatus.STATUS_UNDER_REVIEW
 

	
 
    def get_statuses(self, repo, revision=None, pull_request=None,
 
                     with_revisions=False):
 
        q = self._get_status_query(repo, revision, pull_request,
 
                                   with_revisions)
 
        return q.all()
 

	
 
    def get_status(self, repo, revision=None, pull_request=None):
 
    def get_status(self, repo, revision=None, pull_request=None, as_str=True):
 
        """
 
        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:
 
        :param as_str: return status as string not object
 
        """
 
        q = self._get_status_query(repo, revision, pull_request)
 

	
 
        # 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)
 
        if as_str:
 
            status = status.status if status else status
 
            st = status or ChangesetStatus.DEFAULT
 
            return str(st)
 
        return status
 

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

	
 
        :param repo:
 
        :param revision:
 
        :param status:
 
        :param user:
 
        :param comment:
 
        :param dont_allow_on_closed_pull_request: don't allow a status change
 
            if last status was for pull request and it's closed. We shouldn't
 
            mess around this manually
 
        """
 
        repo = self._get_repo(repo)
 

	
 
        q = ChangesetStatus.query()
 
        if not comment:
 
            from rhodecode.model.comment import ChangesetCommentsModel
 
            comment = ChangesetCommentsModel().create(
 
                text='Auto status change',
 
                repo=repo,
 
                user=user,
 
                pull_request=pull_request,
 
            )
 
        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.revision.in_(pull_request.revisions))
 
        cur_statuses = q.all()
 

	
 
        #if statuses exists and last is associated with a closed pull request
 
        # we need to check if we can allow this status change
 
        if (dont_allow_on_closed_pull_request and cur_statuses
 
            and getattr(cur_statuses[0].pull_request, 'status', '')
 
                == PullRequest.STATUS_CLOSED):
 
            raise StatusChangeOnClosedPullRequestError(
 
                'Changing status on closed pull request is not allowed'
 
            )
 

	
 
        #update all current statuses with older version
 
        if cur_statuses:
 
            for st in cur_statuses:
 
                st.version += 1
 
                self.sa.add(st)
 

	
 
        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/templates/changeset/changeset_file_comment.html
Show inline comments
 
## -*- coding: utf-8 -*-
 
## usage:
 
## <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
 
## ${comment.comment_block(co)}
 
##
 
<%def name="comment_block(co)">
 
  <div class="comment" id="comment-${co.comment_id}" line="${co.line_no}">
 
    <div class="comment-wrapp">
 
  	<div class="meta">
 
        <div style="float:left"> <img src="${h.gravatar_url(co.author.email, 20)}" /> </div>
 
  		<div class="user">
 
  			${co.author.username}
 
  		</div>
 
  		<div class="date">
 
  			${h.age(co.modified_at)} <a class="permalink" href="#comment-${co.comment_id}">&para;</a>
 
  		</div>
 
        %if co.status_change:
 
           <div  style="float:left" class="changeset-status-container">
 
             <div style="float:left;padding:0px 2px 0px 2px"><span style="font-size: 18px;">&rsaquo;</span></div>
 
             <div title="${_('Changeset status')}" class="changeset-status-lbl"> ${co.status_change[0].status_lbl}</div>
 
             <div class="changeset-status-ico"><img src="${h.url(str('/images/icons/flag_status_%s.png' % co.status_change[0].status))}" /></div>
 
             <div style="float:left;padding:3px 0px 0px 5px"> <span class="">
 
             %if co.pull_request:
 
                <a href="${h.url('pullrequest_show',repo_name=co.pull_request.other_repo.repo_name,pull_request_id=co.pull_request.pull_request_id)}">${_('Status from pull request %s') % co.pull_request.pull_request_id}</a>   
 
             %endif
 
             </span> </div>
 
           </div>
 
        %endif
 
      %if h.HasPermissionAny('hg.admin', 'repository.admin')() or co.author.user_id == c.rhodecode_user.user_id:
 
        <div class="buttons">
 
          <span onClick="deleteComment(${co.comment_id})" class="delete-comment ui-btn">${_('Delete')}</span>
 
        </div>
 
      %endif
 
  	</div>
 
  	<div class="text">
 
  		${h.rst_w_mentions(co.text)|n}
 
  	</div>
 
    </div>
 
  </div>
 
</%def>
 

	
 

	
 
<%def name="comment_inline_form()">
 
<div id='comment-inline-form-template' style="display:none">
 
  <div class="comment-inline-form ac">
 
  %if c.rhodecode_user.username != 'default':
 
    <div class="overlay"><div class="overlay-text">${_('Submitting...')}</div></div>
 
      ${h.form('#', class_='inline-form')}
 
      <div class="clearfix">
 
          <div class="comment-help">${_('Commenting on line {1}.')}
 
          ${(_('Comments parsed using %s syntax with %s support.') % (
 
                 ('<a href="%s">RST</a>' % h.url('rst_help')),
 
          	     ('<span style="color:#003367" class="tooltip" title="%s">@mention</span>' % _('Use @username inside this text to send notification to this RhodeCode user'))
 
               )
 
            )|n
 
           }
 
          </div>
 
            <div class="mentions-container" id="mentions_container_{1}"></div>
 
            <textarea id="text_{1}" name="text" class="yui-ac-input"></textarea>
 
      </div>
 
      <div class="comment-button">
 
      <input type="hidden" name="f_path" value="{0}">
 
      <input type="hidden" name="line" value="{1}">
 
      ${h.submit('save', _('Comment'), class_='ui-btn save-inline-form')}
 
      ${h.reset('hide-inline-form', _('Hide'), class_='ui-btn hide-inline-form')}
 
      </div>
 
      ${h.end_form()}
 
  %else:
 
      ${h.form('')}
 
      <div class="clearfix">
 
          <div class="comment-help">
 
            ${_('You need to be logged in to comment.')} <a href="${h.url('login_home',came_from=h.url.current())}">${_('Login now')}</a>
 
          </div>
 
      </div>
 
      <div class="comment-button">
 
      ${h.reset('hide-inline-form', _('Hide'), class_='ui-btn hide-inline-form')}
 
      </div>
 
      ${h.end_form()}
 
  %endif
 
  </div>
 
</div>
 
</%def>
 

	
 

	
 
## generates inlines taken from c.comments var
 
<%def name="inlines()">
 
    <div class="comments-number">${ungettext("%d comment", "%d comments", len(c.comments)) % len(c.comments)} ${ungettext("(%d inline)", "(%d inline)", c.inline_cnt) % c.inline_cnt}</div>
 
    %for path, lines in c.inline_comments:
 
        % for line,comments in lines.iteritems():
 
            <div style="display:none" class="inline-comment-placeholder" path="${path}" target_id="${h.safeid(h.safe_unicode(path))}">
 
            %for co in comments:
 
                ${comment_block(co)}
 
            %endfor
 
            </div>
 
        %endfor
 
    %endfor
 

	
 
</%def>
 

	
 
## generate inline comments and the main ones
 
<%def name="generate_comments()">
 
<div class="comments">
 
    <div id="inline-comments-container">
 
    ## generate inlines for this changeset
 
     ${inlines()}
 
    </div>
 

	
 
    %for co in c.comments:
 
        <div id="comment-tr-${co.comment_id}">
 
          ${comment_block(co)}
 
        </div>
 
    %endfor
 
</div>
 
</%def>
 

	
 
## MAIN COMMENT FORM
 
<%def name="comments(post_url, cur_status, close_btn=False, change_status=True)">
 

	
 
<div class="comments">
 
    %if c.rhodecode_user.username != 'default':
 
    <div class="comment-form ac">
 
        ${h.form(post_url)}
 
        <strong>${_('Leave a comment')}</strong>
 
        <div class="clearfix">
 
            <div class="comment-help">
 
                ${(_('Comments parsed using %s syntax with %s support.') % (('<a href="%s">RST</a>' % h.url('rst_help')),
 
          		'<span style="color:#003367" class="tooltip" title="%s">@mention</span>' %
 
          		_('Use @username inside this text to send notification to this RhodeCode user')))|n}
 
              %if change_status:
 
                | <label for="show_changeset_status_box" class="tooltip" title="${_('Check this to change current status of code-review for this changeset')}"> ${_('change status')}</label>
 
                  <input style="vertical-align: bottom;margin-bottom:-2px" id="show_changeset_status_box" type="checkbox" name="change_changeset_status" />
 
              %endif
 
            </div>
 
            %if change_status:
 
            <div id="status_block_container" class="status-block" style="display:none">
 
                %for status,lbl in c.changeset_statuses:
 
                    <div class="">
 
                        <img src="${h.url('/images/icons/flag_status_%s.png' % status)}" /> <input ${'checked="checked"' if status == cur_status else ''}" type="radio" class="status_change_radio" name="changeset_status" id="${status}" value="${status}">
 
                        <label for="${status}">${lbl}</label>
 
                    </div>
 
                %endfor
 
            </div>
 
            %endif
 
            <div class="mentions-container" id="mentions_container"></div>
 
             ${h.textarea('text')}
 
        </div>
 
        <div class="comment-button">
 
        ${h.submit('save', _('Comment'), class_="ui-btn large")}
 
        %if close_btn and change_status:
 
           ${h.submit('save_close', _('Comment and close'), class_='ui-btn blue large %s' % ('hidden' if cur_status in ['not_reviewed','under_review'] else ''))}
 
        %endif
 
        </div>
 
        ${h.end_form()}
 
    </div>
 
    %endif
 
</div>
 
<script>
 
YUE.onDOMReady(function () {
 
   MentionsAutoComplete('text', 'mentions_container', _USERS_AC_DATA, _GROUPS_AC_DATA);
 

	
 
   // changeset status box listener
 
   YUE.on(YUD.get('show_changeset_status_box'),'change',function(e){
 
       if(e.currentTarget.checked){
 
           YUD.setStyle('status_block_container','display','');
 
       }
 
       else{
 
           YUD.setStyle('status_block_container','display','none');
 
       }
 
   })
 
   YUE.on(YUQ('.status_change_radio'), 'change',function(e){
 
	   var val = e.currentTarget.value;
 
	   if (val == 'approved' || val == 'rejected') {
 
		   YUD.removeClass('save_close', 'hidden');
 
	   }else{
 
		   YUD.addClass('save_close', 'hidden');
 
	   }
 
   })
 

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