Example #1
0
    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)

        # (badly named) pull-request status calculation based on reviewer votes
        c.current_changeset_status = cs_model.calculate_status(
            c.pull_request_reviewers, )
        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')
Example #2
0
    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)

        # (badly named) pull-request status calculation based on reviewer votes
        c.current_changeset_status = cs_model.calculate_status(
                                        c.pull_request_reviewers,
                                         )
        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')
    def test_comment_force_close_pull_request(self, pr_util, csrf_token):
        pull_request = pr_util.create_pull_request()
        pull_request_id = pull_request.pull_request_id
        reviewers_ids = [1, 2]
        PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
        author = pull_request.user_id
        repo = pull_request.target_repo.repo_id
        self.app.post(
            url(controller='pullrequests',
                action='comment',
                repo_name=pull_request.target_repo.scm_instance().name,
                pull_request_id=str(pull_request_id)),
            params={
                'changeset_status': 'forced_closed',
                'csrf_token': csrf_token},
            status=302)

        pull_request = PullRequest.get(pull_request_id)

        action = 'user_closed_pull_request:%d' % pull_request_id
        journal = UserLog.query().filter(
            UserLog.user_id == author,
            UserLog.repository_id == repo,
            UserLog.action == action).all()
        assert len(journal) == 1

        # check only the latest status, not the review status
        status = ChangesetStatusModel().get_status(
            pull_request.source_repo, pull_request=pull_request)
        assert status == ChangesetStatus.STATUS_REJECTED
Example #4
0
    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))

        c.co = 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 '
                        '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))
        #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
    def close_pull_request_with_comment(self,
                                        pull_request,
                                        user,
                                        repo,
                                        message=None):
        status = ChangesetStatus.STATUS_REJECTED

        if not message:
            message = (_('Status change %(transition_icon)s %(status)s') % {
                'transition_icon': '>',
                'status': ChangesetStatus.get_status_lbl(status)
            })

        internal_message = _('Closing with') + ' ' + message

        comm = ChangesetCommentsModel().create(
            text=internal_message,
            repo=repo.repo_id,
            user=user.user_id,
            pull_request=pull_request.pull_request_id,
            f_path=None,
            line_no=None,
            status_change=ChangesetStatus.get_status_lbl(status),
            closing_pr=True)

        ChangesetStatusModel().set_status(
            repo.repo_id,
            status,
            user.user_id,
            comm,
            pull_request=pull_request.pull_request_id)
        Session().flush()

        PullRequestModel().close_pull_request(pull_request.pull_request_id,
                                              user)
def test_commit_has_voted_status_after_vote_on_pull_request(
        pr_util, voted_status):
    pull_request = pr_util.create_pull_request()
    pr_util.create_status_votes(voted_status, pull_request.reviewers[0])
    for commit_id in pull_request.revisions:
        status = ChangesetStatusModel().get_status(
            repo=pr_util.source_repository, revision=commit_id)
        assert status == voted_status
def test_commit_under_review_if_added_to_pull_request(pr_util):
    pull_request = pr_util.create_pull_request()
    pr_util.create_status_votes(db.ChangesetStatus.STATUS_APPROVED,
                                pull_request.reviewers[0])
    added_commit_id = pr_util.add_one_commit()

    status = ChangesetStatusModel().get_status(repo=pr_util.source_repository,
                                               revision=added_commit_id)
    assert status == db.ChangesetStatus.STATUS_UNDER_REVIEW
def test_NotReviewedRevisions():
    repo_id = Repository.get_by_repo_name(HG_REPO).repo_id
    validator = v.NotReviewedRevisions(repo_id)
    rev = '0' * 40
    # add status for a rev, that should throw an error because it is already
    # reviewed
    new_status = ChangesetStatus()
    new_status.author = ChangesetStatusModel()._get_user(TEST_USER_ADMIN_LOGIN)
    new_status.repo = ChangesetStatusModel()._get_repo(HG_REPO)
    new_status.status = ChangesetStatus.STATUS_APPROVED
    new_status.comment = None
    new_status.revision = rev
    Session().add(new_status)
    Session().commit()
    try:
        pytest.raises(formencode.Invalid, validator.to_python, [rev])
    finally:
        Session().delete(new_status)
        Session().commit()
def test_commit_keeps_status_if_removed_from_pull_request(
        pr_util, voted_status):
    pull_request = pr_util.create_pull_request()
    pr_util.add_one_commit()
    pr_util.create_status_votes(voted_status, pull_request.reviewers[0])

    removed_commit_id = pr_util.remove_one_commit()

    status = ChangesetStatusModel().get_status(repo=pr_util.source_repository,
                                               revision=removed_commit_id)
    assert status == voted_status
def test_commit_keeps_status_if_unchanged_after_update_of_pull_request(
        pr_util, voted_status):
    pull_request = pr_util.create_pull_request()
    commit_id = pull_request.revisions[-1]
    pr_util.create_status_votes(voted_status, pull_request.reviewers[0])
    pr_util.update_source_repository()
    PullRequestModel().update_commits(pull_request)
    assert pull_request.revisions[-1] == commit_id

    status = ChangesetStatusModel().get_status(repo=pr_util.source_repository,
                                               revision=commit_id)
    assert status == voted_status
    def create(self,
               created_by,
               source_repo,
               source_ref,
               target_repo,
               target_ref,
               revisions,
               reviewers,
               title,
               description=None):
        created_by_user = self._get_user(created_by)
        source_repo = self._get_repo(source_repo)
        target_repo = self._get_repo(target_repo)

        pull_request = PullRequest()
        pull_request.source_repo = source_repo
        pull_request.source_ref = source_ref
        pull_request.target_repo = target_repo
        pull_request.target_ref = target_ref
        pull_request.revisions = revisions
        pull_request.title = title
        pull_request.description = description
        pull_request.author = created_by_user

        Session().add(pull_request)
        Session().flush()

        # members / reviewers
        for user_id in set(reviewers):
            user = self._get_user(user_id)
            reviewer = PullRequestReviewers(user, pull_request)
            Session().add(reviewer)

        # Set approval status to "Under Review" for all commits which are
        # part of this pull request.
        ChangesetStatusModel().set_status(
            repo=target_repo,
            status=ChangesetStatus.STATUS_UNDER_REVIEW,
            user=created_by_user,
            pull_request=pull_request)

        self.notify_reviewers(pull_request, reviewers)
        self._trigger_pull_request_hook(pull_request, created_by_user,
                                        'create')

        return pull_request
    def test_reject_and_close_pull_request(self, pr_util, csrf_token):
        pull_request = pr_util.create_pull_request()
        pull_request_id = pull_request.pull_request_id
        response = self.app.post(
            url(controller='pullrequests',
                action='update',
                repo_name=pull_request.target_repo.scm_instance().name,
                pull_request_id=str(pull_request.pull_request_id)),
            params={'close_pull_request': 'true', '_method': 'put',
                    'csrf_token': csrf_token})

        pull_request = PullRequest.get(pull_request_id)

        assert response.json is True
        assert pull_request.is_closed()

        # check only the latest status, not the review status
        status = ChangesetStatusModel().get_status(
            pull_request.source_repo, pull_request=pull_request)
        assert status == ChangesetStatus.STATUS_REJECTED
Example #13
0
    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.target_repo = c.pull_request.org_repo.repo_name

        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')
Example #14
0
    def comment(self, repo_name, pull_request_id):
        pull_request_id = safe_int(pull_request_id)
        pull_request = PullRequest.get_or_404(pull_request_id)
        if pull_request.is_closed():
            raise HTTPForbidden()

        # TODO: johbo: Re-think this bit, "approved_closed" does not exist
        # as a changeset status, still we want to send it in one value.
        status = request.POST.get('changeset_status', None)
        text = request.POST.get('text')
        if status and '_closed' in status:
            close_pr = True
            status = status.replace('_closed', '')
        else:
            close_pr = False

        forced = (status == 'forced')
        if forced:
            status = 'rejected'

        allowed_to_change_status = PullRequestModel().check_user_change_status(
            pull_request, c.rhodecode_user)

        if status and allowed_to_change_status:
            message = (_('Status change %(transition_icon)s %(status)s') % {
                'transition_icon': '>',
                'status': ChangesetStatus.get_status_lbl(status)
            })
            if close_pr:
                message = _('Closing with') + ' ' + message
            text = text or message
        comm = ChangesetCommentsModel().create(
            text=text,
            repo=c.rhodecode_db_repo.repo_id,
            user=c.rhodecode_user.user_id,
            pull_request=pull_request_id,
            f_path=request.POST.get('f_path'),
            line_no=request.POST.get('line'),
            status_change=(ChangesetStatus.get_status_lbl(status)
                           if status and allowed_to_change_status else None),
            closing_pr=close_pr)

        if allowed_to_change_status:
            old_calculated_status = pull_request.calculated_review_status()
            # get status if set !
            if status:
                ChangesetStatusModel().set_status(c.rhodecode_db_repo.repo_id,
                                                  status,
                                                  c.rhodecode_user.user_id,
                                                  comm,
                                                  pull_request=pull_request_id)

            Session().flush()
            # we now calculate the status of pull request, and based on that
            # calculation we set the commits status
            calculated_status = pull_request.calculated_review_status()
            if old_calculated_status != calculated_status:
                PullRequestModel()._trigger_pull_request_hook(
                    pull_request, c.rhodecode_user, 'review_status_change')

            calculated_status_lbl = ChangesetStatus.get_status_lbl(
                calculated_status)

            if close_pr:
                status_completed = (calculated_status in [
                    ChangesetStatus.STATUS_APPROVED,
                    ChangesetStatus.STATUS_REJECTED
                ])
                if forced or status_completed:
                    PullRequestModel().close_pull_request(
                        pull_request_id, c.rhodecode_user)
                else:
                    h.flash(_('Closing pull request on other statuses than '
                              'rejected or approved is forbidden. '
                              'Calculated status from all reviewers '
                              'is currently: %s') % calculated_status_lbl,
                            category='warning')

        Session().commit()

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

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

        return data
Example #15
0
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='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
        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.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 comment_pull_request(request,
                         apiuser,
                         repoid,
                         pullrequestid,
                         message=Optional(None),
                         status=Optional(None),
                         userid=Optional(OAttr('apiuser'))):
    """
    Comment on the pull request specified with the `pullrequestid`,
    in the |repo| specified by the `repoid`, and optionally change the
    review status.

    :param apiuser: This is filled automatically from the |authtoken|.
    :type apiuser: AuthUser
    :param repoid: The repository name or repository ID.
    :type repoid: str or int
    :param pullrequestid: The pull request ID.
    :type pullrequestid: int
    :param message: The text content of the comment.
    :type message: str
    :param status: (**Optional**) Set the approval status of the pull
        request. Valid options are:
        * not_reviewed
        * approved
        * rejected
        * under_review
    :type status: str
    :param userid: Comment on the pull request as this user
    :type userid: Optional(str or int)

    Example output:

    .. code-block:: bash

      id : <id_given_in_input>
      result :
        {
            "pull_request_id":  "<Integer>",
            "comment_id":       "<Integer>"
        }
      error :  null
    """
    repo = get_repo_or_error(repoid)
    if not isinstance(userid, Optional):
        if (has_superadmin_permission(apiuser)
                or HasRepoPermissionAnyApi('repository.admin')(
                    user=apiuser, repo_name=repo.repo_name)):
            apiuser = get_user_or_error(userid)
        else:
            raise JSONRPCError('userid is not the same as your user')

    pull_request = get_pull_request_or_error(pullrequestid)
    if not PullRequestModel().check_user_read(pull_request, apiuser, api=True):
        raise JSONRPCError('repository `%s` does not exist' % (repoid, ))
    message = Optional.extract(message)
    status = Optional.extract(status)
    if not message and not status:
        raise JSONRPCError('message and status parameter missing')

    if (status not in (st[0] for st in ChangesetStatus.STATUSES)
            and status is not None):
        raise JSONRPCError('unknown comment status`%s`' % status)

    allowed_to_change_status = PullRequestModel().check_user_change_status(
        pull_request, apiuser)
    text = message
    if status and allowed_to_change_status:
        st_message = (('Status change %(transition_icon)s %(status)s') % {
            'transition_icon': '>',
            'status': ChangesetStatus.get_status_lbl(status)
        })
        text = message or st_message

    rc_config = SettingsModel().get_all_settings()
    renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
    comment = ChangesetCommentsModel().create(
        text=text,
        repo=pull_request.target_repo.repo_id,
        user=apiuser.user_id,
        pull_request=pull_request.pull_request_id,
        f_path=None,
        line_no=None,
        status_change=(ChangesetStatus.get_status_lbl(status)
                       if status and allowed_to_change_status else None),
        closing_pr=False,
        renderer=renderer)

    if allowed_to_change_status and status:
        ChangesetStatusModel().set_status(
            pull_request.target_repo.repo_id,
            status,
            apiuser.user_id,
            comment,
            pull_request=pull_request.pull_request_id)
        Session().flush()

    Session().commit()
    data = {
        'pull_request_id': pull_request.pull_request_id,
        'comment_id': comment.comment_id,
        'status': status
    }
    return data
Example #17
0
    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')
        close_pr = request.POST.get('save_close')

        allowed_to_change_status = self._get_is_allowed_change_status(
            pull_request)
        if status and change_status and allowed_to_change_status:
            _def = (_('Status change -> %s') %
                    ChangesetStatus.get_status_lbl(status))
            if close_pr:
                _def = _('Closing with') + ' ' + _def
            text = text or _def
        comm = ChangesetCommentsModel().create(
            text=text,
            repo=c.rhodecode_db_repo.repo_id,
            user=c.rhodecode_user.user_id,
            pull_request=pull_request_id,
            f_path=request.POST.get('f_path'),
            line_no=request.POST.get('line'),
            status_change=(ChangesetStatus.get_status_lbl(status)
                           if status and change_status
                           and allowed_to_change_status else None),
            closing_pr=close_pr)

        action_logger(self.rhodecode_user,
                      'user_commented_pull_request:%s' % pull_request_id,
                      c.rhodecode_db_repo, self.ip_addr, self.sa)

        if allowed_to_change_status:
            # get status if set !
            if status and change_status:
                ChangesetStatusModel().set_status(c.rhodecode_db_repo.repo_id,
                                                  status,
                                                  c.rhodecode_user.user_id,
                                                  comm,
                                                  pull_request=pull_request_id)

            if close_pr:
                if status in ['rejected', 'approved']:
                    PullRequestModel().close_pull_request(pull_request_id)
                    action_logger(
                        self.rhodecode_user,
                        'user_closed_pull_request:%s' % pull_request_id,
                        c.rhodecode_db_repo, self.ip_addr, self.sa)
                else:
                    h.flash(_('Closing pull request on other statuses than '
                              'rejected or approved forbidden'),
                            category='warning')

        Session().commit()

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

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

        return data
Example #18
0
    def create(self, created_by, org_repo, org_ref, other_repo, other_ref,
               revisions, reviewers, title, description=None):
        from rhodecode.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()
        #members
        for member in set(reviewers):
            _usr = self._get_user(member)
            reviewer = PullRequestReviewers(_usr, new)
            Session().add(reviewer)

        #reset state to under-review
        ChangesetStatusModel().set_status(
            repo=org_repo,
            status=ChangesetStatus.STATUS_UNDER_REVIEW,
            user=created_by_user,
            pull_request=new
        )
        revision_data = [(x.raw_id, x.message)
                         for x in map(org_repo.get_changeset, revisions)]
        #notification to reviewers
        notif = NotificationModel()

        pr_url = h.url('pullrequest_show', repo_name=other_repo.repo_name,
                       pull_request_id=new.pull_request_id,
                       qualified=True,
        )
        subject = safe_unicode(
            h.link_to(
              _('%(user)s wants you to review pull request #%(pr_id)s: %(pr_title)s') % \
                {'user': created_by_user.username,
                 'pr_title': new.title,
                 'pr_id': new.pull_request_id},
                pr_url
            )
        )
        body = description
        kwargs = {
            'pr_title': title,
            'pr_user_created': h.person(created_by_user.email),
            'pr_repo_url': h.url('summary_home', repo_name=other_repo.repo_name,
                                 qualified=True,),
            'pr_url': pr_url,
            'pr_revisions': revision_data
        }

        notif.create(created_by=created_by_user, subject=subject, body=body,
                     recipients=reviewers,
                     type_=Notification.TYPE_PULL_REQUEST, email_kwargs=kwargs)
        return new
def assert_pull_request_status(pull_request, expected_status):
    status = ChangesetStatusModel().calculated_review_status(
        pull_request=pull_request)
    assert status == expected_status
Example #20
0
def changeset_status(repo, revision):
    return ChangesetStatusModel().get_status(repo, revision)
    def comment(self, repo_name, revision):
        commit_id = revision
        status = request.POST.get('changeset_status', None)
        text = request.POST.get('text')
        if status:
            text = text or (
                _('Status change %(transition_icon)s %(status)s') % {
                    'transition_icon': '>',
                    'status': ChangesetStatus.get_status_lbl(status)
                })

        multi_commit_ids = filter(
            lambda s: s not in ['', None],
            request.POST.get('commit_ids', '').split(','),
        )

        commit_ids = multi_commit_ids or [commit_id]
        comment = None
        for current_id in filter(None, commit_ids):
            c.co = comment = ChangesetCommentsModel().create(
                text=text,
                repo=c.rhodecode_db_repo.repo_id,
                user=c.rhodecode_user.user_id,
                revision=current_id,
                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.rhodecode_db_repo.repo_id,
                        status,
                        c.rhodecode_user.user_id,
                        comment,
                        revision=current_id,
                        dont_allow_on_closed_pull_request=True)
                except StatusChangeOnClosedPullRequestError:
                    msg = _('Changing the status of a commit associated with '
                            'a closed pull request is not allowed')
                    log.exception(msg)
                    h.flash(msg, category='warning')
                    return redirect(
                        h.url('changeset_home',
                              repo_name=repo_name,
                              revision=current_id))

        # finalize, commit and redirect
        Session().commit()

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

        return data
    def _index(self, commit_id_range, method):
        c.ignorews_url = _ignorews_url
        c.context_url = _context_url
        c.fulldiff = fulldiff = request.GET.get('fulldiff')
        # get ranges of commit ids if preset
        commit_range = commit_id_range.split('...')[:2]
        enable_comments = True
        try:
            pre_load = [
                'affected_files', 'author', 'branch', 'date', 'message',
                'parents'
            ]

            if len(commit_range) == 2:
                enable_comments = False
                commits = c.rhodecode_repo.get_commits(
                    start_id=commit_range[0],
                    end_id=commit_range[1],
                    pre_load=pre_load)
                commits = list(commits)
            else:
                commits = [
                    c.rhodecode_repo.get_commit(commit_id=commit_id_range,
                                                pre_load=pre_load)
                ]

            c.commit_ranges = commits
            if not c.commit_ranges:
                raise RepositoryError(
                    'The commit range returned an empty result')
        except CommitDoesNotExistError:
            msg = _('No such commit exists for this repository')
            h.flash(msg, category='error')
            raise HTTPNotFound()
        except Exception:
            log.exception("General failure")
            raise HTTPNotFound()

        c.changes = OrderedDict()
        c.lines_added = 0
        c.lines_deleted = 0

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

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

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

                # comments from PR
                st = ChangesetStatusModel().get_statuses(
                    c.rhodecode_db_repo.repo_id,
                    commit.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 is not None, st))
                for pr in prs:
                    c.comments.extend(pr.comments)

                inlines = ChangesetCommentsModel().get_inline_comments(
                    c.rhodecode_db_repo.repo_id, revision=commit.raw_id)
                c.inline_comments.extend(inlines.iteritems())

            c.changes[commit.raw_id] = []

            commit2 = commit
            commit1 = commit.parents[0] if commit.parents else EmptyCommit()

            # fetch global flags of ignore ws or context lines
            context_lcl = get_line_ctx('', request.GET)
            ign_whitespace_lcl = get_ignore_ws('', request.GET)

            _diff = c.rhodecode_repo.get_diff(
                commit1,
                commit2,
                ignore_whitespace=ign_whitespace_lcl,
                context=context_lcl)

            # diff_limit will cut off the whole diff if the limit is applied
            # otherwise it will just hide the big files from the front-end
            diff_limit = self.cut_off_limit_diff
            file_limit = self.cut_off_limit_file

            diff_processor = diffs.DiffProcessor(_diff,
                                                 format='gitdiff',
                                                 diff_limit=diff_limit,
                                                 file_limit=file_limit,
                                                 show_full_diff=fulldiff)
            commit_changes = OrderedDict()
            if method == 'show':
                _parsed = diff_processor.prepare()
                c.limited_diff = isinstance(_parsed,
                                            diffs.LimitedDiffContainer)
                for f in _parsed:
                    c.files.append(f)
                    st = f['stats']
                    c.lines_added += st['added']
                    c.lines_deleted += st['deleted']
                    fid = h.FID(commit.raw_id, f['filename'])
                    diff = diff_processor.as_html(
                        enable_comments=enable_comments, parsed_lines=[f])
                    commit_changes[fid] = [
                        commit1.raw_id, commit2.raw_id, f['operation'],
                        f['filename'], diff, st, f
                    ]
            else:
                # downloads/raw we only need RAW diff nothing else
                diff = diff_processor.as_raw()
                commit_changes[''] = [None, None, None, None, diff, None, None]
            c.changes[commit.raw_id] = commit_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.commit_ranges) == 1:
            c.commit = c.commit_ranges[0]
            c.parent_tmpl = ''.join('# Parent  %s\n' % x.raw_id
                                    for x in c.commit.parents)
        if method == 'download':
            response.content_type = 'text/plain'
            response.content_disposition = ('attachment; filename=%s.diff' %
                                            commit_id_range[: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.commit_ranges) == 1:
                return render('changeset/changeset.html')
            else:
                c.ancestor = None
                c.target_repo = c.rhodecode_db_repo
                return render('changeset/changeset_range.html')
    def update_commits(self, pull_request):
        """
        Get the updated list of commits for the pull request
        and return the new pull request version and the list
        of commits processed by this update action
        """

        pull_request = self.__get_pull_request(pull_request)
        source_ref_type = pull_request.source_ref_parts.type
        source_ref_name = pull_request.source_ref_parts.name
        source_ref_id = pull_request.source_ref_parts.commit_id

        if not self.has_valid_update_type(pull_request):
            log.debug("Skipping update of pull request %s due to ref type: %s",
                      pull_request, source_ref_type)
            return (None, None)

        source_repo = pull_request.source_repo.scm_instance()
        source_commit = source_repo.get_commit(commit_id=source_ref_name)
        if source_ref_id == source_commit.raw_id:
            log.debug("Nothing changed in pull request %s", pull_request)
            return (None, None)

        # Finally there is a need for an update
        pull_request_version = self._create_version_from_snapshot(pull_request)
        self._link_comments_to_version(pull_request_version)

        target_ref_type = pull_request.target_ref_parts.type
        target_ref_name = pull_request.target_ref_parts.name
        target_ref_id = pull_request.target_ref_parts.commit_id
        target_repo = pull_request.target_repo.scm_instance()

        if target_ref_type in ('tag', 'branch', 'book'):
            target_commit = target_repo.get_commit(target_ref_name)
        else:
            target_commit = target_repo.get_commit(target_ref_id)

        # re-compute commit ids
        old_commit_ids = set(pull_request.revisions)
        pre_load = ["author", "branch", "date", "message"]
        commit_ranges = target_repo.compare(target_commit.raw_id,
                                            source_commit.raw_id,
                                            source_repo,
                                            merge=True,
                                            pre_load=pre_load)

        ancestor = target_repo.get_common_ancestor(target_commit.raw_id,
                                                   source_commit.raw_id,
                                                   source_repo)

        pull_request.source_ref = '%s:%s:%s' % (
            source_ref_type, source_ref_name, source_commit.raw_id)
        pull_request.target_ref = '%s:%s:%s' % (target_ref_type,
                                                target_ref_name, ancestor)
        pull_request.revisions = [
            commit.raw_id for commit in reversed(commit_ranges)
        ]
        pull_request.updated_on = datetime.datetime.now()
        Session().add(pull_request)
        new_commit_ids = set(pull_request.revisions)

        changes = self._calculate_commit_id_changes(old_commit_ids,
                                                    new_commit_ids)

        old_diff_data, new_diff_data = self._generate_update_diffs(
            pull_request, pull_request_version)

        ChangesetCommentsModel().outdate_comments(pull_request,
                                                  old_diff_data=old_diff_data,
                                                  new_diff_data=new_diff_data)

        file_changes = self._calculate_file_changes(old_diff_data,
                                                    new_diff_data)

        # Add an automatic comment to the pull request
        update_comment = ChangesetCommentsModel().create(
            text=self._render_update_message(changes, file_changes),
            repo=pull_request.target_repo,
            user=pull_request.author,
            pull_request=pull_request,
            send_email=False,
            renderer=DEFAULT_COMMENTS_RENDERER)

        # Update status to "Under Review" for added commits
        for commit_id in changes.added:
            ChangesetStatusModel().set_status(
                repo=pull_request.source_repo,
                status=ChangesetStatus.STATUS_UNDER_REVIEW,
                comment=update_comment,
                user=pull_request.author,
                pull_request=pull_request,
                revision=commit_id)

        log.debug(
            'Updated pull request %s, added_ids: %s, common_ids: %s, '
            'removed_ids: %s', pull_request.pull_request_id, changes.added,
            changes.common, changes.removed)
        log.debug('Updated pull request with the following file changes: %s',
                  file_changes)

        log.info(
            "Updated pull request %s from commit %s to commit %s, "
            "stored new version %s of this pull request.",
            pull_request.pull_request_id, source_ref_id,
            pull_request.source_ref_parts.commit_id,
            pull_request_version.pull_request_version_id)
        Session().commit()
        self._trigger_pull_request_hook(pull_request, pull_request.author,
                                        'update')
        return (pull_request_version, changes)
def test_commit_under_review_if_part_of_new_pull_request(pr_util):
    pull_request = pr_util.create_pull_request()
    for commit_id in pull_request.revisions:
        status = ChangesetStatusModel().get_status(
            repo=pr_util.source_repository, revision=commit_id)
        assert status == db.ChangesetStatus.STATUS_UNDER_REVIEW