Exemple #1
0
    def show_my(self):
        c.closed = request.GET.get('closed') or ''

        c.my_pull_requests = PullRequest.query(
            include_closed=c.closed,
            sorted=True,
        ).filter_by(owner_id=request.authuser.user_id).all()

        c.participate_in_pull_requests = []
        c.participate_in_pull_requests_todo = []
        done_status = set(
            [ChangesetStatus.STATUS_APPROVED, ChangesetStatus.STATUS_REJECTED])
        for pr in PullRequest.query(
                include_closed=c.closed,
                reviewer_id=request.authuser.user_id,
                sorted=True,
        ):
            status = pr.user_review_status(
                request.authuser.user_id)  # very inefficient!!!
            if status in done_status:
                c.participate_in_pull_requests.append(pr)
            else:
                c.participate_in_pull_requests_todo.append(pr)

        return render('/pullrequests/pullrequest_show_my.html')
Exemple #2
0
    def post(self, repo_name, pull_request_id):
        pull_request = PullRequest.get_or_404(pull_request_id)
        if pull_request.is_closed():
            raise HTTPForbidden()
        assert pull_request.other_repo.repo_name == repo_name
        #only owner or admin can update it
        owner = pull_request.author.user_id == c.authuser.user_id
        repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name)
        if not (h.HasPermissionAny('hg.admin') or repo_admin or owner):
            raise HTTPForbidden()

        _form = PullRequestPostForm()().to_python(request.POST)
        reviewers_ids = [int(s) for s in _form['review_members']]

        if _form['updaterev']:
            return self.create_update(pull_request, _form['updaterev'],
                                      _form['pullrequest_title'],
                                      _form['pullrequest_desc'], reviewers_ids)

        old_description = pull_request.description
        pull_request.title = _form['pullrequest_title']
        pull_request.description = _form['pullrequest_desc'].strip() or _(
            'No description')
        PullRequestModel().mention_from_description(pull_request,
                                                    old_description)

        PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)

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

        return redirect(pull_request.url())
Exemple #3
0
    def post(self, repo_name, pull_request_id):
        pull_request = PullRequest.get_or_404(pull_request_id)
        if pull_request.is_closed():
            raise HTTPForbidden()
        assert pull_request.other_repo.repo_name == repo_name
        #only owner or admin can update it
        owner = pull_request.author.user_id == c.authuser.user_id
        repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name)
        if not (h.HasPermissionAny('hg.admin') or repo_admin or owner):
            raise HTTPForbidden()

        _form = PullRequestPostForm()().to_python(request.POST)
        reviewers_ids = [int(s) for s in _form['review_members']]

        if _form['updaterev']:
            return self.create_update(pull_request,
                                      _form['updaterev'],
                                      _form['pullrequest_title'],
                                      _form['pullrequest_desc'],
                                      reviewers_ids)

        old_description = pull_request.description
        pull_request.title = _form['pullrequest_title']
        pull_request.description = _form['pullrequest_desc'].strip() or _('No description')
        PullRequestModel().mention_from_description(pull_request, old_description)

        PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)

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

        return redirect(pull_request.url())
Exemple #4
0
    def _get_comments(self,
                      repo_id,
                      revision=None,
                      pull_request=None,
                      inline=False):
        """
        Gets comments for either revision or pull_request_id, either inline or general.
        """
        q = Session().query(ChangesetComment)

        if inline:
            q = q.filter(ChangesetComment.line_no != None) \
                .filter(ChangesetComment.f_path != None)
        else:
            q = q.filter(ChangesetComment.line_no == None) \
                .filter(ChangesetComment.f_path == None)

        if revision is not None:
            q = q.filter(ChangesetComment.revision == revision) \
                .filter(ChangesetComment.repo_id == repo_id)
        elif pull_request is not None:
            pull_request = PullRequest.guess_instance(pull_request)
            q = q.filter(ChangesetComment.pull_request == pull_request)
        else:
            raise Exception('Please specify either revision or pull_request')

        return q.order_by(ChangesetComment.created_on).all()
    def test_close_pr(self):
        self.log_user()
        pr_id = self._create_pr()

        text = 'general comment on pullrequest'
        params = {
            'text': text,
            'save_close': 'close',
            '_session_csrf_secret_token': self.session_csrf_secret_token()
        }
        response = self.app.post(base.url(controller='pullrequests',
                                          action='comment',
                                          repo_name=base.HG_REPO,
                                          pull_request_id=pr_id),
                                 params=params,
                                 extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
        # Test response...
        assert response.status == '200 OK'

        response = self.app.get(
            base.url(controller='pullrequests',
                     action='show',
                     repo_name=base.HG_REPO,
                     pull_request_id=pr_id,
                     extra=''))
        response.mustcontain('''title (Closed)''')
        response.mustcontain(text)

        # test DB
        assert PullRequest.get(pr_id).status == PullRequest.STATUS_CLOSED
    def test_delete_pr(self):
        self.log_user()
        pr_id = self._create_pr()

        text = 'general comment on pullrequest'
        params = {
            'text': text,
            'save_delete': 'delete',
            '_session_csrf_secret_token': self.session_csrf_secret_token()
        }
        response = self.app.post(base.url(controller='pullrequests',
                                          action='comment',
                                          repo_name=base.HG_REPO,
                                          pull_request_id=pr_id),
                                 params=params,
                                 extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
        # Test response...
        assert response.status == '200 OK'

        response = self.app.get(base.url(controller='pullrequests',
                                         action='show',
                                         repo_name=base.HG_REPO,
                                         pull_request_id=pr_id,
                                         extra=''),
                                status=404)

        # test DB
        assert PullRequest.get(pr_id) is None
    def test_delete_closed_pr(self):
        self.log_user()
        pr_id = self._create_pr()

        # first close
        text = 'general comment on pullrequest'
        params = {
            'text': text,
            'save_close': 'close',
            '_session_csrf_secret_token': self.session_csrf_secret_token()
        }
        response = self.app.post(base.url(controller='pullrequests',
                                          action='comment',
                                          repo_name=base.HG_REPO,
                                          pull_request_id=pr_id),
                                 params=params,
                                 extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
        assert response.status == '200 OK'

        # attempt delete, should fail
        params = {
            'text': text,
            'save_delete': 'delete',
            '_session_csrf_secret_token': self.session_csrf_secret_token()
        }
        response = self.app.post(base.url(controller='pullrequests',
                                          action='comment',
                                          repo_name=base.HG_REPO,
                                          pull_request_id=pr_id),
                                 params=params,
                                 extra_environ={'HTTP_X_PARTIAL_XHR': '1'},
                                 status=403)

        # verify that PR still exists, in closed state
        assert PullRequest.get(pr_id).status == PullRequest.STATUS_CLOSED
    def set_status(self, repo, status, user, comment, revision=None,
                   pull_request=None, dont_allow_on_closed_pull_request=False):
        """
        Creates new status for changeset or updates the old ones bumping their
        version, leaving the current status at the value of 'status'.

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

        q = ChangesetStatus.query()
        if revision is not None:
            assert pull_request is None
            q = q.filter(ChangesetStatus.repo == repo)
            q = q.filter(ChangesetStatus.revision == revision)
            revisions = [revision]
        else:
            assert pull_request is not None
            pull_request = PullRequest.guess_instance(pull_request)
            repo = pull_request.org_repo
            q = q.filter(ChangesetStatus.repo == repo)
            q = q.filter(ChangesetStatus.revision.in_(pull_request.revisions))
            revisions = pull_request.revisions
        cur_statuses = q.all()

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

        #update all current statuses with older version
        for st in cur_statuses:
            st.version += 1

        new_statuses = []
        for rev in revisions:
            new_status = ChangesetStatus()
            new_status.version = 0 # default
            new_status.author = User.guess_instance(user)
            new_status.repo = Repository.guess_instance(repo)
            new_status.status = status
            new_status.comment = comment
            new_status.revision = rev
            new_status.pull_request = pull_request
            new_statuses.append(new_status)
            Session().add(new_status)
        return new_statuses
Exemple #9
0
 def comment(self, repo_name, pull_request_id):
     pull_request = PullRequest.get_or_404(pull_request_id)
     allowed_to_change_status = self._is_allowed_to_change_status(
         pull_request)
     return create_cs_pr_comment(
         repo_name,
         pull_request=pull_request,
         allowed_to_change_status=allowed_to_change_status)
Exemple #10
0
    def post(self, repo_name, pull_request_id):
        pull_request = PullRequest.get_or_404(pull_request_id)
        if pull_request.is_closed():
            raise HTTPForbidden()
        assert pull_request.other_repo.repo_name == repo_name
        # only owner or admin can update it
        owner = pull_request.owner_id == request.authuser.user_id
        repo_admin = h.HasRepoPermissionLevel('admin')(c.repo_name)
        if not (h.HasPermissionAny('hg.admin')() or repo_admin or owner):
            raise HTTPForbidden()

        _form = PullRequestPostForm()().to_python(request.POST)

        cur_reviewers = set(pull_request.get_reviewer_users())
        new_reviewers = set(_get_reviewer(s) for s in _form['review_members'])
        old_reviewers = set(
            _get_reviewer(s) for s in _form['org_review_members'])

        other_added = cur_reviewers - old_reviewers
        other_removed = old_reviewers - cur_reviewers

        if other_added:
            h.flash(
                _('Meanwhile, the following reviewers have been added: %s') %
                (', '.join(u.username for u in other_added)),
                category='warning')
        if other_removed:
            h.flash(
                _('Meanwhile, the following reviewers have been removed: %s') %
                (', '.join(u.username for u in other_removed)),
                category='warning')

        if _form['updaterev']:
            return self.create_new_iteration(pull_request, _form['updaterev'],
                                             _form['pullrequest_title'],
                                             _form['pullrequest_desc'],
                                             new_reviewers)

        added_reviewers = new_reviewers - old_reviewers - cur_reviewers
        removed_reviewers = (old_reviewers - new_reviewers) & cur_reviewers

        old_description = pull_request.description
        pull_request.title = _form['pullrequest_title']
        pull_request.description = _form['pullrequest_desc'].strip() or _(
            'No description')
        pull_request.owner = User.get_by_username(_form['owner'])
        user = User.get(request.authuser.user_id)

        PullRequestModel().mention_from_description(user, pull_request,
                                                    old_description)
        PullRequestModel().add_reviewers(user, pull_request, added_reviewers)
        PullRequestModel().remove_reviewers(user, pull_request,
                                            removed_reviewers)

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

        raise HTTPFound(location=pull_request.url())
 def delete(self, pull_request):
     pull_request = PullRequest.guess_instance(pull_request)
     Session().delete(pull_request)
     if pull_request.org_repo.scm_instance.alias == 'git':
         # remove a ref under refs/pull/ so that commits can be garbage-collected
         try:
             del pull_request.org_repo.scm_instance._repo["refs/pull/%d/head" % pull_request.pull_request_id]
         except KeyError:
             pass
Exemple #12
0
 def delete(self, repo_name, pull_request_id):
     pull_request = PullRequest.get_or_404(pull_request_id)
     # only owner can delete it !
     if pull_request.owner_id == request.authuser.user_id:
         PullRequestModel().delete(pull_request)
         Session().commit()
         h.flash(_('Successfully deleted pull request'), category='success')
         raise HTTPFound(location=url('my_pullrequests'))
     raise HTTPForbidden()
Exemple #13
0
    def _before(self, *args, **kwargs):
        """
        _before is called before controller methods and after __call__
        """
        c.kallithea_version = __version__
        rc_config = Setting.get_app_settings()

        # Visual options
        c.visual = AttributeDict({})

        ## DB stored
        c.visual.show_public_icon = str2bool(rc_config.get('show_public_icon'))
        c.visual.show_private_icon = str2bool(rc_config.get('show_private_icon'))
        c.visual.stylify_metatags = str2bool(rc_config.get('stylify_metatags'))
        c.visual.page_size = safe_int(rc_config.get('dashboard_items', 100))
        c.visual.admin_grid_items = safe_int(rc_config.get('admin_grid_items', 100))
        c.visual.repository_fields = str2bool(rc_config.get('repository_fields'))
        c.visual.show_version = str2bool(rc_config.get('show_version'))
        c.visual.use_gravatar = str2bool(rc_config.get('use_gravatar'))
        c.visual.gravatar_url = rc_config.get('gravatar_url')

        c.ga_code = rc_config.get('ga_code')
        # TODO: replace undocumented backwards compatibility hack with db upgrade and rename ga_code
        if c.ga_code and '<' not in c.ga_code:
            c.ga_code = '''<script type="text/javascript">
                var _gaq = _gaq || [];
                _gaq.push(['_setAccount', '%s']);
                _gaq.push(['_trackPageview']);

                (function() {
                    var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
                    ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
                    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
                    })();
            </script>''' % c.ga_code
        c.site_name = rc_config.get('title')
        c.clone_uri_tmpl = rc_config.get('clone_uri_tmpl')

        ## INI stored
        c.visual.allow_repo_location_change = str2bool(config.get('allow_repo_location_change', True))
        c.visual.allow_custom_hooks_settings = str2bool(config.get('allow_custom_hooks_settings', True))

        c.instance_id = config.get('instance_id')
        c.issues_url = config.get('bugtracker', url('issues_url'))
        # END CONFIG VARS

        c.repo_name = get_repo_slug(request)  # can be empty
        c.backends = BACKENDS.keys()
        c.unread_notifications = NotificationModel() \
                        .get_unread_cnt_for_user(request.authuser.user_id)

        self.cut_off_limit = safe_int(config.get('cut_off_limit'))

        c.my_pr_count = PullRequest.query(reviewer_id=request.authuser.user_id, include_closed=False).count()

        self.scm_model = ScmModel()
Exemple #14
0
 def delete(self, repo_name, pull_request_id):
     pull_request = PullRequest.get_or_404(pull_request_id)
     #only owner can delete it !
     if pull_request.owner.user_id == c.authuser.user_id:
         PullRequestModel().delete(pull_request)
         Session().commit()
         h.flash(_('Successfully deleted pull request'),
                 category='success')
         raise HTTPFound(location=url('my_pullrequests'))
     raise HTTPForbidden()
Exemple #15
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
    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
Exemple #17
0
    def show_my(self):
        c.closed = request.GET.get('closed') or ''

        def _filter(pr):
            s = sorted(pr, key=lambda o: o.created_on, reverse=True)
            if not c.closed:
                s = filter(lambda p: p.status != PullRequest.STATUS_CLOSED, s)
            return s

        c.my_pull_requests = _filter(PullRequest.query()\
                                .filter(PullRequest.user_id ==
                                        self.authuser.user_id)\
                                .all())

        c.participate_in_pull_requests = _filter(PullRequest.query()\
                                .join(PullRequestReviewers)\
                                .filter(PullRequestReviewers.user_id ==
                                        self.authuser.user_id)\
                                                 )

        return render('/pullrequests/pullrequest_show_my.html')
    def post(self, repo_name, pull_request_id):
        pull_request = PullRequest.get_or_404(pull_request_id)
        if pull_request.is_closed():
            raise HTTPForbidden()
        assert pull_request.other_repo.repo_name == repo_name
        #only owner or admin can update it
        owner = pull_request.owner_id == request.authuser.user_id
        repo_admin = h.HasRepoPermissionLevel('admin')(c.repo_name)
        if not (h.HasPermissionAny('hg.admin')() or repo_admin or owner):
            raise HTTPForbidden()

        _form = PullRequestPostForm()().to_python(request.POST)

        cur_reviewers = set(pull_request.get_reviewer_users())
        new_reviewers = set(_get_reviewer(s) for s in _form['review_members'])
        old_reviewers = set(_get_reviewer(s) for s in _form['org_review_members'])

        other_added = cur_reviewers - old_reviewers
        other_removed = old_reviewers - cur_reviewers

        if other_added:
            h.flash(_('Meanwhile, the following reviewers have been added: %s') %
                    (', '.join(u.username for u in other_added)),
                    category='warning')
        if other_removed:
            h.flash(_('Meanwhile, the following reviewers have been removed: %s') %
                    (', '.join(u.username for u in other_removed)),
                    category='warning')

        if _form['updaterev']:
            return self.create_new_iteration(pull_request,
                                      _form['updaterev'],
                                      _form['pullrequest_title'],
                                      _form['pullrequest_desc'],
                                      new_reviewers)

        added_reviewers = new_reviewers - old_reviewers - cur_reviewers
        removed_reviewers = (old_reviewers - new_reviewers) & cur_reviewers

        old_description = pull_request.description
        pull_request.title = _form['pullrequest_title']
        pull_request.description = _form['pullrequest_desc'].strip() or _('No description')
        pull_request.owner = User.get_by_username(_form['owner'])
        user = User.get(request.authuser.user_id)

        PullRequestModel().mention_from_description(user, pull_request, old_description)
        PullRequestModel().add_reviewers(user, pull_request, added_reviewers)
        PullRequestModel().remove_reviewers(user, pull_request, removed_reviewers)

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

        raise HTTPFound(location=pull_request.url())
Exemple #19
0
    def show_my(self):
        c.closed = request.GET.get('closed') or ''

        def _filter(pr):
            s = sorted(pr, key=lambda o: o.created_on, reverse=True)
            if not c.closed:
                s = filter(lambda p: p.status != PullRequest.STATUS_CLOSED, s)
            return s

        c.my_pull_requests = _filter(PullRequest.query() \
                                .filter(PullRequest.user_id ==
                                        self.authuser.user_id) \
                                .all())

        c.participate_in_pull_requests = _filter(PullRequest.query() \
                                .join(PullRequestReviewers) \
                                .filter(PullRequestReviewers.user_id ==
                                        self.authuser.user_id) \
                                                 )

        return render('/pullrequests/pullrequest_show_my.html')
    def set_status(self,
                   repo,
                   status,
                   user,
                   comment,
                   revision=None,
                   pull_request=None):
        """
        Creates new status for changeset or updates the old ones bumping their
        version, leaving the current status at the value of 'status'.

        :param repo:
        :param status:
        :param user:
        :param comment:
        :param revision:
        :param pull_request:
        """
        repo = Repository.guess_instance(repo)

        q = ChangesetStatus.query()
        if revision is not None:
            assert pull_request is None
            q = q.filter(ChangesetStatus.repo == repo)
            q = q.filter(ChangesetStatus.revision == revision)
            revisions = [revision]
        else:
            assert pull_request is not None
            pull_request = PullRequest.guess_instance(pull_request)
            repo = pull_request.org_repo
            q = q.filter(ChangesetStatus.repo == repo)
            q = q.filter(ChangesetStatus.revision.in_(pull_request.revisions))
            revisions = pull_request.revisions
        cur_statuses = q.all()

        # update all current statuses with older version
        for st in cur_statuses:
            st.version += 1

        new_statuses = []
        for rev in revisions:
            new_status = ChangesetStatus()
            new_status.version = 0  # default
            new_status.author = User.guess_instance(user)
            new_status.repo = Repository.guess_instance(repo)
            new_status.status = status
            new_status.comment = comment
            new_status.revision = rev
            new_status.pull_request = pull_request
            new_statuses.append(new_status)
            Session().add(new_status)
        return new_statuses
Exemple #21
0
 def get_all(self, repo_name, from_=False, closed=False):
     """Get all PRs for repo.
     Default is all PRs to the repo, PRs from the repo if from_.
     Closed PRs are only included if closed is true."""
     repo = self._get_repo(repo_name)
     q = PullRequest.query()
     if from_:
         q = q.filter(PullRequest.org_repo == repo)
     else:
         q = q.filter(PullRequest.other_repo == repo)
     if not closed:
         q = q.filter(PullRequest.status != PullRequest.STATUS_CLOSED)
     return q.order_by(PullRequest.created_on.desc()).all()
Exemple #22
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.owner = created_by_user
        Session().add(new)
        Session().flush()

        #reset state to under-review
        from kallithea.model.comment import ChangesetCommentsModel
        comment = ChangesetCommentsModel().create(
            text=u'',
            repo=org_repo,
            user=new.owner,
            pull_request=new,
            send_email=False,
            status_change=ChangesetStatus.STATUS_UNDER_REVIEW,
        )
        ChangesetStatusModel().set_status(
            org_repo,
            ChangesetStatus.STATUS_UNDER_REVIEW,
            new.owner,
            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(created_by_user, new, reviewers, mention_recipients)

        return new
Exemple #23
0
    def get_pull_request():
        from kallithea.model.db import PullRequest
        pull_request_id = action_params
        nice_id = PullRequest.make_nice_id(pull_request_id)

        deleted = user_log.repository is None
        if deleted:
            repo_name = user_log.repository_name
        else:
            repo_name = user_log.repository.repo_name

        return link_to(_('Pull request %s') % nice_id,
                    url('pullrequest_show', repo_name=repo_name,
                    pull_request_id=pull_request_id))
Exemple #24
0
    def get_pull_request():
        from kallithea.model.db import PullRequest
        pull_request_id = action_params
        nice_id = PullRequest.make_nice_id(pull_request_id)

        deleted = user_log.repository is None
        if deleted:
            repo_name = user_log.repository_name
        else:
            repo_name = user_log.repository.repo_name

        return link_to(_('Pull request %s') % nice_id,
                    url('pullrequest_show', repo_name=repo_name,
                    pull_request_id=pull_request_id))
    def show_my(self):
        c.closed = request.GET.get('closed') or ''

        c.my_pull_requests = PullRequest.query(
            include_closed=c.closed,
            sorted=True,
        ).filter_by(owner_id=request.authuser.user_id).all()

        c.participate_in_pull_requests = []
        c.participate_in_pull_requests_todo = []
        done_status = set([ChangesetStatus.STATUS_APPROVED, ChangesetStatus.STATUS_REJECTED])
        for pr in PullRequest.query(
            include_closed=c.closed,
            reviewer_id=request.authuser.user_id,
            sorted=True,
        ):
            status = pr.user_review_status(request.authuser.user_id) # very inefficient!!!
            if status in done_status:
                c.participate_in_pull_requests.append(pr)
            else:
                c.participate_in_pull_requests_todo.append(pr)

        return render('/pullrequests/pullrequest_show_my.html')
Exemple #26
0
    def show_all(self, repo_name):
        c.from_ = request.GET.get('from_') or ''
        c.closed = request.GET.get('closed') or ''
        p = safe_int(request.GET.get('page'), 1)

        q = PullRequest.query(include_closed=c.closed, sorted=True)
        if c.from_:
            q = q.filter_by(org_repo=c.db_repo)
        else:
            q = q.filter_by(other_repo=c.db_repo)
        c.pull_requests = q.all()

        c.pullrequests_pager = Page(c.pull_requests, page=p, items_per_page=100)

        return render('/pullrequests/pullrequest_show_all.html')
Exemple #27
0
    def _get_comments(self,
                      repo_id,
                      revision=None,
                      pull_request=None,
                      inline=False,
                      f_path=None,
                      line_no=None):
        """
        Gets comments for either revision or pull_request_id, either inline or general.
        If a file path and optionally line number are given, return only the matching inline comments.
        """
        if f_path is None and line_no is not None:
            raise Exception("line_no only makes sense if f_path is given.")

        if inline is None and f_path is not None:
            raise Exception("f_path only makes sense for inline comments.")

        q = Session().query(ChangesetComment)

        if inline:
            if f_path is not None:
                # inline comments for a given file...
                q = q.filter(ChangesetComment.f_path == f_path)
                if line_no is None:
                    # ... on any line
                    q = q.filter(ChangesetComment.line_no != None)
                else:
                    # ... on specific line
                    q = q.filter(ChangesetComment.line_no == line_no)
            else:
                # all inline comments
                q = q.filter(ChangesetComment.line_no != None) \
                    .filter(ChangesetComment.f_path != None)
        else:
            # all general comments
            q = q.filter(ChangesetComment.line_no == None) \
                .filter(ChangesetComment.f_path == None)

        if revision is not None:
            q = q.filter(ChangesetComment.revision == revision) \
                .filter(ChangesetComment.repo_id == repo_id)
        elif pull_request is not None:
            pull_request = PullRequest.guess_instance(pull_request)
            q = q.filter(ChangesetComment.pull_request == pull_request)
        else:
            raise Exception('Please specify either revision or pull_request')

        return q.order_by(ChangesetComment.created_on).all()
    def _get_status_query(self, repo, revision, pull_request,
                          with_revisions=False):
        repo = Repository.guess_instance(repo)

        q = ChangesetStatus.query() \
            .filter(ChangesetStatus.repo == repo)
        if not with_revisions:
            # only report the latest vote across all users! TODO: be smarter!
            q = q.filter(ChangesetStatus.version == 0)

        if revision:
            q = q.filter(ChangesetStatus.revision == revision)
        elif pull_request:
            pull_request = PullRequest.guess_instance(pull_request)
            q = q.filter(ChangesetStatus.pull_request == pull_request)
        else:
            raise Exception('Please specify revision or pull_request')
        q = q.order_by(ChangesetStatus.version.asc())
        return q
    def _get_status_query(self,
                          repo,
                          revision,
                          pull_request,
                          with_revisions=False):
        repo = Repository.guess_instance(repo)

        q = ChangesetStatus.query() \
            .filter(ChangesetStatus.repo == repo)
        if not with_revisions:
            # only report the latest vote across all users! TODO: be smarter!
            q = q.filter(ChangesetStatus.version == 0)

        if revision:
            q = q.filter(ChangesetStatus.revision == revision)
        elif pull_request:
            pull_request = PullRequest.guess_instance(pull_request)
            q = q.filter(ChangesetStatus.pull_request == pull_request)
        else:
            raise Exception('Please specify revision or pull_request')
        q = q.order_by(ChangesetStatus.version.asc())
        return q
Exemple #30
0
    def _get_comments(self, repo_id, revision=None, pull_request=None, inline=False):
        """
        Gets comments for either revision or pull_request_id, either inline or general.
        """
        q = Session().query(ChangesetComment)

        if inline:
            q = q.filter(ChangesetComment.line_no != None) \
                .filter(ChangesetComment.f_path != None)
        else:
            q = q.filter(ChangesetComment.line_no == None) \
                .filter(ChangesetComment.f_path == None)

        if revision is not None:
            q = q.filter(ChangesetComment.revision == revision) \
                .filter(ChangesetComment.repo_id == repo_id)
        elif pull_request is not None:
            pull_request = PullRequest.guess_instance(pull_request)
            q = q.filter(ChangesetComment.pull_request == pull_request)
        else:
            raise Exception('Please specify either revision or pull_request')

        return q.order_by(ChangesetComment.created_on).all()
    def test_iteration_refs(self):
        # Repo graph excerpt:
        #   o   fb95b340e0d0 webvcs
        #  /:
        # o :   41d2568309a0 default
        # : :
        # : o   5ec21f21aafe webvcs
        # : :
        # : o   9e6119747791 webvcs
        # : :
        # o :   3d1091ee5a53 default
        # :/
        # o     948da46b29c1 default

        self.log_user()

        # create initial PR
        response = self.app.post(
            url(controller='pullrequests', action='create', repo_name=HG_REPO),
            {
                'org_repo': HG_REPO,
                'org_ref':
                'rev:9e6119747791:9e6119747791ff886a5abe1193a730b6bf874e1c',
                'other_repo': HG_REPO,
                'other_ref':
                'branch:default:3d1091ee5a533b1f4577ec7d8a226bb315fb1336',
                'pullrequest_title': 'title',
                'pullrequest_desc': 'description',
                '_authentication_token': self.authentication_token(),
            },
            status=302)
        pr1_id = int(
            re.search('/pull-request/(\d+)/', response.location).group(1))
        pr1 = PullRequest.get(pr1_id)

        assert pr1.org_ref == 'branch:webvcs:9e6119747791ff886a5abe1193a730b6bf874e1c'
        assert pr1.other_ref == 'branch:default:948da46b29c125838a717f6a8496eb409717078d'

        Session().rollback(
        )  # invalidate loaded PR objects before issuing next request.

        # create PR 2 (new iteration with same ancestor)
        response = self.app.post(
            url(controller='pullrequests',
                action='post',
                repo_name=HG_REPO,
                pull_request_id=pr1_id), {
                    'updaterev': '5ec21f21aafe95220f1fc4843a4a57c378498b71',
                    'pullrequest_title': 'title',
                    'pullrequest_desc': 'description',
                    'owner': TEST_USER_REGULAR_LOGIN,
                    '_authentication_token': self.authentication_token(),
                },
            status=302)
        pr2_id = int(
            re.search('/pull-request/(\d+)/', response.location).group(1))
        pr1 = PullRequest.get(pr1_id)
        pr2 = PullRequest.get(pr2_id)

        assert pr2_id != pr1_id
        assert pr1.status == PullRequest.STATUS_CLOSED
        assert pr2.org_ref == 'branch:webvcs:5ec21f21aafe95220f1fc4843a4a57c378498b71'
        assert pr2.other_ref == pr1.other_ref

        Session().rollback(
        )  # invalidate loaded PR objects before issuing next request.

        # create PR 3 (new iteration with new ancestor)
        response = self.app.post(
            url(controller='pullrequests',
                action='post',
                repo_name=HG_REPO,
                pull_request_id=pr2_id), {
                    'updaterev': 'fb95b340e0d03fa51f33c56c991c08077c99303e',
                    'pullrequest_title': 'title',
                    'pullrequest_desc': 'description',
                    'owner': TEST_USER_REGULAR_LOGIN,
                    '_authentication_token': self.authentication_token(),
                },
            status=302)
        pr3_id = int(
            re.search('/pull-request/(\d+)/', response.location).group(1))
        pr2 = PullRequest.get(pr2_id)
        pr3 = PullRequest.get(pr3_id)

        assert pr3_id != pr2_id
        assert pr2.status == PullRequest.STATUS_CLOSED
        assert pr3.org_ref == 'branch:webvcs:fb95b340e0d03fa51f33c56c991c08077c99303e'
        assert pr3.other_ref == 'branch:default:41d2568309a05f422cffb8008e599d385f8af439'
Exemple #32
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
Exemple #33
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')
Exemple #34
0
    def comment(self, repo_name, pull_request_id):
        pull_request = PullRequest.get_or_404(pull_request_id)

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

        allowed_to_change_status = self._get_is_allowed_change_status(pull_request)
        if not allowed_to_change_status:
            if status or close_pr:
                h.flash(_('No permission to change pull request status'), 'error')
                raise HTTPForbidden()

        if 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': url('my_pullrequests'), # or repo pr list?
                }
                raise HTTPFound(location=url('my_pullrequests')) # or repo pr list?
            raise HTTPForbidden()

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

        comment = create_comment(
            text,
            status,
            pull_request_id=pull_request_id,
            f_path=f_path,
            line_no=line_no,
            closing_pr=close_pr,
        )

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

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

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

        if not request.environ.get('HTTP_X_PARTIAL_XHR'):
            raise HTTPFound(location=pull_request.url())

        data = {
           'target_id': h.safeid(h.safe_unicode(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
Exemple #35
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
Exemple #36
0
    def create(self,
               text,
               repo,
               author,
               revision=None,
               pull_request=None,
               f_path=None,
               line_no=None,
               status_change=None,
               closing_pr=False,
               send_email=True):
        """
        Creates a new comment for either a changeset or a pull request.
        status_change and closing_pr is only for the optional email.

        Returns the created comment.
        """
        if not status_change and not text:
            log.warning('Missing text for comment, skipping...')
            return None

        repo = Repository.guess_instance(repo)
        author = User.guess_instance(author)
        comment = ChangesetComment()
        comment.repo = repo
        comment.author = author
        comment.text = text
        comment.f_path = f_path
        comment.line_no = line_no

        if revision is not None:
            comment.revision = revision
        elif pull_request is not None:
            pull_request = PullRequest.guess_instance(pull_request)
            comment.pull_request = pull_request
        else:
            raise Exception('Please specify revision or pull_request_id')

        Session().add(comment)
        Session().flush()

        if send_email:
            (subj, body, recipients, notification_type,
             email_kwargs) = self._get_notification_data(
                 repo,
                 comment,
                 author,
                 comment_text=text,
                 line_no=line_no,
                 revision=revision,
                 pull_request=pull_request,
                 status_change=status_change,
                 closing_pr=closing_pr)
            email_kwargs['is_mention'] = False
            # create notification objects, and emails
            NotificationModel().create(
                created_by=author,
                subject=subj,
                body=body,
                recipients=recipients,
                type_=notification_type,
                email_kwargs=email_kwargs,
            )

            mention_recipients = extract_mentioned_users(body).difference(
                recipients)
            if mention_recipients:
                email_kwargs['is_mention'] = True
                subj = _('[Mention]') + ' ' + subj
                # FIXME: this subject is wrong and unused!
                NotificationModel().create(created_by=author,
                                           subject=subj,
                                           body=body,
                                           recipients=mention_recipients,
                                           type_=notification_type,
                                           email_kwargs=email_kwargs)

        return comment
Exemple #37
0
    def _before(self, *args, **kwargs):
        """
        _before is called before controller methods and after __call__
        """
        if request.needs_csrf_check:
            # CSRF protection: Whenever a request has ambient authority (whether
            # through a session cookie or its origin IP address), it must include
            # the correct token, unless the HTTP method is GET or HEAD (and thus
            # guaranteed to be side effect free. In practice, the only situation
            # where we allow side effects without ambient authority is when the
            # authority comes from an API key; and that is handled above.
            from kallithea.lib import helpers as h
            token = request.POST.get(h.session_csrf_secret_name)
            if not token or token != h.session_csrf_secret_token():
                log.error('CSRF check failed')
                raise webob.exc.HTTPForbidden()

        c.kallithea_version = __version__
        rc_config = Setting.get_app_settings()

        # Visual options
        c.visual = AttributeDict({})

        ## DB stored
        c.visual.show_public_icon = str2bool(rc_config.get('show_public_icon'))
        c.visual.show_private_icon = str2bool(
            rc_config.get('show_private_icon'))
        c.visual.stylify_metalabels = str2bool(
            rc_config.get('stylify_metalabels'))
        c.visual.page_size = safe_int(rc_config.get('dashboard_items', 100))
        c.visual.admin_grid_items = safe_int(
            rc_config.get('admin_grid_items', 100))
        c.visual.repository_fields = str2bool(
            rc_config.get('repository_fields'))
        c.visual.show_version = str2bool(rc_config.get('show_version'))
        c.visual.use_gravatar = str2bool(rc_config.get('use_gravatar'))
        c.visual.gravatar_url = rc_config.get('gravatar_url')

        c.ga_code = rc_config.get('ga_code')
        # TODO: replace undocumented backwards compatibility hack with db upgrade and rename ga_code
        if c.ga_code and '<' not in c.ga_code:
            c.ga_code = '''<script type="text/javascript">
                var _gaq = _gaq || [];
                _gaq.push(['_setAccount', '%s']);
                _gaq.push(['_trackPageview']);

                (function() {
                    var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
                    ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
                    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
                    })();
            </script>''' % c.ga_code
        c.site_name = rc_config.get('title')
        c.clone_uri_tmpl = rc_config.get(
            'clone_uri_tmpl') or Repository.DEFAULT_CLONE_URI
        c.clone_ssh_tmpl = rc_config.get(
            'clone_ssh_tmpl') or Repository.DEFAULT_CLONE_SSH

        ## INI stored
        c.visual.allow_repo_location_change = str2bool(
            config.get('allow_repo_location_change', True))
        c.visual.allow_custom_hooks_settings = str2bool(
            config.get('allow_custom_hooks_settings', True))
        c.ssh_enabled = str2bool(config.get('ssh_enabled', False))

        c.instance_id = config.get('instance_id')
        c.issues_url = config.get('bugtracker', url('issues_url'))
        # END CONFIG VARS

        c.repo_name = get_repo_slug(request)  # can be empty
        c.backends = list(BACKENDS)

        self.cut_off_limit = safe_int(config.get('cut_off_limit'))

        c.my_pr_count = PullRequest.query(reviewer_id=request.authuser.user_id,
                                          include_closed=False).count()

        self.scm_model = ScmModel()
 def close_pull_request(self, pull_request):
     pull_request = PullRequest.guess_instance(pull_request)
     pull_request.status = PullRequest.STATUS_CLOSED
     pull_request.updated_on = datetime.datetime.now()
    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)
Exemple #40
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')
Exemple #41
0
    def comment(self, repo_name, pull_request_id):
        pull_request = PullRequest.get_or_404(pull_request_id)

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

        allowed_to_change_status = self._get_is_allowed_change_status(pull_request)
        if not allowed_to_change_status:
            if status or close_pr:
                h.flash(_('No permission to change pull request status'), 'error')
                raise HTTPForbidden()

        if delete == "delete":
            if (pull_request.owner.user_id == c.authuser.user_id or
                h.HasPermissionAny('hg.admin')() or
                h.HasRepoPermissionAny('repository.admin')(pull_request.org_repo.repo_name) or
                h.HasRepoPermissionAny('repository.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': url('my_pullrequests'), # or repo pr list?
                }
                raise HTTPFound(location=url('my_pullrequests')) # or repo pr list?
            raise HTTPForbidden()

        text = request.POST.get('text', '').strip()
        if close_pr:
            text = _('Closing.') + '\n' + text

        comment = create_comment(
            text,
            status,
            pull_request_id=pull_request_id,
            f_path=f_path,
            line_no=line_no,
            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 status:
            ChangesetStatusModel().set_status(
                c.db_repo.repo_id,
                status,
                c.authuser.user_id,
                comment,
                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'):
            raise HTTPFound(location=pull_request.url())

        data = {
           'target_id': h.safeid(h.safe_unicode(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
Exemple #42
0
 def get_pullrequest_cnt_for_user(self, user):
     return PullRequest.query()\
                             .join(PullRequestReviewers)\
                             .filter(PullRequestReviewers.user_id == user)\
                             .filter(PullRequest.status != PullRequest.STATUS_CLOSED)\
                             .count()
Exemple #43
0
    def create(self, text, repo, author, revision=None, pull_request=None,
               f_path=None, line_no=None, status_change=None, closing_pr=False,
               send_email=True):
        """
        Creates a new comment for either a changeset or a pull request.
        status_change and closing_pr is only for the optional email.

        Returns the created comment.
        """
        if not status_change and not text:
            log.warning('Missing text for comment, skipping...')
            return None

        repo = Repository.guess_instance(repo)
        author = User.guess_instance(author)
        comment = ChangesetComment()
        comment.repo = repo
        comment.author = author
        comment.text = text
        comment.f_path = f_path
        comment.line_no = line_no

        if revision is not None:
            comment.revision = revision
        elif pull_request is not None:
            pull_request = PullRequest.guess_instance(pull_request)
            comment.pull_request = pull_request
        else:
            raise Exception('Please specify revision or pull_request_id')

        Session().add(comment)
        Session().flush()

        if send_email:
            (subj, body, recipients, notification_type,
             email_kwargs) = self._get_notification_data(
                                repo, comment, author,
                                comment_text=text,
                                line_no=line_no,
                                revision=revision,
                                pull_request=pull_request,
                                status_change=status_change,
                                closing_pr=closing_pr)
            email_kwargs['is_mention'] = False
            # create notification objects, and emails
            NotificationModel().create(
                created_by=author, subject=subj, body=body,
                recipients=recipients, type_=notification_type,
                email_kwargs=email_kwargs,
            )

            mention_recipients = extract_mentioned_users(body).difference(recipients)
            if mention_recipients:
                email_kwargs['is_mention'] = True
                subj = _('[Mention]') + ' ' + subj
                # FIXME: this subject is wrong and unused!
                NotificationModel().create(
                    created_by=author, subject=subj, body=body,
                    recipients=mention_recipients,
                    type_=notification_type,
                    email_kwargs=email_kwargs
                )

        return comment
Exemple #44
0
 def get_pull_requests(self, repo):
     repo = Repository.guess_instance(repo)
     return PullRequest.query() \
             .filter(PullRequest.other_repo == repo) \
             .filter(PullRequest.status != PullRequest.STATUS_CLOSED).count()