# HG changeset patch # User Marcin Kuzminski # Date 2012-06-10 16:15:00 # Node ID 1bc579bcd67aeb053dbc509ccf36b792ce544218 # Parent ad19dfcdb1cc92bdb29bce8545761dfddb123ea5 - pull request generates overview based on it's params - added page to show all pull-requests for a repository - db schema changes to support comments and inline comments for pull-requests diff --git a/rhodecode/config/routing.py b/rhodecode/config/routing.py --- a/rhodecode/config/routing.py +++ b/rhodecode/config/routing.py @@ -451,6 +451,12 @@ def make_map(config): action='show', conditions=dict(function=check_repo, method=["GET"])) + rmap.connect('pullrequest_show_all', + '/{repo_name:.*}/pull-request', + controller='pullrequests', + action='show_all', conditions=dict(function=check_repo, + method=["GET"])) + rmap.connect('summary_home', '/{repo_name:.*}/summary', controller='summary', conditions=dict(function=check_repo)) diff --git a/rhodecode/controllers/changeset.py b/rhodecode/controllers/changeset.py --- a/rhodecode/controllers/changeset.py +++ b/rhodecode/controllers/changeset.py @@ -295,7 +295,7 @@ class ChangesetController(BaseRepoContro ) # count inline comments - for _, lines in c.inline_comments: + for __, lines in c.inline_comments: for comments in lines.values(): c.inline_cnt += len(comments) diff --git a/rhodecode/controllers/pullrequests.py b/rhodecode/controllers/pullrequests.py --- a/rhodecode/controllers/pullrequests.py +++ b/rhodecode/controllers/pullrequests.py @@ -24,6 +24,9 @@ # along with this program. If not, see . import logging import traceback +import binascii + +from webob.exc import HTTPNotFound from pylons import request, response, session, tmpl_context as c, url from pylons.controllers.util import abort, redirect @@ -32,9 +35,13 @@ from pylons.i18n.translation import _ from rhodecode.lib.base import BaseRepoController, render from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator from rhodecode.lib import helpers as h -from rhodecode.model.db import User, PullRequest +from rhodecode.lib import diffs +from rhodecode.model.db import User, PullRequest, Repository, ChangesetStatus from rhodecode.model.pull_request import PullRequestModel from rhodecode.model.meta import Session +from rhodecode.model.repo import RepoModel +from rhodecode.model.comment import ChangesetCommentsModel +from rhodecode.model.changeset_status import ChangesetStatusModel log = logging.getLogger(__name__) @@ -50,12 +57,12 @@ class PullrequestsController(BaseRepoCon def _get_repo_refs(self, repo): hist_l = [] - branches_group = ([('branch:' + k, k) for k in repo.branches.keys()], - _("Branches")) - bookmarks_group = ([('book:' + k, k) for k in repo.bookmarks.keys()], - _("Bookmarks")) - tags_group = ([('tag:' + k, k) for k in repo.tags.keys()], - _("Tags")) + branches_group = ([('branch:%s:%s' % (k, v), k) for + k, v in repo.branches.iteritems()], _("Branches")) + bookmarks_group = ([('book:%s:%s' % (k, v), k) for + k, v in repo.bookmarks.iteritems()], _("Bookmarks")) + tags_group = ([('tag:%s:%s' % (k, v), k) for + k, v in repo.tags.iteritems()], _("Tags")) hist_l.append(bookmarks_group) hist_l.append(branches_group) @@ -63,6 +70,11 @@ class PullrequestsController(BaseRepoCon return hist_l + def show_all(self, repo_name): + c.pull_requests = PullRequestModel().get_all(repo_name) + c.repo_name = repo_name + return render('/pullrequests/pullrequest_show_all.html') + def index(self): org_repo = c.rhodecode_db_repo c.org_refs = self._get_repo_refs(c.rhodecode_repo) @@ -128,6 +140,118 @@ class PullrequestsController(BaseRepoCon return redirect(url('changelog_home', repo_name=repo_name)) + def _get_changesets(self, org_repo, org_ref, other_repo, other_ref, tmp): + changesets = [] + #case two independent repos + if org_repo != other_repo: + common, incoming, rheads = tmp + + if not incoming: + revs = [] + else: + revs = org_repo._repo.changelog.findmissing(common, rheads) + + for cs in reversed(map(binascii.hexlify, revs)): + changesets.append(org_repo.get_changeset(cs)) + else: + revs = ['ancestors(%s) and not ancestors(%s)' % (org_ref[1], + other_ref[1])] + from mercurial import scmutil + out = scmutil.revrange(org_repo._repo, revs) + for cs in reversed(out): + changesets.append(org_repo.get_changeset(cs)) + + return changesets + + def _get_discovery(self, org_repo, org_ref, other_repo, other_ref): + from mercurial import discovery + other = org_repo._repo + repo = other_repo._repo + tip = other[org_ref[1]] + log.debug('Doing discovery for %s@%s vs %s@%s' % ( + org_repo, org_ref, other_repo, other_ref) + ) + log.debug('Filter heads are %s[%s]' % (tip, org_ref[1])) + tmp = discovery.findcommonincoming( + repo=repo, # other_repo we check for incoming + remote=other, # org_repo source for incoming + heads=[tip.node()], + force=False + ) + return tmp + + def _compare(self, pull_request): + + org_repo = pull_request.org_repo + org_ref_type, org_ref_, org_ref = pull_request.org_ref.split(':') + other_repo = pull_request.other_repo + other_ref_type, other_ref, other_ref_ = pull_request.other_ref.split(':') + + org_ref = (org_ref_type, org_ref) + other_ref = (other_ref_type, other_ref) + + c.org_repo = org_repo + c.other_repo = other_repo + + discovery_data = self._get_discovery(org_repo.scm_instance, + org_ref, + other_repo.scm_instance, + other_ref) + c.cs_ranges = self._get_changesets(org_repo.scm_instance, + org_ref, + other_repo.scm_instance, + other_ref, + discovery_data) + + c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in + c.cs_ranges]) + # defines that we need hidden inputs with changesets + c.as_form = request.GET.get('as_form', False) + if request.environ.get('HTTP_X_PARTIAL_XHR'): + return render('compare/compare_cs.html') + + c.org_ref = org_ref[1] + c.other_ref = other_ref[1] + # diff needs to have swapped org with other to generate proper diff + _diff = diffs.differ(other_repo, other_ref, org_repo, org_ref, + discovery_data) + diff_processor = diffs.DiffProcessor(_diff, format='gitdiff') + _parsed = diff_processor.prepare() + + c.files = [] + c.changes = {} + + for f in _parsed: + fid = h.FID('', f['filename']) + c.files.append([fid, f['operation'], f['filename'], f['stats']]) + diff = diff_processor.as_html(enable_comments=False, diff_lines=[f]) + c.changes[fid] = [f['operation'], f['filename'], diff] + def show(self, repo_name, pull_request_id): + repo_model = RepoModel() + c.users_array = repo_model.get_users_js() + c.users_groups_array = repo_model.get_users_groups_js() c.pull_request = PullRequest.get(pull_request_id) + ##TODO: need more generic solution + self._compare(c.pull_request) + + # inline comments + c.inline_cnt = 0 + c.inline_comments = ChangesetCommentsModel()\ + .get_inline_comments(c.rhodecode_db_repo.repo_id, + pull_request=pull_request_id) + # count inline comments + for __, lines in c.inline_comments: + for comments in lines.values(): + c.inline_cnt += len(comments) + # comments + c.comments = ChangesetCommentsModel()\ + .get_comments(c.rhodecode_db_repo.repo_id, + pull_request=pull_request_id) + + # changeset(pull-request) statuse + c.current_changeset_status = ChangesetStatusModel()\ + .get_status(c.rhodecode_db_repo.repo_id, + pull_request=pull_request_id) + c.changeset_statuses = ChangesetStatus.STATUSES return render('/pullrequests/pullrequest_show.html') diff --git a/rhodecode/lib/base.py b/rhodecode/lib/base.py --- a/rhodecode/lib/base.py +++ b/rhodecode/lib/base.py @@ -204,7 +204,7 @@ class BaseRepoController(BaseController) super(BaseRepoController, self).__before__() if c.repo_name: - c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name) + dbr = c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name) c.rhodecode_repo = c.rhodecode_db_repo.scm_instance if c.rhodecode_repo is None: @@ -213,5 +213,7 @@ class BaseRepoController(BaseController) redirect(url('home')) - c.repository_followers = self.scm_model.get_followers(c.repo_name) - c.repository_forks = self.scm_model.get_forks(c.repo_name) + # some globals counter for menu + c.repository_followers = self.scm_model.get_followers(dbr) + c.repository_forks = self.scm_model.get_forks(dbr) + c.repository_pull_requests = self.scm_model.get_pull_requests(dbr) \ No newline at end of file diff --git a/rhodecode/model/changeset_status.py b/rhodecode/model/changeset_status.py --- a/rhodecode/model/changeset_status.py +++ b/rhodecode/model/changeset_status.py @@ -26,7 +26,7 @@ import logging from rhodecode.model import BaseModel -from rhodecode.model.db import ChangesetStatus +from rhodecode.model.db import ChangesetStatus, PullRequest log = logging.getLogger(__name__) @@ -36,23 +36,37 @@ class ChangesetStatusModel(BaseModel): def __get_changeset_status(self, changeset_status): return self._get_instance(ChangesetStatus, changeset_status) - def get_status(self, repo, revision): + def __get_pull_request(self, pull_request): + return self._get_instance(PullRequest, pull_request) + + def get_status(self, repo, revision=None, pull_request=None): """ - Returns status of changeset for given revision and version 0 - versioning makes a history of statuses, and version == 0 is always the - current one + Returns latest status of changeset for given revision or for given + pull request. Statuses are versioned inside a table itself and + version == 0 is always the current one :param repo: :type repo: - :param revision: 40char hash + :param revision: 40char hash or None :type revision: str + :param pull_request: pull_request reference + :type: """ repo = self._get_repo(repo) - status = ChangesetStatus.query()\ + q = ChangesetStatus.query()\ .filter(ChangesetStatus.repo == repo)\ - .filter(ChangesetStatus.revision == revision)\ - .filter(ChangesetStatus.version == 0).scalar() + .filter(ChangesetStatus.version == 0) + + if revision: + q = q.filter(ChangesetStatus.revision == revision) + elif pull_request: + pull_request = self.__get_pull_request(pull_request) + q = q.filter(ChangesetStatus.pull_request == pull_request) + else: + raise Exception('Please specify revision or pull_request') + + status = q.scalar() status = status.status if status else status st = status or ChangesetStatus.DEFAULT return str(st) diff --git a/rhodecode/model/comment.py b/rhodecode/model/comment.py --- a/rhodecode/model/comment.py +++ b/rhodecode/model/comment.py @@ -32,7 +32,8 @@ from sqlalchemy.util.compat import defau from rhodecode.lib.utils2 import extract_mentioned_users, safe_unicode from rhodecode.lib import helpers as h from rhodecode.model import BaseModel -from rhodecode.model.db import ChangesetComment, User, Repository, Notification +from rhodecode.model.db import ChangesetComment, User, Repository, \ + Notification, PullRequest from rhodecode.model.notification import NotificationModel log = logging.getLogger(__name__) @@ -43,6 +44,9 @@ class ChangesetCommentsModel(BaseModel): def __get_changeset_comment(self, changeset_comment): return self._get_instance(ChangesetComment, changeset_comment) + def __get_pull_request(self, pull_request): + return self._get_instance(PullRequest, pull_request) + def _extract_mentions(self, s): user_objects = [] for username in extract_mentioned_users(s): @@ -135,7 +139,7 @@ class ChangesetCommentsModel(BaseModel): return comment - def get_comments(self, repo_id, revision=None, pull_request_id=None): + def get_comments(self, repo_id, revision=None, pull_request=None): """ Get's main comments based on revision or pull_request_id @@ -143,22 +147,24 @@ class ChangesetCommentsModel(BaseModel): :type repo_id: :param revision: :type revision: - :param pull_request_id: - :type pull_request_id: + :param pull_request: + :type pull_request: """ + q = ChangesetComment.query()\ .filter(ChangesetComment.repo_id == repo_id)\ .filter(ChangesetComment.line_no == None)\ .filter(ChangesetComment.f_path == None) if revision: q = q.filter(ChangesetComment.revision == revision) - elif pull_request_id: - q = q.filter(ChangesetComment.pull_request_id == pull_request_id) + elif pull_request: + pull_request = self.__get_pull_request(pull_request) + q = q.filter(ChangesetComment.pull_request == pull_request) else: - raise Exception('Please specify revision or pull_request_id') + raise Exception('Please specify revision or pull_request') return q.all() - def get_inline_comments(self, repo_id, revision=None, pull_request_id=None): + def get_inline_comments(self, repo_id, revision=None, pull_request=None): q = self.sa.query(ChangesetComment)\ .filter(ChangesetComment.repo_id == repo_id)\ .filter(ChangesetComment.line_no != None)\ @@ -167,8 +173,9 @@ class ChangesetCommentsModel(BaseModel): if revision: q = q.filter(ChangesetComment.revision == revision) - elif pull_request_id: - q = q.filter(ChangesetComment.pull_request_id == pull_request_id) + elif pull_request: + pull_request = self.__get_pull_request(pull_request) + q = q.filter(ChangesetComment.pull_request == pull_request) else: raise Exception('Please specify revision or pull_request_id') diff --git a/rhodecode/model/db.py b/rhodecode/model/db.py --- a/rhodecode/model/db.py +++ b/rhodecode/model/db.py @@ -1322,7 +1322,8 @@ class ChangesetComment(Base, BaseModel): ) comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True) repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) - revision = Column('revision', String(40), nullable=False) + revision = Column('revision', String(40), nullable=True) + pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) line_no = Column('line_no', Unicode(10), nullable=True) f_path = Column('f_path', Unicode(1000), nullable=True) user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False) @@ -1332,6 +1333,7 @@ class ChangesetComment(Base, BaseModel): author = relationship('User', lazy='joined') repo = relationship('Repository') status_change = relationship('ChangesetStatus', uselist=False) + pull_request = relationship('PullRequest', lazy='joined') @classmethod def get_users(cls, revision): @@ -1397,6 +1399,8 @@ class PullRequest(Base, BaseModel): pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True) title = Column('title', Unicode(256), nullable=True) description = Column('description', Unicode(10240), nullable=True) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None) _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) org_ref = Column('org_ref', Unicode(256), nullable=False) @@ -1411,6 +1415,7 @@ class PullRequest(Base, BaseModel): def revisions(self, val): self._revisions = ':'.join(val) + author = relationship('User', lazy='joined') reviewers = relationship('PullRequestReviewers') org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id') other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id') diff --git a/rhodecode/model/pull_request.py b/rhodecode/model/pull_request.py --- a/rhodecode/model/pull_request.py +++ b/rhodecode/model/pull_request.py @@ -37,8 +37,13 @@ log = logging.getLogger(__name__) class PullRequestModel(BaseModel): + def get_all(self, repo): + repo = self._get_repo(repo) + return PullRequest.query().filter(PullRequest.other_repo == repo).all() + def create(self, created_by, org_repo, org_ref, other_repo, other_ref, revisions, reviewers, title, description=None): + created_by_user = self._get_user(created_by) new = PullRequest() new.org_repo = self._get_repo(org_repo) @@ -48,7 +53,7 @@ class PullRequestModel(BaseModel): new.revisions = revisions new.title = title new.description = description - + new.author = created_by_user self.sa.add(new) #members @@ -59,7 +64,7 @@ class PullRequestModel(BaseModel): #notification to reviewers notif = NotificationModel() - created_by_user = self._get_user(created_by) + subject = safe_unicode( h.link_to( _('%(user)s wants you to review pull request #%(pr_id)s') % \ diff --git a/rhodecode/model/scm.py b/rhodecode/model/scm.py --- a/rhodecode/model/scm.py +++ b/rhodecode/model/scm.py @@ -43,7 +43,7 @@ from rhodecode.lib.utils import get_repo action_logger, EmptyChangeset, REMOVED_REPO_PAT from rhodecode.model import BaseModel from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \ - UserFollowing, UserLog, User, RepoGroup + UserFollowing, UserLog, User, RepoGroup, PullRequest log = logging.getLogger(__name__) @@ -320,19 +320,21 @@ class ScmModel(BaseModel): return f is not None - def get_followers(self, repo_id): - if not isinstance(repo_id, int): - repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id') + def get_followers(self, repo): + repo = self._get_repo(repo) return self.sa.query(UserFollowing)\ - .filter(UserFollowing.follows_repo_id == repo_id).count() + .filter(UserFollowing.follows_repository == repo).count() - def get_forks(self, repo_id): - if not isinstance(repo_id, int): - repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id') + def get_forks(self, repo): + repo = self._get_repo(repo) + return self.sa.query(Repository)\ + .filter(Repository.fork == repo).count() - return self.sa.query(Repository)\ - .filter(Repository.fork_id == repo_id).count() + def get_pull_requests(self, repo): + repo = self._get_repo(repo) + return self.sa.query(PullRequest)\ + .filter(PullRequest.other_repo == repo).count() def mark_as_fork(self, repo, fork, user): repo = self.__get_repo(repo) diff --git a/rhodecode/templates/base/base.html b/rhodecode/templates/base/base.html --- a/rhodecode/templates/base/base.html +++ b/rhodecode/templates/base/base.html @@ -247,6 +247,14 @@ ${c.repository_forks} +
  • + + + ${_('Pull requests')} + + ${c.repository_pull_requests} + +
  • ${usermenu()} + + ## diff block + <%namespace name="diff_block" file="/changeset/diff_block.html"/> + %for fid, change, f, stat in c.files: + ${diff_block.diff_block_simple([c.changes[fid]])} + %endfor + + ## template for inline comment form + <%namespace name="comment" file="/changeset/changeset_file_comment.html"/> + ##${comment.comment_inline_form(c.changeset)} + + ## render comments main comments form and it status + ##${comment.comments(h.url('pull_request_comment', repo_name=c.repo_name, pull_request_id=c.pull_request.pull_request_id), + ## c.current_changeset_status)} + + +