##// END OF EJS Templates
i18n: updated translation for Polish...
i18n: updated translation for Polish Currently translated at 56.5% (614 of 1087 strings)

File last commit:

r8075:e3537310 default
r8092:7fef5132 default
Show More
pull_request.py
392 lines | 16.4 KiB | text/x-python | PythonLexer
Bradley M. Kuhn
Second step in two-part process to rename directories....
r4187 # -*- coding: utf-8 -*-
# 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/>.
"""
kallithea.model.pull_request
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Bradley M. Kuhn
General renaming to Kallithea
r4212 pull request model for Kallithea
Bradley M. Kuhn
Second step in two-part process to rename directories....
r4187
Bradley M. Kuhn
RhodeCode GmbH is not the sole author of this work
r4211 This file was forked by the Kallithea project in July 2014.
Original author and date, and relevant copyright and licensing information is below:
Bradley M. Kuhn
Second step in two-part process to rename directories....
r4187 :created_on: Jun 6, 2012
:author: marcink
Bradley M. Kuhn
RhodeCode GmbH is not the sole author of this work
r4211 :copyright: (c) 2013 RhodeCode GmbH, and others.
Bradley M. Kuhn
Correct licensing information in individual files....
r4208 :license: GPLv3, see LICENSE.md for more details.
Bradley M. Kuhn
Second step in two-part process to rename directories....
r4187 """
Mads Kiilerich
scripts: initial run of import cleanup using isort
r7718 import datetime
Bradley M. Kuhn
Second step in two-part process to rename directories....
r4187 import logging
Søren Løvborg
pullrequests: introduce "action objects" for PR creation...
r6493 import re
Bradley M. Kuhn
Second step in two-part process to rename directories....
r4187
Mads Kiilerich
tg: minimize future diff by some mocking and replacing some pylons imports with tg...
r6508 from tg import request
from tg.i18n import ugettext as _
Bradley M. Kuhn
Second step in two-part process to rename directories....
r4187
Mads Kiilerich
scripts: initial run of import cleanup using isort
r7718 from kallithea.lib import helpers as h
Mads Kiilerich
py3: remove safe_unicode in places where it no longer is needed because all strings (except bytes) already *are* unicode strings...
r8075 from kallithea.lib.utils2 import ascii_bytes, extract_mentioned_users
Mads Kiilerich
scripts: initial run of import cleanup using isort
r7718 from kallithea.model.db import ChangesetStatus, PullRequest, PullRequestReviewer, User
Bradley M. Kuhn
Second step in two-part process to rename directories....
r4187 from kallithea.model.meta import Session
from kallithea.model.notification import NotificationModel
log = logging.getLogger(__name__)
Søren Løvborg
pullrequests: pass around reviewer User objects, not IDs...
r6485 def _assert_valid_reviewers(seq):
"""Sanity check: elements are actual User objects, and not the default user."""
assert not any(user.is_default_user for user in seq)
Bradley M. Kuhn
Second step in two-part process to rename directories....
r4187
Søren Løvborg
pullrequests: pass around reviewer User objects, not IDs...
r6485
class PullRequestModel(object):
Søren Løvborg
pull requests: prevent adding DEFAULT user as reviewer...
r5841
Mads Kiilerich
pullrequests: when updating a PR, only add and remove the reviewers that actually were added/removed
r6231 def add_reviewers(self, user, pr, reviewers, mention_recipients=None):
"""Add reviewer and send notification to them.
"""
Søren Løvborg
pullrequests: pass around reviewer User objects, not IDs...
r6485 reviewers = set(reviewers)
_assert_valid_reviewers(reviewers)
if mention_recipients is not None:
mention_recipients = set(mention_recipients) - reviewers
_assert_valid_reviewers(mention_recipients)
Lars Kruse
codingstyle: trivial whitespace fixes...
r6789 # members
Søren Løvborg
pullrequests: pass around reviewer User objects, not IDs...
r6485 for reviewer in reviewers:
Søren Løvborg
db: it should be "PullRequestReviewer" (singular)...
r6284 prr = PullRequestReviewer(reviewer, pr)
Mads Kiilerich
pullrequests: when updating a PR, only add and remove the reviewers that actually were added/removed
r6231 Session().add(prr)
Mads Kiilerich
pull requests: notify added reviewers, just like initial reviewers are
r4312
Lars Kruse
codingstyle: trivial whitespace fixes...
r6789 # notification to reviewers
Mads Kiilerich
pull requests: show more helpful URLs
r4447 pr_url = pr.url(canonical=True)
Mads Kiilerich
pullrequests: use correct References header when adding reviewers to (new?) PRs...
r5102 threading = ['%s-pr-%s@%s' % (pr.other_repo.repo_name,
pr.pull_request_id,
h.canonical_hostname())]
Mads Kiilerich
py3: remove safe_unicode in places where it no longer is needed because all strings (except bytes) already *are* unicode strings...
r8075 subject = h.link_to(
_('%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s') %
Mads Kiilerich
pull requests: blame the right user when an admin adds reviewers to other peoples PRs...
r5499 {'user': user.username,
Mads Kiilerich
pull requests: notify added reviewers, just like initial reviewers are
r4312 'pr_title': pr.title,
Thomas De Schampheleire
pullrequests: add PullRequest.nice_id method...
r5089 'pr_nice_id': pr.nice_id()},
Mads Kiilerich
py3: remove safe_unicode in places where it no longer is needed because all strings (except bytes) already *are* unicode strings...
r8075 pr_url)
Mads Kiilerich
pull requests: notify added reviewers, just like initial reviewers are
r4312 body = pr.description
Mads Kiilerich
notifications: improve Email subjects
r4381 _org_ref_type, org_ref_name, _org_rev = pr.org_ref.split(':')
Mads Kiilerich
notifications: make more template strings available for mails...
r6019 _other_ref_type, other_ref_name, _other_rev = pr.other_ref.split(':')
Mads Kiilerich
pullrequests: when updating a PR, only add and remove the reviewers that actually were added/removed
r6231 revision_data = [(x.raw_id, x.message)
for x in map(pr.org_repo.get_changeset, pr.revisions)]
Mads Kiilerich
notifications: improve Email subjects
r4381 email_kwargs = {
Mads Kiilerich
pull requests: notify added reviewers, just like initial reviewers are
r4312 'pr_title': pr.title,
Thomas De Schampheleire <thomas.de.schampheleire at gmail.com>
email: add relevant title to subject of emails...
r6021 'pr_title_short': h.shorter(pr.title, 50),
Mads Kiilerich
pull requests: blame the right user when an admin adds reviewers to other peoples PRs...
r5499 'pr_user_created': user.full_name_and_username,
Mads Kiilerich
urls: introduce canonical_url config setting...
r4445 'pr_repo_url': h.canonical_url('summary_home', repo_name=pr.other_repo.repo_name),
Bradley M. Kuhn
Second step in two-part process to rename directories....
r4187 'pr_url': pr_url,
Mads Kiilerich
notifications: improve Email subjects
r4381 'pr_revisions': revision_data,
'repo_name': pr.other_repo.repo_name,
Thomas De Schampheleire
pullrequest: linkify changesets in html notification email to reviewers...
r5673 'org_repo_name': pr.org_repo.repo_name,
Thomas De Schampheleire
pullrequests: add PullRequest.nice_id method...
r5089 'pr_nice_id': pr.nice_id(),
Mads Kiilerich
notifications: make more template strings available for mails...
r6019 'pr_target_repo': h.canonical_url('summary_home',
repo_name=pr.other_repo.repo_name),
'pr_target_branch': other_ref_name,
'pr_source_repo': h.canonical_url('summary_home',
repo_name=pr.org_repo.repo_name),
'pr_source_branch': org_ref_name,
'pr_owner': pr.owner,
Mads Kiilerich
notifications: tweak PR mail subject lines...
r6022 'pr_owner_username': pr.owner.username,
Mads Kiilerich
pull requests: blame the right user when an admin adds reviewers to other peoples PRs...
r5499 'pr_username': user.username,
Mads Kiilerich
notifications: fix threading - use line number...
r4446 'threading': threading,
Mads Kiilerich
pull requests: handle @mention in PR description ... but so far without fancy js completion
r4404 'is_mention': False,
Mads Kiilerich
notifications: improve Email subjects
r4381 }
Mads Kiilerich
pull requests: handle @mention in PR description ... but so far without fancy js completion
r4404 if reviewers:
Mads Kiilerich
pull requests: blame the right user when an admin adds reviewers to other peoples PRs...
r5499 NotificationModel().create(created_by=user, subject=subject, body=body,
Mads Kiilerich
pull requests: handle @mention in PR description ... but so far without fancy js completion
r4404 recipients=reviewers,
Thomas De Schampheleire
model: move notification types from Notification to NotificationModel...
r7368 type_=NotificationModel.TYPE_PULL_REQUEST,
Mads Kiilerich
pull requests: handle @mention in PR description ... but so far without fancy js completion
r4404 email_kwargs=email_kwargs)
if mention_recipients:
email_kwargs['is_mention'] = True
subject = _('[Mention]') + ' ' + subject
Mads Kiilerich
tests: add test coverage of PR comment @mention
r6020 # FIXME: this subject is wrong and unused!
Mads Kiilerich
pull requests: blame the right user when an admin adds reviewers to other peoples PRs...
r5499 NotificationModel().create(created_by=user, subject=subject, body=body,
Søren Løvborg
pullrequests: pass around reviewer User objects, not IDs...
r6485 recipients=mention_recipients,
Thomas De Schampheleire
model: move notification types from Notification to NotificationModel...
r7368 type_=NotificationModel.TYPE_PULL_REQUEST,
Mads Kiilerich
pull requests: handle @mention in PR description ... but so far without fancy js completion
r4404 email_kwargs=email_kwargs)
Mads Kiilerich
pull requests: blame the right user when an admin adds reviewers to other peoples PRs...
r5499 def mention_from_description(self, user, pr, old_description=''):
Søren Løvborg
utils: improve extract_mentioned_users usefulness...
r5840 mention_recipients = (extract_mentioned_users(pr.description) -
extract_mentioned_users(old_description))
Mads Kiilerich
pull requests: handle @mention in PR description ... but so far without fancy js completion
r4404
Mads Kiilerich
cleanup: pass log strings unformatted - avoid unnecessary % formatting when not logging
r5375 log.debug("Mentioning %s", mention_recipients)
Mads Kiilerich
pullrequests: when updating a PR, only add and remove the reviewers that actually were added/removed
r6231 self.add_reviewers(user, pr, set(), mention_recipients)
Bradley M. Kuhn
Second step in two-part process to rename directories....
r4187
Søren Løvborg
pullrequests: pass around reviewer User objects, not IDs...
r6485 def remove_reviewers(self, user, pull_request, reviewers):
"""Remove specified users from being reviewers of the PR."""
Mads Kiilerich
sqlalchemy: fix warnings from running the test suite...
r6614 if not reviewers:
return # avoid SQLAlchemy warning about empty sequence for IN-predicate
Bradley M. Kuhn
Second step in two-part process to rename directories....
r4187
Søren Løvborg
db: it should be "PullRequestReviewer" (singular)...
r6284 PullRequestReviewer.query() \
Mads Kiilerich
pullrequests: when updating a PR, only add and remove the reviewers that actually were added/removed
r6231 .filter_by(pull_request=pull_request) \
Søren Løvborg
pullrequests: pass around reviewer User objects, not IDs...
r6485 .filter(PullRequestReviewer.user_id.in_(r.user_id for r in reviewers)) \
Mads Kiilerich
pullrequests: when updating a PR, only add and remove the reviewers that actually were added/removed
r6231 .delete(synchronize_session='fetch') # the default of 'evaluate' is not available
Bradley M. Kuhn
Second step in two-part process to rename directories....
r4187
def delete(self, pull_request):
Søren Løvborg
model: refactor and simplify _get_instance...
r6082 pull_request = PullRequest.guess_instance(pull_request)
Bradley M. Kuhn
Second step in two-part process to rename directories....
r4187 Session().delete(pull_request)
Andrew Shadura
git: add references for Git pull request heads...
r6550 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
Bradley M. Kuhn
Second step in two-part process to rename directories....
r4187
def close_pull_request(self, pull_request):
Søren Løvborg
model: refactor and simplify _get_instance...
r6082 pull_request = PullRequest.guess_instance(pull_request)
Bradley M. Kuhn
Second step in two-part process to rename directories....
r4187 pull_request.status = PullRequest.STATUS_CLOSED
pull_request.updated_on = datetime.datetime.now()
Søren Løvborg
pullrequests: introduce "action objects" for PR creation...
r6493
class CreatePullRequestAction(object):
class ValidationError(Exception):
pass
class Empty(ValidationError):
pass
class AmbiguousAncestor(ValidationError):
pass
class Unauthorized(ValidationError):
pass
@staticmethod
def is_user_authorized(org_repo, other_repo):
"""Performs authorization check with only the minimum amount of
information needed for such a check, rather than a full command
object.
"""
if (h.HasRepoPermissionLevel('read')(org_repo.repo_name) and
Mads Kiilerich
flake8: fix E129 visually indented line with same indent as next logical line
r7732 h.HasRepoPermissionLevel('read')(other_repo.repo_name)
):
Søren Løvborg
pullrequests: introduce "action objects" for PR creation...
r6493 return True
return False
def __init__(self, org_repo, other_repo, org_ref, other_ref, title, description, owner, reviewers):
from kallithea.controllers.compare import CompareController
reviewers = set(reviewers)
_assert_valid_reviewers(reviewers)
(org_ref_type,
org_ref_name,
org_rev) = org_ref.split(':')
org_display = h.short_ref(org_ref_type, org_ref_name)
if org_ref_type == 'rev':
cs = org_repo.scm_instance.get_changeset(org_rev)
org_ref = 'branch:%s:%s' % (cs.branch, cs.raw_id)
(other_ref_type,
other_ref_name,
other_rev) = other_ref.split(':')
if other_ref_type == 'rev':
cs = other_repo.scm_instance.get_changeset(other_rev)
other_ref_name = cs.raw_id[:12]
other_ref = '%s:%s:%s' % (other_ref_type, other_ref_name, cs.raw_id)
other_display = h.short_ref(other_ref_type, other_ref_name)
cs_ranges, _cs_ranges_not, ancestor_revs = \
CompareController._get_changesets(org_repo.scm_instance.alias,
other_repo.scm_instance, other_rev, # org and other "swapped"
org_repo.scm_instance, org_rev,
)
if not cs_ranges:
raise self.Empty(_('Cannot create empty pull request'))
if not ancestor_revs:
ancestor_rev = org_repo.scm_instance.EMPTY_CHANGESET
elif len(ancestor_revs) == 1:
ancestor_rev = ancestor_revs[0]
else:
raise self.AmbiguousAncestor(
_('Cannot create pull request - criss cross merge detected, please merge a later %s revision to %s')
% (other_ref_name, org_ref_name))
self.revisions = [cs_.raw_id for cs_ in cs_ranges]
# hack: ancestor_rev is not an other_rev but we want to show the
# requested destination and have the exact ancestor
other_ref = '%s:%s:%s' % (other_ref_type, other_ref_name, ancestor_rev)
if not title:
if org_repo == other_repo:
title = '%s to %s' % (org_display, other_display)
else:
title = '%s#%s to %s#%s' % (org_repo.repo_name, org_display,
other_repo.repo_name, other_display)
description = description or _('No description')
self.org_repo = org_repo
self.other_repo = other_repo
self.org_ref = org_ref
Andrew Shadura
git: add references for Git pull request heads...
r6550 self.org_rev = org_rev
Søren Løvborg
pullrequests: introduce "action objects" for PR creation...
r6493 self.other_ref = other_ref
self.title = title
self.description = description
self.owner = owner
self.reviewers = reviewers
if not CreatePullRequestAction.is_user_authorized(self.org_repo, self.other_repo):
raise self.Unauthorized(_('You are not authorized to create the pull request'))
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
Andrew Shadura
git: add references for Git pull request heads...
r6550 if self.org_repo.scm_instance.alias == 'git':
# create a ref under refs/pull/ so that commits don't get garbage-collected
Mads Kiilerich
py3: start using ascii_str and ascii_bytes - we will need it later
r7960 self.org_repo.scm_instance._repo[b"refs/pull/%d/head" % pr.pull_request_id] = ascii_bytes(self.org_rev)
Andrew Shadura
git: add references for Git pull request heads...
r6550
Lars Kruse
codingstyle: trivial whitespace fixes...
r6789 # reset state to under-review
Søren Løvborg
pullrequests: introduce "action objects" for PR creation...
r6493 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
class CreatePullRequestIterationAction(object):
@staticmethod
def is_user_authorized(old_pull_request):
"""Performs authorization check with only the minimum amount of
information needed for such a check, rather than a full command
object.
"""
if h.HasPermissionAny('hg.admin')():
return True
# Authorized to edit the old PR?
if request.authuser.user_id != old_pull_request.owner_id:
return False
# Authorized to create a new PR?
if not CreatePullRequestAction.is_user_authorized(old_pull_request.org_repo, old_pull_request.other_repo):
return False
return True
Søren Løvborg
pullrequests: fix broken "new PR iteration" handling of ancestor changes...
r6541 def __init__(self, old_pull_request, new_org_rev, new_other_rev, title, description, owner, reviewers):
Søren Løvborg
pullrequests: introduce "action objects" for PR creation...
r6493 self.old_pull_request = old_pull_request
org_repo = old_pull_request.org_repo
org_ref_type, org_ref_name, org_rev = old_pull_request.org_ref.split(':')
other_repo = old_pull_request.other_repo
other_ref_type, other_ref_name, other_rev = old_pull_request.other_ref.split(':') # other_rev is ancestor
#assert other_ref_type == 'branch', other_ref_type # TODO: what if not?
new_org_ref = '%s:%s:%s' % (org_ref_type, org_ref_name, new_org_rev)
Søren Løvborg
pullrequests: fix broken "new PR iteration" handling of ancestor changes...
r6541 new_other_ref = '%s:%s:%s' % (other_ref_type, other_ref_name, new_other_rev)
Søren Løvborg
pullrequests: introduce "action objects" for PR creation...
r6493
Søren Løvborg
pullrequests: fix broken "new PR iteration" handling of ancestor changes...
r6541 self.create_action = CreatePullRequestAction(org_repo, other_repo, new_org_ref, new_other_ref, None, None, owner, reviewers)
Søren Løvborg
pullrequests: introduce "action objects" for PR creation...
r6493
# Generate complete title/description
old_revisions = set(old_pull_request.revisions)
revisions = self.create_action.revisions
new_revisions = [r for r in revisions if r not in old_revisions]
lost = old_revisions.difference(revisions)
infos = ['This is a new iteration of %s "%s".' %
(h.canonical_url('pullrequest_show', repo_name=old_pull_request.other_repo.repo_name,
pull_request_id=old_pull_request.pull_request_id),
old_pull_request.title)]
if lost:
infos.append(_('Missing changesets since the previous iteration:'))
for r in old_pull_request.revisions:
if r in lost:
rev_desc = org_repo.get_changeset(r).message.split('\n')[0]
infos.append(' %s %s' % (h.short_id(r), rev_desc))
if new_revisions:
infos.append(_('New changesets on %s %s since the previous iteration:') % (org_ref_type, org_ref_name))
for r in reversed(revisions):
if r in new_revisions:
rev_desc = org_repo.get_changeset(r).message.split('\n')[0]
infos.append(' %s %s' % (h.short_id(r), h.shorter(rev_desc, 80)))
if self.create_action.other_ref == old_pull_request.other_ref:
infos.append(_("Ancestor didn't change - diff since previous iteration:"))
infos.append(h.canonical_url('compare_url',
repo_name=org_repo.repo_name, # other_repo is always same as repo_name
org_ref_type='rev', org_ref_name=h.short_id(org_rev), # use old org_rev as base
other_ref_type='rev', other_ref_name=h.short_id(new_org_rev),
)) # note: linear diff, merge or not doesn't matter
else:
infos.append(_('This iteration is based on another %s revision and there is no simple diff.') % other_ref_name)
else:
Mads Kiilerich
flake8: fix E111 indentation is not a multiple of four
r7730 infos.append(_('No changes found on %s %s since previous iteration.') % (org_ref_type, org_ref_name))
# TODO: fail?
Søren Løvborg
pullrequests: introduce "action objects" for PR creation...
r6493
Mads Kiilerich
pull-request: refactor iteration version bump...
r8049 v = 2
m = re.match(r'(.*)\(v(\d+)\)\s*$', title)
if m is not None:
title = m.group(1)
v = int(m.group(2)) + 1
Søren Løvborg
pullrequests: introduce "action objects" for PR creation...
r6493 self.create_action.title = '%s (v%s)' % (title.strip(), v)
# using a mail-like separator, insert new iteration info in description with latest first
descriptions = description.replace('\r\n', '\n').split('\n-- \n', 1)
description = descriptions[0].strip() + '\n\n-- \n' + '\n'.join(infos)
if len(descriptions) > 1:
description += '\n\n' + descriptions[1].strip()
self.create_action.description = description
if not CreatePullRequestIterationAction.is_user_authorized(self.old_pull_request):
raise CreatePullRequestAction.Unauthorized(_('You are not authorized to create the pull request'))
def execute(self):
pull_request = self.create_action.execute()
# Close old iteration
from kallithea.model.comment import ChangesetCommentsModel
ChangesetCommentsModel().create(
text=_('Closed, next iteration: %s .') % pull_request.url(canonical=True),
repo=self.old_pull_request.other_repo_id,
author=request.authuser.user_id,
pull_request=self.old_pull_request.pull_request_id,
closing_pr=True)
PullRequestModel().close_pull_request(self.old_pull_request.pull_request_id)
return pull_request