Ejemplo n.º 1
0
    def test_create_delete_general_comment(self):
        with test_context(self.app):
            repo_id = Repository.get_by_repo_name(base.HG_REPO).repo_id
            revision = '9a7b4ff9e8b40bbda72fc75f162325b9baa45cda'

            self._check_comment_count(repo_id,
                                      revision,
                                      expected_len_comments=0,
                                      expected_len_inline_comments=0)

            text = 'a comment'
            new_comment = ChangesetCommentsModel().create(
                text=text,
                repo=base.HG_REPO,
                author=base.TEST_USER_REGULAR_LOGIN,
                revision=revision,
                send_email=False)

            self._check_comment_count(repo_id,
                                      revision,
                                      expected_len_comments=1,
                                      expected_len_inline_comments=0)

            ChangesetCommentsModel().delete(new_comment)

            self._check_comment_count(repo_id,
                                      revision,
                                      expected_len_comments=0,
                                      expected_len_inline_comments=0)
Ejemplo n.º 2
0
    def _check_comment_count(self,
                             repo_id,
                             revision,
                             expected_len_comments,
                             expected_len_inline_comments,
                             f_path=None,
                             line_no=None):
        comments = ChangesetCommentsModel().get_comments(repo_id,
                                                         revision=revision)
        assert len(comments) == expected_len_comments
        inline_comments = ChangesetCommentsModel().get_inline_comments(
            repo_id, revision=revision, f_path=f_path, line_no=line_no)
        assert len(inline_comments) == expected_len_inline_comments

        return comments, inline_comments
Ejemplo n.º 3
0
    def comment(self, repo_name, revision):
        status = request.POST.get('changeset_status')
        text = request.POST.get('text', '').strip() or _('No comments.')

        c.co = comm = ChangesetCommentsModel().create(
            text=text,
            repo=c.db_repo.repo_id,
            user=c.authuser.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 else None))

        # get status if set !
        if 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.db_repo.repo_id,
                    status,
                    c.authuser.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 '
                        '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.authuser, 'user_commented_revision:%s' % revision,
                      c.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))
        #only ajax below
        data = {
            'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
        }
        if comm:
            data.update(comm.get_dict())
            data.update({
                'rendered_text':
                render('changeset/changeset_comment_block.html')
            })

        return data
Ejemplo n.º 4
0
 def delete_comment(self, repo_name, comment_id):
     co = ChangesetComment.get_or_404(comment_id)
     if co.repo.repo_name != repo_name:
         raise HTTPNotFound()
     owner = co.author_id == request.authuser.user_id
     repo_admin = h.HasRepoPermissionLevel('admin')(repo_name)
     if h.HasPermissionAny('hg.admin')() or repo_admin or owner:
         ChangesetCommentsModel().delete(comment=co)
         Session().commit()
         return True
     else:
         raise HTTPForbidden()
Ejemplo n.º 5
0
 def delete_comment(self, repo_name, comment_id):
     co = ChangesetComment.get(comment_id)
     if not co:
         raise HTTPBadRequest()
     owner = co.author.user_id == c.authuser.user_id
     repo_admin = h.HasRepoPermissionAny('repository.admin')
     if h.HasPermissionAny('hg.admin')() or repo_admin or owner:
         ChangesetCommentsModel().delete(comment=co)
         Session().commit()
         return True
     else:
         raise HTTPForbidden()
Ejemplo n.º 6
0
    def test_create_delete_inline_comment(self):
        with test_context(self.app):
            repo_id = Repository.get_by_repo_name(base.HG_REPO).repo_id
            revision = '9a7b4ff9e8b40bbda72fc75f162325b9baa45cda'

            self._check_comment_count(repo_id,
                                      revision,
                                      expected_len_comments=0,
                                      expected_len_inline_comments=0)

            text = 'an inline comment'
            f_path = 'vcs/tests/base.py'
            line_no = 'n50'
            new_comment = ChangesetCommentsModel().create(
                text=text,
                repo=base.HG_REPO,
                author=base.TEST_USER_REGULAR_LOGIN,
                revision=revision,
                f_path=f_path,
                line_no=line_no,
                send_email=False)

            comments, inline_comments = self._check_comment_count(
                repo_id,
                revision,
                expected_len_comments=0,
                expected_len_inline_comments=1)
            # inline_comments is a list of tuples (file_path, dict)
            # where the dict keys are line numbers and values are lists of comments
            assert inline_comments[0][0] == f_path
            assert len(inline_comments[0][1]) == 1
            assert line_no in inline_comments[0][1]
            assert inline_comments[0][1][line_no][0].text == text

            ChangesetCommentsModel().delete(new_comment)

            self._check_comment_count(repo_id,
                                      revision,
                                      expected_len_comments=0,
                                      expected_len_inline_comments=0)
Ejemplo n.º 7
0
    def execute(self):
        pull_request = self.create_action.execute()

        # Close old iteration
        from kallithea.model.comment import ChangesetCommentsModel
        ChangesetCommentsModel().create(
            text=_('Closed, next iteration: %s .') % pull_request.url(canonical=True),
            repo=self.old_pull_request.other_repo_id,
            author=request.authuser.user_id,
            pull_request=self.old_pull_request.pull_request_id,
            closing_pr=True)
        PullRequestModel().close_pull_request(self.old_pull_request.pull_request_id)
        return pull_request
Ejemplo n.º 8
0
    def delete_comment(self, repo_name, comment_id):
        co = ChangesetComment.get(comment_id)
        if co.pull_request.is_closed():
            #don't allow deleting comments on closed pull request
            raise HTTPForbidden()

        owner = co.author_id == request.authuser.user_id
        repo_admin = h.HasRepoPermissionLevel('admin')(c.repo_name)
        if h.HasPermissionAny('hg.admin')() or repo_admin or owner:
            ChangesetCommentsModel().delete(comment=co)
            Session().commit()
            return True
        else:
            raise HTTPForbidden()
Ejemplo n.º 9
0
def delete_cs_pr_comment(repo_name, comment_id):
    """Delete a comment from a changeset or pull request"""
    co = ChangesetComment.get_or_404(comment_id)
    if co.repo.repo_name != repo_name:
        raise HTTPNotFound()
    if co.pull_request and co.pull_request.is_closed():
        # don't allow deleting comments on closed pull request
        raise HTTPForbidden()

    owner = co.author_id == request.authuser.user_id
    repo_admin = h.HasRepoPermissionLevel('admin')(repo_name)
    if h.HasPermissionAny('hg.admin')() or repo_admin or owner:
        ChangesetCommentsModel().delete(comment=co)
        Session().commit()
        return True
    else:
        raise HTTPForbidden()
Ejemplo n.º 10
0
 def review_changeset(self,
                      repo,
                      revision,
                      status,
                      author=TEST_USER_ADMIN_LOGIN):
     comment = ChangesetCommentsModel().create("review comment",
                                               repo,
                                               author,
                                               revision=revision,
                                               send_email=False)
     csm = ChangesetStatusModel().set_status(
         repo,
         ChangesetStatus.STATUS_APPROVED,
         author,
         comment,
         revision=revision)
     Session().commit()
     return csm
Ejemplo n.º 11
0
def create_comment(text, status, f_path, line_no, revision=None, pull_request_id=None, closing_pr=None):
    """Comment functionality shared between changesets and pullrequests"""
    f_path = f_path or None
    line_no = line_no or None

    comment = ChangesetCommentsModel().create(
        text=text,
        repo=c.db_repo.repo_id,
        author=request.authuser.user_id,
        revision=revision,
        pull_request=pull_request_id,
        f_path=f_path,
        line_no=line_no,
        status_change=ChangesetStatus.get_status_lbl(status) if status else None,
        closing_pr=closing_pr,
    )

    return comment
Ejemplo n.º 12
0
    def create(self, created_by, org_repo, org_ref, other_repo, other_ref,
               revisions, reviewers, title, description=None):
        from kallithea.model.changeset_status import ChangesetStatusModel

        created_by_user = self._get_user(created_by)
        org_repo = self._get_repo(org_repo)
        other_repo = self._get_repo(other_repo)

        new = PullRequest()
        new.org_repo = org_repo
        new.org_ref = org_ref
        new.other_repo = other_repo
        new.other_ref = other_ref
        new.revisions = revisions
        new.title = title
        new.description = description
        new.author = created_by_user
        Session().add(new)
        Session().flush()

        #reset state to under-review
        from kallithea.model.comment import ChangesetCommentsModel
        comment = ChangesetCommentsModel().create(
            text=u'Auto status change to %s' % (ChangesetStatus.get_status_lbl(ChangesetStatus.STATUS_UNDER_REVIEW)),
            repo=org_repo,
            user=new.author,
            pull_request=new,
            send_email=False
        )
        ChangesetStatusModel().set_status(
            org_repo,
            ChangesetStatus.STATUS_UNDER_REVIEW,
            new.author,
            comment,
            pull_request=new
        )

        mention_recipients = set(User.get_by_username(username, case_insensitive=True)
                                 for username in extract_mentioned_users(new.description))
        self.__add_reviewers(new, reviewers, mention_recipients)

        return new
Ejemplo n.º 13
0
    def execute(self):
        created_by = User.get(request.authuser.user_id)

        pr = PullRequest()
        pr.org_repo = self.org_repo
        pr.org_ref = self.org_ref
        pr.other_repo = self.other_repo
        pr.other_ref = self.other_ref
        pr.revisions = self.revisions
        pr.title = self.title
        pr.description = self.description
        pr.owner = self.owner
        Session().add(pr)
        Session().flush() # make database assign pull_request_id

        if self.org_repo.scm_instance.alias == 'git':
            # create a ref under refs/pull/ so that commits don't get garbage-collected
            self.org_repo.scm_instance._repo["refs/pull/%d/head" % pr.pull_request_id] = safe_str(self.org_rev)

        #reset state to under-review
        from kallithea.model.changeset_status import ChangesetStatusModel
        from kallithea.model.comment import ChangesetCommentsModel
        comment = ChangesetCommentsModel().create(
            text=u'',
            repo=self.org_repo,
            author=created_by,
            pull_request=pr,
            send_email=False,
            status_change=ChangesetStatus.STATUS_UNDER_REVIEW,
        )
        ChangesetStatusModel().set_status(
            self.org_repo,
            ChangesetStatus.STATUS_UNDER_REVIEW,
            created_by,
            comment,
            pull_request=pr,
        )

        mention_recipients = extract_mentioned_users(self.description)
        PullRequestModel().add_reviewers(created_by, pr, self.reviewers, mention_recipients)

        return pr
Ejemplo n.º 14
0
    def create_update(self, old_pull_request, updaterev, title, description,
                      reviewers_ids):
        org_repo = RepoModel()._get_repo(old_pull_request.org_repo.repo_name)
        org_ref_type, org_ref_name, org_rev = old_pull_request.org_ref.split(
            ':')
        new_org_rev = self._get_ref_rev(org_repo, 'rev', updaterev)

        other_repo = RepoModel()._get_repo(
            old_pull_request.other_repo.repo_name)
        other_ref_type, other_ref_name, other_rev = old_pull_request.other_ref.split(
            ':')  # other_rev is ancestor
        #assert other_ref_type == 'branch', other_ref_type # TODO: what if not?
        new_other_rev = self._get_ref_rev(other_repo, other_ref_type,
                                          other_ref_name)

        cs_ranges, _cs_ranges_not, ancestor_rev = CompareController._get_changesets(
            org_repo.scm_instance.alias,
            other_repo.scm_instance,
            new_other_rev,  # org and other "swapped"
            org_repo.scm_instance,
            new_org_rev)

        old_revisions = set(old_pull_request.revisions)
        revisions = [cs.raw_id for cs in cs_ranges]
        new_revisions = [r for r in revisions if r not in old_revisions]
        lost = old_revisions.difference(revisions)

        infos = [
            'This is an update of %s "%s".' %
            (h.canonical_url('pullrequest_show',
                             repo_name=old_pull_request.other_repo.repo_name,
                             pull_request_id=old_pull_request.pull_request_id),
             old_pull_request.title)
        ]

        if lost:
            infos.append(
                _('Missing changesets since the previous pull request:'))
            for r in old_pull_request.revisions:
                if r in lost:
                    rev_desc = org_repo.get_changeset(r).message.split('\n')[0]
                    infos.append('  %s "%s"' % (h.short_id(r), rev_desc))

        if new_revisions:
            infos.append(
                _('New changesets on %s %s since the previous pull request:') %
                (org_ref_type, org_ref_name))
            for r in reversed(revisions):
                if r in new_revisions:
                    rev_desc = org_repo.get_changeset(r).message.split('\n')[0]
                    infos.append('  %s %s' %
                                 (h.short_id(r), h.shorter(rev_desc, 80)))

            if ancestor_rev == other_rev:
                infos.append(
                    _("Ancestor didn't change - show diff since previous version:"
                      ))
                infos.append(
                    h.canonical_url(
                        'compare_url',
                        repo_name=org_repo.
                        repo_name,  # other_repo is always same as repo_name
                        org_ref_type='rev',
                        org_ref_name=h.short_id(
                            org_rev),  # use old org_rev as base
                        other_ref_type='rev',
                        other_ref_name=h.short_id(new_org_rev),
                    ))  # note: linear diff, merge or not doesn't matter
            else:
                infos.append(
                    _('This pull request is based on another %s revision and there is no simple diff.'
                      ) % other_ref_name)
        else:
            infos.append(
                _('No changes found on %s %s since previous version.') %
                (org_ref_type, org_ref_name))
            # TODO: fail?

        # hack: ancestor_rev is not an other_ref but we want to show the
        # requested destination and have the exact ancestor
        new_other_ref = '%s:%s:%s' % (other_ref_type, other_ref_name,
                                      ancestor_rev)
        new_org_ref = '%s:%s:%s' % (org_ref_type, org_ref_name, new_org_rev)

        try:
            title, old_v = re.match(r'(.*)\(v(\d+)\)\s*$', title).groups()
            v = int(old_v) + 1
        except (AttributeError, ValueError):
            v = 2
        title = '%s (v%s)' % (title.strip(), v)

        # using a mail-like separator, insert new update info at the top of the list
        descriptions = description.replace('\r\n', '\n').split('\n-- \n', 1)
        description = descriptions[0].strip() + '\n\n-- \n' + '\n'.join(infos)
        if len(descriptions) > 1:
            description += '\n\n' + descriptions[1].strip()

        try:
            pull_request = PullRequestModel().create(
                self.authuser.user_id, old_pull_request.org_repo.repo_name,
                new_org_ref, old_pull_request.other_repo.repo_name,
                new_other_ref, revisions, reviewers_ids, title, description)
        except Exception:
            h.flash(_('Error occurred while creating pull request'),
                    category='error')
            log.error(traceback.format_exc())
            return redirect(old_pull_request.url())

        ChangesetCommentsModel().create(
            text=_('Closed, replaced by %s .') %
            pull_request.url(canonical=True),
            repo=old_pull_request.other_repo.repo_id,
            user=c.authuser.user_id,
            pull_request=old_pull_request.pull_request_id,
            closing_pr=True)
        PullRequestModel().close_pull_request(old_pull_request.pull_request_id)

        Session().commit()
        h.flash(_('Pull request update created'), category='success')

        return redirect(pull_request.url())
Ejemplo n.º 15
0
class ChangesetController(BaseRepoController):
    def __before__(self):
        super(ChangesetController, self).__before__()
        c.affected_files_cut_off = 60

    def __load_data(self):
        repo_model = RepoModel()
        c.users_array = repo_model.get_users_js()
        c.user_groups_array = repo_model.get_user_groups_js()

    def _index(self, revision, method):
        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
        c.cs_repo = c.db_repo
        try:
            if len(rev_range) == 2:
                enable_comments = False
                rev_start = rev_range[0]
                rev_end = rev_range[1]
                rev_ranges = c.db_repo_scm_instance.get_changesets(
                    start=rev_start, end=rev_end)
            else:
                rev_ranges = [c.db_repo_scm_instance.get_changeset(revision)]

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

        except (ChangesetDoesNotExistError, ), e:
            log.error(traceback.format_exc())
            msg = _('Such revision does not exist for this repository')
            h.flash(msg, category='error')
            raise HTTPNotFound()

        c.changes = OrderedDict()

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

        c.changeset_statuses = ChangesetStatus.STATUSES
        comments = dict()
        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.db_repo.repo_id,
                                                      changeset.raw_id)
                ])

                # Changeset comments
                comments.update(
                    (com.comment_id, com)
                    for com in ChangesetCommentsModel().get_comments(
                        c.db_repo.repo_id, revision=changeset.raw_id))

                # Status change comments - mostly from pull requests
                comments.update((
                    st.changeset_comment_id, st.comment
                ) for st in ChangesetStatusModel().get_statuses(
                    c.db_repo.repo_id, changeset.raw_id, with_revisions=True))

                inlines = ChangesetCommentsModel()\
                            .get_inline_comments(c.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().raw_id
            context_lcl = get_line_ctx('', request.GET)
            ign_whitespace_lcl = ign_whitespace_lcl = get_ignore_ws(
                '', request.GET)

            _diff = c.db_repo_scm_instance.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.db_repo_scm_instance.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']
                    c.lines_added += st['added']
                    c.lines_deleted += st['deleted']
                    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 in creation order
        c.comments = [com for com_id, com in sorted(comments.items())]

        # 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':
            self.__load_data()
            if len(c.cs_ranges) == 1:
                return render('changeset/changeset.html')
            else:
                c.cs_ranges_org = None
                c.cs_comments = {}
                revs = [ctx.revision for ctx in reversed(c.cs_ranges)]
                c.jsdata = json.dumps(graph_data(c.db_repo_scm_instance, revs))
                return render('changeset/changeset_range.html')
Ejemplo n.º 16
0
    def comment(self, repo_name, pull_request_id):
        pull_request = PullRequest.get_or_404(pull_request_id)

        status = 0
        close_pr = False
        allowed_to_change_status = self._get_is_allowed_change_status(pull_request)
        if allowed_to_change_status:
            status = request.POST.get('changeset_status')
            close_pr = request.POST.get('save_close')
        text = request.POST.get('text', '').strip() or _('No comments.')
        if close_pr:
            text = _('Closing.') + '\n' + text

        comm = ChangesetCommentsModel().create(
            text=text,
            repo=c.db_repo.repo_id,
            user=c.authuser.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 allowed_to_change_status else None),
            closing_pr=close_pr
        )

        action_logger(self.authuser,
                      'user_commented_pull_request:%s' % pull_request_id,
                      c.db_repo, self.ip_addr, self.sa)

        if allowed_to_change_status:
            # get status if set !
            if status:
                ChangesetStatusModel().set_status(
                    c.db_repo.repo_id,
                    status,
                    c.authuser.user_id,
                    comm,
                    pull_request=pull_request_id
                )

            if close_pr:
                PullRequestModel().close_pull_request(pull_request_id)
                action_logger(self.authuser,
                              'user_closed_pull_request:%s' % pull_request_id,
                              c.db_repo, self.ip_addr, self.sa)

        Session().commit()

        if not request.environ.get('HTTP_X_PARTIAL_XHR'):
            return redirect(pull_request.url())

        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
Ejemplo n.º 17
0
    def show(self, repo_name, pull_request_id, extra=None):
        repo_model = RepoModel()
        c.users_array = repo_model.get_users_js()
        c.user_groups_array = repo_model.get_user_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()

        # 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
        c.cs_repo = c.pull_request.org_repo
        (c.cs_ref_type,
         c.cs_ref_name,
         c.cs_rev) = c.pull_request.org_ref.split(':')

        c.a_repo = c.pull_request.other_repo
        (c.a_ref_type,
         c.a_ref_name,
         c.a_rev) = c.pull_request.other_ref.split(':') # a_rev is ancestor

        org_scm_instance = c.cs_repo.scm_instance # property with expensive cache invalidation check!!!
        try:
            c.cs_ranges = [org_scm_instance.get_changeset(x)
                           for x in c.pull_request.revisions]
        except ChangesetDoesNotExistError:
            c.cs_ranges = []
            h.flash(_('Revision %s not found in %s') % (x, c.cs_repo.repo_name),
                'error')
        c.cs_ranges_org = None # not stored and not important and moving target - could be calculated ...
        revs = [ctx.revision for ctx in reversed(c.cs_ranges)]
        c.jsdata = graph_data(org_scm_instance, revs)

        c.is_range = False
        try:
            if c.a_ref_type == 'rev': # this looks like a free range where target is ancestor
                cs_a = org_scm_instance.get_changeset(c.a_rev)
                root_parents = c.cs_ranges[0].parents
                c.is_range = cs_a in root_parents
                #c.merge_root = len(root_parents) > 1 # a range starting with a merge might deserve a warning
        except ChangesetDoesNotExistError: # probably because c.a_rev not found
            pass
        except IndexError: # probably because c.cs_ranges is empty, probably because revisions are missing
            pass

        avail_revs = set()
        avail_show = []
        c.cs_branch_name = c.cs_ref_name
        c.a_branch_name = None
        other_scm_instance = c.a_repo.scm_instance
        c.update_msg = ""
        c.update_msg_other = ""
        try:
            if not c.cs_ranges:
                c.update_msg = _('Error: changesets not found when displaying pull request from %s.') % c.cs_rev
            elif org_scm_instance.alias == 'hg' and c.a_ref_name != 'ancestor':
                if c.cs_ref_type != 'branch':
                    c.cs_branch_name = org_scm_instance.get_changeset(c.cs_ref_name).branch # use ref_type ?
                c.a_branch_name = c.a_ref_name
                if c.a_ref_type != 'branch':
                    try:
                        c.a_branch_name = other_scm_instance.get_changeset(c.a_ref_name).branch # use ref_type ?
                    except EmptyRepositoryError:
                        c.a_branch_name = 'null' # not a branch name ... but close enough
                # candidates: descendants of old head that are on the right branch
                #             and not are the old head itself ...
                #             and nothing at all if old head is a descendant of target ref name
                if not c.is_range and other_scm_instance._repo.revs('present(%s)::&%s', c.cs_ranges[-1].raw_id, c.a_branch_name):
                    c.update_msg = _('This pull request has already been merged to %s.') % c.a_branch_name
                elif c.pull_request.is_closed():
                    c.update_msg = _('This pull request has been closed and can not be updated.')
                else: # look for descendants of PR head on source branch in org repo
                    avail_revs = org_scm_instance._repo.revs('%s:: & branch(%s)',
                                                             revs[0], c.cs_branch_name)
                    if len(avail_revs) > 1: # more than just revs[0]
                        # also show changesets that not are descendants but would be merged in
                        targethead = other_scm_instance.get_changeset(c.a_branch_name).raw_id
                        if org_scm_instance.path != other_scm_instance.path:
                            # Note: org_scm_instance.path must come first so all
                            # valid revision numbers are 100% org_scm compatible
                            # - both for avail_revs and for revset results
                            hgrepo = unionrepo.unionrepository(org_scm_instance.baseui,
                                                               org_scm_instance.path,
                                                               other_scm_instance.path)
                        else:
                            hgrepo = org_scm_instance._repo
                        show = set(hgrepo.revs('::%ld & !::parents(%s) & !::%s',
                                               avail_revs, revs[0], targethead))
                        c.update_msg = _('The following additional changes are available on %s:') % c.cs_branch_name
                    else:
                        show = set()
                        avail_revs = set() # drop revs[0]
                        c.update_msg = _('No additional changesets found for iterating on this pull request.')

                    # TODO: handle branch heads that not are tip-most
                    brevs = org_scm_instance._repo.revs('%s - %ld - %s', c.cs_branch_name, avail_revs, revs[0])
                    if brevs:
                        # also show changesets that are on branch but neither ancestors nor descendants
                        show.update(org_scm_instance._repo.revs('::%ld - ::%ld - ::%s', brevs, avail_revs, c.a_branch_name))
                        show.add(revs[0]) # make sure graph shows this so we can see how they relate
                        c.update_msg_other = _('Note: Branch %s has another head: %s.') % (c.cs_branch_name,
                            h.short_id(org_scm_instance.get_changeset((max(brevs))).raw_id))

                    avail_show = sorted(show, reverse=True)

            elif org_scm_instance.alias == 'git':
                c.cs_repo.scm_instance.get_changeset(c.cs_rev) # check it exists - raise ChangesetDoesNotExistError if not
                c.update_msg = _("Git pull requests don't support iterating yet.")
        except ChangesetDoesNotExistError:
            c.update_msg = _('Error: some changesets not found when displaying pull request from %s.') % c.cs_rev

        c.avail_revs = avail_revs
        c.avail_cs = [org_scm_instance.get_changeset(r) for r in avail_show]
        c.avail_jsdata = graph_data(org_scm_instance, avail_show)

        raw_ids = [x.raw_id for x in c.cs_ranges]
        c.cs_comments = c.cs_repo.get_comments(raw_ids)
        c.statuses = c.cs_repo.statuses(raw_ids)

        ignore_whitespace = request.GET.get('ignorews') == '1'
        line_context = safe_int(request.GET.get('context'), 3)
        c.ignorews_url = _ignorews_url
        c.context_url = _context_url
        c.fulldiff = request.GET.get('fulldiff')
        diff_limit = self.cut_off_limit if not c.fulldiff else None

        # we swap org/other ref since we run a simple diff on one repo
        log.debug('running diff between %s and %s in %s',
                  c.a_rev, c.cs_rev, org_scm_instance.path)
        try:
            txtdiff = org_scm_instance.get_diff(rev1=safe_str(c.a_rev), rev2=safe_str(c.cs_rev),
                                                ignore_whitespace=ignore_whitespace,
                                                context=line_context)
        except ChangesetDoesNotExistError:
            txtdiff =  _("The diff can't be shown - the PR revisions could not be found.")
        diff_processor = diffs.DiffProcessor(txtdiff or '', format='gitdiff',
                                             diff_limit=diff_limit)
        _parsed = diff_processor.prepare()

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

        c.file_diff_data = []
        c.lines_added = 0
        c.lines_deleted = 0

        for f in _parsed:
            st = f['stats']
            c.lines_added += st['added']
            c.lines_deleted += st['deleted']
            filename = f['filename']
            fid = h.FID('', filename)
            diff = diff_processor.as_html(enable_comments=True,
                                          parsed_lines=[f])
            c.file_diff_data.append((fid, None, f['operation'], f['old_filename'], filename, diff, st))

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

        # (badly named) pull-request status calculation based on reviewer votes
        (c.pull_request_reviewers,
         c.pull_request_pending_reviewers,
         c.current_voting_result,
         ) = cs_model.calculate_pull_request_result(c.pull_request)
        c.changeset_statuses = ChangesetStatus.STATUSES

        c.is_ajax_preview = False
        c.ancestors = None # [c.a_rev] ... but that is shown in an other way
        return render('/pullrequests/pullrequest_show.html')
    def export(self, repo_name, pull_request_id, fname, **kwargs):
        ext = fname.split('.')[1]
        export_name = '{repo}-{pr_id}.{ext}'.format(
            repo=safe_str(repo_name.replace('/', '_')),
            pr_id=safe_str(pull_request_id),
            ext=safe_str(ext))
        fd, export_path = mkstemp()
        log.debug(
            'Creating new temp export in {path}'.format(path=export_path))

        try:
            pr = PullRequest.get(pull_request_id)
            if repo_name != pr.other_repo.repo_name:
                raise RepositoryError
        except Exception as e:
            log.error(e)
            return _('Pull request #{id} not found').format(id=pull_request_id)
        cc_model = ChangesetCommentsModel()
        inline_comments = cc_model.get_inline_comments(
            pr.org_repo_id, pull_request=pull_request_id)
        file_comments = {}
        for f_path, lines in inline_comments:
            file_comments[f_path] = lines
        sorted_file_comments_by_name = sorted(file_comments.items(),
                                              key=lambda x: x[0],
                                              reverse=False)
        general_comments = cc_model.get_comments(pr.org_repo_id,
                                                 pull_request=pull_request_id)

        wb = Workbook()
        ws = wb.create_sheet(_('comments'), 0)
        ws['A1'].value = _('File path')
        ws.column_dimensions['A'].width = 3.0
        ws['B1'].value = _('Comment ID')
        ws['C1'].value = _('Line no (old)')
        ws['D1'].value = _('Line no (new)')
        ws['E1'].value = _('Author')
        ws['F1'].value = _('Status')
        ws['G1'].value = _('Comment')
        ws.column_dimensions['G'].width = 60.0
        ws['H1'].value = _('Opinion')
        ws.column_dimensions['H'].width = 60.0
        ws['I1'].value = _('Retouch')
        ws['J1'].value = _('Priority')
        ws['K1'].value = _('Deadline')

        align_rot_90 = Alignment(text_rotation=90)
        align_wrap = Alignment(wrap_text=True, vertical='top')

        rows = 2
        for f_path, lines in sorted_file_comments_by_name:
            sorted_inline_comments_by_lineno = sorted(
                lines.iteritems(),
                key=lambda (line_no, comments): int(line_no[1:]),
                reverse=False)
            base_rows = rows
            for line_no, comments in sorted_inline_comments_by_lineno:
                top_comments = {}
                reply_comments = {}
                for co in comments:
                    m = re.match(r'`replyto comment-(?P<replyto>[\d]+)',
                                 co.text)
                    if m:
                        replyto = int(m.group('replyto'))
                        if reply_comments.get(replyto, None) is None:
                            reply_comments[replyto] = []
                        reply_comments[replyto].append(co)
                    else:
                        top_comments[co.comment_id] = co

                def _make_threaded_message(comment_id, stops=0):
                    message = '\n' if stops > 0 else ''
                    for reply in reply_comments.get(comment_id, []):
                        text = re.sub(
                            r'`replyto comment-(?:[\d]+) <#comment-(?:[\d]+)>`_ :',
                            '', reply.text)
                        indent = ' ' * 2 * stops
                        message += '{indent}commented by {author}:\n'.format(
                            indent=indent, author=reply.author.username)
                        message += '\n'.join(indent + line
                                             for line in text.splitlines()
                                             if len(line) > 0)
                        message += '\n{indent}----'.format(indent=indent)
                        message += _make_threaded_message(
                            reply.comment_id, stops + 1)
                    return message

                for comment_id, co in top_comments.items():
                    link = pr.url(
                        canonical=True,
                        anchor='comment-{id}'.format(id=co.comment_id))
                    ws['B{row}'.format(row=rows)].value = co.comment_id
                    ws['B{row}'.format(row=rows)].hyperlink = link
                    if co.line_no.startswith('o'):
                        ws['C{row}'.format(row=rows)].value = co.line_no[1:]
                    else:
                        ws['D{row}'.format(row=rows)].value = co.line_no[1:]
                    ws['E{row}'.format(row=rows)].value = co.author.username
                    if co.status_change:
                        ws['F{row}'.format(row=rows)].value = str(
                            h.changeset_status_lbl(co.status_change[0].status))
                    ws['G{row}'.format(row=rows)].value = co.text.replace(
                        '@', '(at)')
                    ws['G{row}'.format(row=rows)].alignment = align_wrap
                    ws['H{row}'.format(
                        row=rows)].value = _make_threaded_message(
                            comment_id).replace('@', '(at)')
                    ws['H{row}'.format(row=rows)].alignment = align_wrap
                    rows += 1
            ws.merge_cells('A{start}:A{end}'.format(start=base_rows,
                                                    end=rows - 1))
            for i in range(rows - base_rows):
                ws['A{row}'.format(row=base_rows + i)].value = f_path
            ws['A{start}'.format(start=base_rows)].alignment = align_rot_90

        ws['A{row}'.format(row=rows)].value = 'General'
        base_rows = rows
        top_comments = {}
        reply_comments = {}
        for co in general_comments:
            m = re.match(r'`replyto comment-(?P<replyto>[\d]+)', co.text)
            if m:
                replyto = int(m.group('replyto'))
                if reply_comments.get(replyto, None) is None:
                    reply_comments[replyto] = []
                reply_comments[replyto].append(co)
            else:
                top_comments[co.comment_id] = co

        def _make_threaded_message(comment_id, stops=0):
            message = '\n' if stops > 0 else ''
            for reply in reply_comments.get(comment_id, []):
                text = re.sub(
                    r'`replyto comment-(?:[\d]+) <#comment-(?:[\d]+)>`_ :', '',
                    reply.text)
                indent = ' ' * 2 * stops
                message += '{indent}commented by {author}:\n'.format(
                    indent=indent, author=reply.author.username)
                message += '\n'.join(indent + line
                                     for line in text.splitlines()
                                     if len(line) > 0)
                message += '\n{indent}----'.format(indent=indent)
                message += _make_threaded_message(reply.comment_id, stops + 1)
            return message

        for comment_id, co in top_comments.items():
            link = pr.url(canonical=True,
                          anchor='comment-{id}'.format(id=co.comment_id))
            ws['B{row}'.format(row=rows)].value = co.comment_id
            ws['B{row}'.format(row=rows)].hyperlink = link
            ws['E{row}'.format(row=rows)].value = co.author.username
            if co.status_change:
                ws['F{row}'.format(row=rows)].value = str(
                    h.changeset_status_lbl(co.status_change[0].status))
            ws['G{row}'.format(row=rows)].value = co.text.replace('@', '(at)')
            ws['G{row}'.format(row=rows)].alignment = align_wrap
            ws['H{row}'.format(
                row=rows)].value = _make_threaded_message(comment_id).replace(
                    '@', '(at)')
            ws['H{row}'.format(row=rows)].alignment = align_wrap
            rows += 1
        ws.merge_cells('A{start}:A{end}'.format(start=base_rows, end=rows - 1))
        for i in range(rows - base_rows):
            ws['A{row}'.format(row=base_rows + i)].value = 'General'
        ws['A{start}'.format(start=base_rows)].alignment = align_rot_90

        with os.fdopen(fd, 'wb') as s:
            s.write(save_virtual_workbook(wb))

        def get_chunked_export(export_path):
            stream = open(export_path, 'rb')
            while True:
                data = stream.read(16 * 1024)
                if not data:
                    break
                yield data
            stream.close()
            log.debug('Destroying temp export %s', export_path)
            os.remove(export_path)

        response.content_disposition = str('attachment; filename=%s' %
                                           (export_name))
        response.content_type = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
        return get_chunked_export(export_path)
Ejemplo n.º 19
0
    def show(self, repo_name, pull_request_id, extra=None):
        repo_model = RepoModel()
        c.users_array = repo_model.get_users_js()
        c.user_groups_array = repo_model.get_user_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()

        # 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
        c.cs_repo = c.pull_request.org_repo
        (c.cs_ref_type,
         c.cs_ref_name,
         c.cs_rev) = c.pull_request.org_ref.split(':')

        c.a_repo = c.pull_request.other_repo
        (c.a_ref_type,
         c.a_ref_name,
         c.a_rev) = c.pull_request.other_ref.split(':') # other_rev is ancestor

        org_scm_instance = c.cs_repo.scm_instance # property with expensive cache invalidation check!!!
        c.cs_repo = c.cs_repo
        try:
            c.cs_ranges = [org_scm_instance.get_changeset(x)
                           for x in c.pull_request.revisions]
        except ChangesetDoesNotExistError:
            c.cs_ranges = []
        c.cs_ranges_org = None # not stored and not important and moving target - could be calculated ...
        revs = [ctx.revision for ctx in reversed(c.cs_ranges)]
        c.jsdata = json.dumps(graph_data(org_scm_instance, revs))

        c.is_range = False
        if c.a_ref_type == 'rev': # this looks like a free range where target is ancestor
            cs_a = org_scm_instance.get_changeset(c.a_rev)
            root_parents = c.cs_ranges[0].parents
            c.is_range = cs_a in root_parents
            #c.merge_root = len(root_parents) > 1 # a range starting with a merge might deserve a warning

        avail_revs = set()
        avail_show = []
        c.cs_branch_name = c.cs_ref_name
        other_scm_instance = c.a_repo.scm_instance
        c.update_msg = ""
        c.update_msg_other = ""
        try:
            if org_scm_instance.alias == 'hg' and c.a_ref_name != 'ancestor':
                if c.cs_ref_type != 'branch':
                    c.cs_branch_name = org_scm_instance.get_changeset(c.cs_ref_name).branch # use ref_type ?
                c.a_branch_name = c.a_ref_name
                if c.a_ref_type != 'branch':
                    try:
                        c.a_branch_name = other_scm_instance.get_changeset(c.a_ref_name).branch # use ref_type ?
                    except EmptyRepositoryError:
                        c.a_branch_name = 'null' # not a branch name ... but close enough
                # candidates: descendants of old head that are on the right branch
                #             and not are the old head itself ...
                #             and nothing at all if old head is a descendant of target ref name
                if not c.is_range and other_scm_instance._repo.revs('present(%s)::&%s', c.cs_ranges[-1].raw_id, c.a_branch_name):
                    c.update_msg = _('This pull request has already been merged to %s.') % c.a_branch_name
                elif c.pull_request.is_closed():
                    c.update_msg = _('This pull request has been closed and can not be updated.')
                else: # look for descendants of PR head on source branch in org repo
                    avail_revs = org_scm_instance._repo.revs('%s:: & branch(%s)',
                                                             revs[0], c.cs_branch_name)
                    if len(avail_revs) > 1: # more than just revs[0]
                        # also show changesets that not are descendants but would be merged in
                        targethead = other_scm_instance.get_changeset(c.a_branch_name).raw_id
                        if org_scm_instance.path != other_scm_instance.path:
                            # Note: org_scm_instance.path must come first so all
                            # valid revision numbers are 100% org_scm compatible
                            # - both for avail_revs and for revset results
                            hgrepo = unionrepo.unionrepository(org_scm_instance.baseui,
                                                               org_scm_instance.path,
                                                               other_scm_instance.path)
                        else:
                            hgrepo = org_scm_instance._repo
                        show = set(hgrepo.revs('::%ld & !::parents(%s) & !::%s',
                                               avail_revs, revs[0], targethead))
                        c.update_msg = _('The following changes are available on %s:') % c.cs_branch_name
                    else:
                        show = set()
                        avail_revs = set() # drop revs[0]
                        c.update_msg = _('No changesets found for updating this pull request.')

                    # TODO: handle branch heads that not are tip-most
                    brevs = org_scm_instance._repo.revs('%s - %ld - %s', c.cs_branch_name, avail_revs, revs[0])
                    if brevs:
                        # also show changesets that are on branch but neither ancestors nor descendants
                        show.update(org_scm_instance._repo.revs('::%ld - ::%ld - ::%s', brevs, avail_revs, c.a_branch_name))
                        show.add(revs[0]) # make sure graph shows this so we can see how they relate
                        c.update_msg_other = _('Note: Branch %s has another head: %s.') % (c.cs_branch_name,
                            h.short_id(org_scm_instance.get_changeset((max(brevs))).raw_id))

                    avail_show = sorted(show, reverse=True)

            elif org_scm_instance.alias == 'git':
                c.cs_repo.scm_instance.get_changeset(c.cs_rev) # check it exists - raise ChangesetDoesNotExistError if not
                c.update_msg = _("Git pull requests don't support updates yet.")
        except ChangesetDoesNotExistError:
            c.update_msg = _('Error: revision %s was not found. Please create a new pull request!') % c.cs_rev

        c.avail_revs = avail_revs
        c.avail_cs = [org_scm_instance.get_changeset(r) for r in avail_show]
        c.avail_jsdata = json.dumps(graph_data(org_scm_instance, avail_show))

        raw_ids = [x.raw_id for x in c.cs_ranges]
        c.cs_comments = c.cs_repo.get_comments(raw_ids)
        c.statuses = c.cs_repo.statuses(raw_ids)

        ignore_whitespace = request.GET.get('ignorews') == '1'
        line_context = request.GET.get('context', 3)
        c.ignorews_url = _ignorews_url
        c.context_url = _context_url
        c.fulldiff = request.GET.get('fulldiff')
        diff_limit = self.cut_off_limit if not c.fulldiff else None

        # we swap org/other ref since we run a simple diff on one repo
        log.debug('running diff between %s and %s in %s',
                  c.a_rev, c.cs_rev, org_scm_instance.path)
        try:
            txtdiff = org_scm_instance.get_diff(rev1=safe_str(c.a_rev), rev2=safe_str(c.cs_rev),
                                                ignore_whitespace=ignore_whitespace,
                                                context=line_context)
        except ChangesetDoesNotExistError:
            txtdiff =  _("The diff can't be shown - the PR revisions could not be found.")
        diff_processor = diffs.DiffProcessor(txtdiff 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']
            c.lines_added += st['added']
            c.lines_deleted += st['deleted']
            fid = h.FID('', f['filename'])
            c.files.append([fid, f['operation'], f['filename'], f['stats']])
            htmldiff = diff_processor.as_html(enable_comments=True,
                                              parsed_lines=[f])
            c.changes[fid] = [f['operation'], f['filename'], htmldiff]

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

        # (badly named) pull-request status calculation based on reviewer votes
        (c.pull_request_reviewers,
         c.pull_request_pending_reviewers,
         c.current_voting_result,
         ) = cs_model.calculate_pull_request_result(c.pull_request)
        c.changeset_statuses = ChangesetStatus.STATUSES

        c.as_form = False
        c.ancestor = None # there is one - but right here we don't know which
        return render('/pullrequests/pullrequest_show.html')
Ejemplo n.º 20
0
    def test_selective_retrieval_of_inline_comments(self):
        with test_context(self.app):
            repo_id = Repository.get_by_repo_name(base.HG_REPO).repo_id
            revision = '9a7b4ff9e8b40bbda72fc75f162325b9baa45cda'

            self._check_comment_count(repo_id,
                                      revision,
                                      expected_len_comments=0,
                                      expected_len_inline_comments=0)

            text = 'an inline comment'
            f_path = 'vcs/tests/base.py'
            line_no = 'n50'
            new_comment = ChangesetCommentsModel().create(
                text=text,
                repo=base.HG_REPO,
                author=base.TEST_USER_REGULAR_LOGIN,
                revision=revision,
                f_path=f_path,
                line_no=line_no,
                send_email=False)

            text2 = 'another inline comment, same file'
            line_no2 = 'o41'
            new_comment2 = ChangesetCommentsModel().create(
                text=text2,
                repo=base.HG_REPO,
                author=base.TEST_USER_REGULAR_LOGIN,
                revision=revision,
                f_path=f_path,
                line_no=line_no2,
                send_email=False)

            text3 = 'another inline comment, same file'
            f_path3 = 'vcs/tests/test_hg.py'
            line_no3 = 'n159'
            new_comment3 = ChangesetCommentsModel().create(
                text=text3,
                repo=base.HG_REPO,
                author=base.TEST_USER_REGULAR_LOGIN,
                revision=revision,
                f_path=f_path3,
                line_no=line_no3,
                send_email=False)

            # now selectively retrieve comments of one file
            comments, inline_comments = self._check_comment_count(
                repo_id,
                revision,
                f_path=f_path,
                expected_len_comments=0,
                expected_len_inline_comments=1)
            # inline_comments is a list of tuples (file_path, dict)
            # where the dict keys are line numbers and values are lists of comments
            assert inline_comments[0][0] == f_path
            assert len(inline_comments[0][1]) == 2
            assert inline_comments[0][1][line_no][0].text == text
            assert inline_comments[0][1][line_no2][0].text == text2

            # now selectively retrieve comments of one file, one line
            comments, inline_comments = self._check_comment_count(
                repo_id,
                revision,
                f_path=f_path,
                line_no=line_no2,
                expected_len_comments=0,
                expected_len_inline_comments=1)
            # inline_comments is a list of tuples (file_path, dict)
            # where the dict keys are line numbers and values are lists of comments
            assert inline_comments[0][0] == f_path
            assert len(inline_comments[0][1]) == 1
            assert inline_comments[0][1][line_no2][0].text == text2

            # verify that retrieval based on line_no but no f_path fails
            with pytest.raises(Exception) as excinfo:
                self._check_comment_count(repo_id,
                                          revision,
                                          f_path=None,
                                          line_no=line_no2,
                                          expected_len_comments=0,
                                          expected_len_inline_comments=0)
            assert 'line_no only makes sense if f_path is given' in str(
                excinfo.value)
Ejemplo n.º 21
0
def create_cs_pr_comment(repo_name,
                         revision=None,
                         pull_request=None,
                         allowed_to_change_status=True):
    """
    Add a comment to the specified changeset or pull request, using POST values
    from the request.

    Comments can be inline (when a file path and line number is specified in
    POST) or general comments.
    A comment can be accompanied by a review status change (accepted, rejected,
    etc.). Pull requests can be closed or deleted.

    Parameter 'allowed_to_change_status' is used for both status changes and
    closing of pull requests. For deleting of pull requests, more specific
    checks are done.
    """

    assert request.environ.get('HTTP_X_PARTIAL_XHR')
    if pull_request:
        pull_request_id = pull_request.pull_request_id
    else:
        pull_request_id = None

    status = request.POST.get('changeset_status')
    close_pr = request.POST.get('save_close')
    delete = request.POST.get('save_delete')
    f_path = request.POST.get('f_path')
    line_no = request.POST.get('line')

    if (status or close_pr or delete) and (f_path or line_no):
        # status votes and closing is only possible in general comments
        raise HTTPBadRequest()

    if not allowed_to_change_status:
        if status or close_pr:
            h.flash(_('No permission to change status'), 'error')
            raise HTTPForbidden()

    if pull_request and delete == "delete":
        if (pull_request.owner_id == request.authuser.user_id
                or h.HasPermissionAny('hg.admin')()
                or h.HasRepoPermissionLevel('admin')(
                    pull_request.org_repo.repo_name)
                or h.HasRepoPermissionLevel('admin')(
                    pull_request.other_repo.repo_name)
            ) and not pull_request.is_closed():
            PullRequestModel().delete(pull_request)
            Session().commit()
            h.flash(_('Successfully deleted pull request %s') %
                    pull_request_id,
                    category='success')
            return {
                'location': h.url('my_pullrequests'),  # or repo pr list?
            }
        raise HTTPForbidden()

    text = request.POST.get('text', '').strip()

    comment = ChangesetCommentsModel().create(
        text=text,
        repo=c.db_repo.repo_id,
        author=request.authuser.user_id,
        revision=revision,
        pull_request=pull_request_id,
        f_path=f_path or None,
        line_no=line_no or None,
        status_change=ChangesetStatus.get_status_lbl(status)
        if status else None,
        closing_pr=close_pr,
    )

    if status:
        ChangesetStatusModel().set_status(
            c.db_repo.repo_id,
            status,
            request.authuser.user_id,
            comment,
            revision=revision,
            pull_request=pull_request_id,
        )

    if pull_request:
        action = 'user_commented_pull_request:%s' % pull_request_id
    else:
        action = 'user_commented_revision:%s' % revision
    action_logger(request.authuser, action, c.db_repo, request.ip_addr)

    if pull_request and close_pr:
        PullRequestModel().close_pull_request(pull_request_id)
        action_logger(request.authuser,
                      'user_closed_pull_request:%s' % pull_request_id,
                      c.db_repo, request.ip_addr)

    Session().commit()

    data = {
        'target_id': h.safeid(request.POST.get('f_path')),
    }
    if comment is not None:
        c.comment = comment
        data.update(comment.get_dict())
        data.update({
            'rendered_text':
            render('changeset/changeset_comment_block.html')
        })

    return data
Ejemplo n.º 22
0
    def _index(self, revision, method):
        c.pull_request = None
        c.anchor_url = anchor_url
        c.ignorews_url = _ignorews_url
        c.context_url = _context_url
        c.fulldiff = request.GET.get(
            'fulldiff')  # for reporting number of changed files
        # get ranges of revisions if preset
        rev_range = revision.split('...')[:2]
        enable_comments = True
        c.cs_repo = c.db_repo
        try:
            if len(rev_range) == 2:
                enable_comments = False
                rev_start = rev_range[0]
                rev_end = rev_range[1]
                rev_ranges = c.db_repo_scm_instance.get_changesets(
                    start=rev_start, end=rev_end)
            else:
                rev_ranges = [c.db_repo_scm_instance.get_changeset(revision)]

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

        except (ChangesetDoesNotExistError, EmptyRepositoryError):
            log.debug(traceback.format_exc())
            msg = _('Such revision does not exist for this repository')
            h.flash(msg, category='error')
            raise HTTPNotFound()

        c.changes = OrderedDict()

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

        c.changeset_statuses = ChangesetStatus.STATUSES
        comments = dict()
        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:
            if method == 'show':
                c.statuses.extend([
                    ChangesetStatusModel().get_status(c.db_repo.repo_id,
                                                      changeset.raw_id)
                ])

                # Changeset comments
                comments.update(
                    (com.comment_id, com)
                    for com in ChangesetCommentsModel().get_comments(
                        c.db_repo.repo_id, revision=changeset.raw_id))

                # Status change comments - mostly from pull requests
                comments.update((
                    st.comment_id, st.comment
                ) for st in ChangesetStatusModel().get_statuses(
                    c.db_repo.repo_id, changeset.raw_id, with_revisions=True)
                                if st.comment_id is not None)

                inlines = ChangesetCommentsModel() \
                            .get_inline_comments(c.db_repo.repo_id,
                                                 revision=changeset.raw_id)
                c.inline_comments.extend(inlines)

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

            raw_diff = diffs.get_diff(c.db_repo_scm_instance,
                                      cs1,
                                      cs2,
                                      ignore_whitespace=ign_whitespace_lcl,
                                      context=context_lcl)
            diff_limit = None if c.fulldiff else self.cut_off_limit
            file_diff_data = []
            if method == 'show':
                diff_processor = diffs.DiffProcessor(
                    raw_diff,
                    vcs=c.db_repo_scm_instance.alias,
                    diff_limit=diff_limit)
                c.limited_diff = diff_processor.limited_diff
                for f in diff_processor.parsed:
                    st = f['stats']
                    c.lines_added += st['added']
                    c.lines_deleted += st['deleted']
                    filename = f['filename']
                    fid = h.FID(changeset.raw_id, filename)
                    url_fid = h.FID('', filename)
                    html_diff = diffs.as_html(enable_comments=enable_comments,
                                              parsed_lines=[f])
                    file_diff_data.append(
                        (fid, url_fid, f['operation'], f['old_filename'],
                         filename, html_diff, st))
            else:
                # downloads/raw we only need RAW diff nothing else
                file_diff_data.append(('', None, None, None, raw_diff, None))
            c.changes[changeset.raw_id] = (cs1, cs2, file_diff_data)

        # sort comments in creation order
        c.comments = [com for com_id, com in sorted(comments.items())]

        # 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])
            c.changeset_graft_source_hash = ascii_str(
                c.changeset.extra.get(b'source', b''))
            c.changeset_transplant_source_hash = ascii_str(
                binascii.hexlify(
                    c.changeset.extra.get(b'transplant_source', b'')))
        if method == 'download':
            response.content_type = 'text/plain'
            response.content_disposition = 'attachment; filename=%s.diff' \
                                            % revision[:12]
            return raw_diff
        elif method == 'patch':
            response.content_type = 'text/plain'
            c.diff = safe_str(raw_diff)
            return render('changeset/patch_changeset.html')
        elif method == 'raw':
            response.content_type = 'text/plain'
            return raw_diff
        elif method == 'show':
            if len(c.cs_ranges) == 1:
                return render('changeset/changeset.html')
            else:
                c.cs_ranges_org = None
                c.cs_comments = {}
                revs = [ctx.revision for ctx in reversed(c.cs_ranges)]
                c.jsdata = graph_data(c.db_repo_scm_instance, revs)
                return render('changeset/changeset_range.html')
Ejemplo n.º 23
0
    def comment(self, repo_name, pull_request_id):
        pull_request = PullRequest.get_or_404(pull_request_id)

        status = 0
        close_pr = False
        allowed_to_change_status = self._get_is_allowed_change_status(
            pull_request)
        if allowed_to_change_status:
            status = request.POST.get('changeset_status')
            close_pr = request.POST.get('save_close')
        text = request.POST.get('text', '').strip() or _('No comments.')
        if close_pr:
            text = _('Closing.') + '\n' + text

        comm = ChangesetCommentsModel().create(
            text=text,
            repo=c.db_repo.repo_id,
            user=c.authuser.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 allowed_to_change_status else None),
            closing_pr=close_pr)

        action_logger(self.authuser,
                      'user_commented_pull_request:%s' % pull_request_id,
                      c.db_repo, self.ip_addr, self.sa)

        if allowed_to_change_status:
            # get status if set !
            if status:
                ChangesetStatusModel().set_status(c.db_repo.repo_id,
                                                  status,
                                                  c.authuser.user_id,
                                                  comm,
                                                  pull_request=pull_request_id)

            if close_pr:
                PullRequestModel().close_pull_request(pull_request_id)
                action_logger(self.authuser,
                              'user_closed_pull_request:%s' % pull_request_id,
                              c.db_repo, self.ip_addr, self.sa)

        Session().commit()

        if not request.environ.get('HTTP_X_PARTIAL_XHR'):
            return redirect(pull_request.url())

        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