Files @ 7b5bf8e01325
Branch filter:

Location: kallithea/rhodecode/model/pull_request.py - annotation

Mads Kiilerich
Fix inconsistent casing
f29469677319
f29469677319
58c529332e7e
58c529332e7e
f29469677319
f29469677319
f29469677319
f29469677319
f29469677319
f29469677319
f29469677319
f29469677319
f29469677319
f29469677319
f29469677319
f29469677319
f29469677319
f29469677319
f29469677319
f29469677319
f29469677319
f29469677319
f29469677319
f29469677319
f29469677319
f29469677319
58c529332e7e
bf96fd1920c1
7b092b919f4f
f29469677319
f29469677319
7b092b919f4f
ae5ac36cdf83
f29469677319
5d1d25c1c700
5d1d25c1c700
f29469677319
f29469677319
f29469677319
ae5ac36cdf83
3c91ec4419a6
3bf057a7f7e8
f29469677319
f29469677319
f29469677319
f29469677319
f29469677319
17893d61792a
17893d61792a
58c529332e7e
58c529332e7e
58c529332e7e
1bc579bcd67a
1bc579bcd67a
14e9a06c37a7
14e9a06c37a7
1c4505e3be5b
14e9a06c37a7
1bc579bcd67a
5d1d25c1c700
5d1d25c1c700
5d1d25c1c700
1c2ba03c42b4
1bc579bcd67a
1c2ba03c42b4
1c2ba03c42b4
f29469677319
f29469677319
1c2ba03c42b4
f29469677319
1c2ba03c42b4
f29469677319
f29469677319
f29469677319
f29469677319
1bc579bcd67a
bbe21df7ad48
1c2ba03c42b4
f29469677319
1c4505e3be5b
f29469677319
f29469677319
bbe21df7ad48
f29469677319
5d1d25c1c700
5d1d25c1c700
5d1d25c1c700
5d1d25c1c700
5d1d25c1c700
5d1d25c1c700
5d1d25c1c700
bbe21df7ad48
bbe21df7ad48
f29469677319
f29469677319
1bc579bcd67a
493646d3146f
493646d3146f
493646d3146f
493646d3146f
f29469677319
f29469677319
e76859961cbc
f29469677319
e76859961cbc
f29469677319
493646d3146f
f29469677319
f29469677319
f29469677319
493646d3146f
493646d3146f
493646d3146f
493646d3146f
493646d3146f
493646d3146f
bbe21df7ad48
493646d3146f
1c4505e3be5b
1c2ba03c42b4
f29469677319
493646d3146f
f29469677319
3bf057a7f7e8
3f50a5e8fc4d
3f50a5e8fc4d
3f50a5e8fc4d
3f50a5e8fc4d
3f50a5e8fc4d
3f50a5e8fc4d
3f50a5e8fc4d
3f50a5e8fc4d
3f50a5e8fc4d
3f50a5e8fc4d
3f50a5e8fc4d
3f50a5e8fc4d
3f50a5e8fc4d
3f50a5e8fc4d
3f50a5e8fc4d
3f50a5e8fc4d
3f50a5e8fc4d
3f50a5e8fc4d
bbe21df7ad48
3f50a5e8fc4d
3f50a5e8fc4d
3f50a5e8fc4d
3f50a5e8fc4d
3f50a5e8fc4d
3f50a5e8fc4d
3f50a5e8fc4d
bbe21df7ad48
3f50a5e8fc4d
49a4864b11c1
49a4864b11c1
49a4864b11c1
49a4864b11c1
58c529332e7e
58c529332e7e
58c529332e7e
58c529332e7e
bbe21df7ad48
58c529332e7e
2053053e0882
3bf057a7f7e8
a07296564f6b
a07296564f6b
3bf057a7f7e8
3bf057a7f7e8
3bf057a7f7e8
3bf057a7f7e8
3bf057a7f7e8
3bf057a7f7e8
3bf057a7f7e8
c2a206162062
a07296564f6b
c2a206162062
2c0208bd686b
3c91ec4419a6
3c91ec4419a6
3c91ec4419a6
3c91ec4419a6
3c91ec4419a6
3c91ec4419a6
3c91ec4419a6
bd5420ea396b
f73a63831d3f
f73a63831d3f
3761a66b8add
a327492d84df
5dcfa6304f88
f73a63831d3f
f73a63831d3f
3761a66b8add
a327492d84df
3bf057a7f7e8
2c0208bd686b
2c0208bd686b
c9b0f1d363c7
c9b0f1d363c7
c9b0f1d363c7
c9b0f1d363c7
c9b0f1d363c7
2c0208bd686b
bf96fd1920c1
2c0208bd686b
c9b0f1d363c7
fc08484c5bf3
2053053e0882
2053053e0882
2053053e0882
a07296564f6b
a07296564f6b
a07296564f6b
630c98f52416
630c98f52416
a07296564f6b
2053053e0882
2053053e0882
2053053e0882
2053053e0882
2053053e0882
2053053e0882
2053053e0882
2c0208bd686b
2c0208bd686b
2c0208bd686b
2c0208bd686b
2c0208bd686b
2c0208bd686b
2c0208bd686b
a07296564f6b
a07296564f6b
3bf057a7f7e8
a07296564f6b
3bf057a7f7e8
2053053e0882
3bf057a7f7e8
3c91ec4419a6
3bf057a7f7e8
3bf057a7f7e8
3bf057a7f7e8
3bf057a7f7e8
3bf057a7f7e8
3bf057a7f7e8
3bf057a7f7e8
3bf057a7f7e8
3bf057a7f7e8
3bf057a7f7e8
3bf057a7f7e8
3bf057a7f7e8
3bf057a7f7e8
3ac76dfdab8e
3ac76dfdab8e
2053053e0882
2053053e0882
a07296564f6b
# -*- coding: utf-8 -*-
"""
    rhodecode.model.pull_request
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    pull request model for RhodeCode

    :created_on: Jun 6, 2012
    :author: marcink
    :copyright: (C) 2012-2012 Marcin Kuzminski <marcin@python-works.com>
    :license: GPLv3, see COPYING for more details.
"""
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import logging
import datetime
import re

from pylons.i18n.translation import _

from rhodecode.model.meta import Session
from rhodecode.lib import helpers as h, unionrepo
from rhodecode.model import BaseModel
from rhodecode.model.db import PullRequest, PullRequestReviewers, Notification,\
    ChangesetStatus
from rhodecode.model.notification import NotificationModel
from rhodecode.lib.utils2 import safe_unicode

from rhodecode.lib.vcs.utils.hgcompat import scmutil
from rhodecode.lib.vcs.utils import safe_str

log = logging.getLogger(__name__)


class PullRequestModel(BaseModel):

    cls = PullRequest

    def __get_pull_request(self, pull_request):
        return self._get_instance(PullRequest, pull_request)

    def get_all(self, repo):
        repo = self._get_repo(repo)
        return PullRequest.query()\
                .filter(PullRequest.other_repo == repo)\
                .order_by(PullRequest.created_on.desc())\
                .all()

    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 update_reviewers(self, pull_request, reviewers_ids):
        reviewers_ids = set(reviewers_ids)
        pull_request = self.__get_pull_request(pull_request)
        current_reviewers = PullRequestReviewers.query()\
                            .filter(PullRequestReviewers.pull_request==
                                   pull_request)\
                            .all()
        current_reviewers_ids = set([x.user.user_id for x in current_reviewers])

        to_add = reviewers_ids.difference(current_reviewers_ids)
        to_remove = current_reviewers_ids.difference(reviewers_ids)

        log.debug("Adding %s reviewers" % to_add)
        log.debug("Removing %s reviewers" % to_remove)

        for uid in to_add:
            _usr = self._get_user(uid)
            reviewer = PullRequestReviewers(_usr, pull_request)
            Session().add(reviewer)

        for uid in to_remove:
            reviewer = PullRequestReviewers.query()\
                    .filter(PullRequestReviewers.user_id==uid,
                            PullRequestReviewers.pull_request==pull_request)\
                    .scalar()
            if reviewer:
                Session().delete(reviewer)

    def delete(self, pull_request):
        pull_request = self.__get_pull_request(pull_request)
        Session().delete(pull_request)

    def close_pull_request(self, pull_request):
        pull_request = self.__get_pull_request(pull_request)
        pull_request.status = PullRequest.STATUS_CLOSED
        pull_request.updated_on = datetime.datetime.now()
        Session().add(pull_request)

    def _get_changesets(self, alias, org_repo, org_ref, other_repo, other_ref, merge):
        """
        Returns a list of changesets that can be merged from org_repo@org_ref
        to other_repo@other_ref ... and the ancestor that would be used for merge

        :param org_repo:
        :param org_ref:
        :param other_repo:
        :param other_ref:
        :param tmp:
        """

        ancestor = None

        if alias == 'hg':
            # lookup up the exact node id
            _revset_predicates = {
                    'branch': 'branch',
                    'book': 'bookmark',
                    'tag': 'tag',
                    'rev': 'id',
                }

            org_rev_spec = "max(%s('%s'))" % (_revset_predicates[org_ref[0]],
                                              safe_str(org_ref[1]))
            org_revs = scmutil.revrange(org_repo._repo, [org_rev_spec])
            org_rev = org_repo._repo[org_revs[-1] if org_revs else -1].hex()

            other_rev_spec = "max(%s('%s'))" % (_revset_predicates[other_ref[0]],
                                                safe_str(other_ref[1]))
            other_revs = scmutil.revrange(other_repo._repo, [other_rev_spec])
            other_rev = other_repo._repo[other_revs[-1] if other_revs else -1].hex()

            #case two independent repos
            if org_repo != other_repo:
                hgrepo = unionrepo.unionrepository(other_repo.baseui,
                                                   other_repo.path,
                                                   org_repo.path)
                # all the changesets we are looking for will be in other_repo,
                # so rev numbers from hgrepo can be used in other_repo

            #no remote compare do it on the same repository
            else:
                hgrepo = other_repo._repo

            if merge:
                revs = ["ancestors(id('%s')) and not ancestors(id('%s')) and not id('%s')" %
                        (other_rev, org_rev, org_rev)]

                ancestors = scmutil.revrange(hgrepo,
                     ["ancestor(id('%s'), id('%s'))" % (org_rev, other_rev)])
                if ancestors:
                    # pick arbitrary ancestor - but there is usually only one
                    ancestor = hgrepo[ancestors[0]].hex()
            else:
                # TODO: have both + and - changesets
                revs = ["id('%s') :: id('%s') - id('%s')" %
                        (org_rev, other_rev, org_rev)]

            changesets = [other_repo.get_changeset(cs)
                          for cs in scmutil.revrange(hgrepo, revs)]

        elif alias == 'git':
            assert org_repo == other_repo, (org_repo, other_repo) # no git support for different repos
            so, se = org_repo.run_git_command(
                'log --reverse --pretty="format: %%H" -s -p %s..%s' % (org_ref[1],
                                                                       other_ref[1])
            )
            changesets = [org_repo.get_changeset(cs)
                          for cs in re.findall(r'[0-9a-fA-F]{40}', so)]

        return changesets, ancestor

    def get_compare_data(self, org_repo, org_ref, other_repo, other_ref, merge):
        """
        Returns incoming changesets for mercurial repositories

        :param org_repo:
        :param org_ref:
        :param other_repo:
        :param other_ref:
        """

        if len(org_ref) != 2 or not isinstance(org_ref, (list, tuple)):
            raise Exception('org_ref must be a two element list/tuple')

        if len(other_ref) != 2 or not isinstance(org_ref, (list, tuple)):
            raise Exception('other_ref must be a two element list/tuple')

        cs_ranges, ancestor = self._get_changesets(org_repo.scm_instance.alias,
                                                   org_repo.scm_instance, org_ref,
                                                   other_repo.scm_instance, other_ref,
                                                   merge)
        return cs_ranges, ancestor