@@ -149,384 +149,386 @@ class PullrequestsController(BaseRepoCon
tags.append((n, tag))
if rev == tagrev:
selected = n
# prio 1: rev was selected as existing entry above
# prio 2: create special entry for rev; rev _must_ be used
specials = []
if rev and selected is None:
selected = 'rev:%s:%s' % (rev, rev)
specials = [(selected, '%s: %s' % (_("Changeset"), rev[:12]))]
# prio 3: most recent peer branch
if peers and not selected:
selected = peers[0][0]
# prio 4: tip revision
if not selected:
if h.is_hg(repo):
if tipbranch:
selected = 'branch:%s:%s' % (tipbranch, tiprev)
else:
selected = 'tag:null:' + repo.EMPTY_CHANGESET
tags.append((selected, 'null'))
if 'master' in repo.branches:
selected = 'branch:master:%s' % repo.branches['master']
k, v = repo.branches.items()[0]
selected = 'branch:%s:%s' % (k, v)
groups = [(specials, _("Special")),
(peers, _("Peer branches")),
(bookmarks, _("Bookmarks")),
(branches, _("Branches")),
(tags, _("Tags")),
]
return [g for g in groups if g[0]], selected
def _get_is_allowed_change_status(self, pull_request):
if pull_request.is_closed():
return False
owner = self.authuser.user_id == pull_request.user_id
reviewer = self.authuser.user_id in [x.user_id for x in
pull_request.reviewers]
return self.authuser.admin or owner or reviewer
@LoginRequired()
@HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
'repository.admin')
def show_all(self, repo_name):
c.from_ = request.GET.get('from_') or ''
c.closed = request.GET.get('closed') or ''
c.pull_requests = PullRequestModel().get_all(repo_name, from_=c.from_, closed=c.closed)
c.repo_name = repo_name
p = safe_int(request.GET.get('page', 1), 1)
c.pullrequests_pager = Page(c.pull_requests, page=p, items_per_page=10)
if request.environ.get('HTTP_X_PARTIAL_XHR'):
return render('/pullrequests/pullrequest_data.html')
return render('/pullrequests/pullrequest_show_all.html')
@NotAnonymous()
def show_my(self):
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 ==
)
return render('/pullrequests/pullrequest_show_my.html')
def index(self):
org_repo = c.db_repo
org_scm_instance = org_repo.scm_instance
try:
org_scm_instance.get_changeset()
except EmptyRepositoryError, e:
h.flash(h.literal(_('There are no changesets yet')),
category='warning')
redirect(url('summary_home', repo_name=org_repo.repo_name))
org_rev = request.GET.get('rev_end')
# rev_start is not directly useful - its parent could however be used
# as default for other and thus give a simple compare view
#other_rev = request.POST.get('rev_start')
branch = request.GET.get('branch')
c.cs_repos = [(org_repo.repo_name, org_repo.repo_name)]
c.default_cs_repo = org_repo.repo_name
c.cs_refs, c.default_cs_ref = self._get_repo_refs(org_scm_instance, rev=org_rev, branch=branch)
default_cs_ref_type, default_cs_branch, default_cs_rev = c.default_cs_ref.split(':')
if default_cs_ref_type != 'branch':
default_cs_branch = org_repo.get_changeset(default_cs_rev).branch
# add org repo to other so we can open pull request against peer branches on itself
c.a_repos = [(org_repo.repo_name, '%s (self)' % org_repo.repo_name)]
if org_repo.parent:
# add parent of this fork also and select it.
# use the same branch on destination as on source, if available.
c.a_repos.append((org_repo.parent.repo_name, '%s (parent)' % org_repo.parent.repo_name))
c.a_repo = org_repo.parent
c.a_refs, c.default_a_ref = self._get_repo_refs(
org_repo.parent.scm_instance, branch=default_cs_branch)
c.a_repo = org_repo
c.a_refs, c.default_a_ref = self._get_repo_refs(org_scm_instance) # without rev and branch
# gather forks and add to this list ... even though it is rare to
# request forks to pull from their parent
for fork in org_repo.forks:
c.a_repos.append((fork.repo_name, fork.repo_name))
return render('/pullrequests/pullrequest.html')
@jsonify
def repo_info(self, repo_name):
repo = RepoModel()._get_repo(repo_name)
refs, selected_ref = self._get_repo_refs(repo.scm_instance)
return {
'description': repo.description.split('\n', 1)[0],
'selected_ref': selected_ref,
'refs': refs,
}
def create(self, repo_name):
_form = PullRequestForm(repo.repo_id)().to_python(request.POST)
except formencode.Invalid, errors:
log.error(traceback.format_exc())
log.error(str(errors))
msg = _('Error creating pull request: %s') % errors.msg
h.flash(msg, 'error')
raise HTTPBadRequest
# heads up: org and other might seem backward here ...
org_repo_name = _form['org_repo']
org_ref = _form['org_ref'] # will have merge_rev as rev but symbolic name
org_repo = RepoModel()._get_repo(org_repo_name)
(org_ref_type,
org_ref_name,
org_rev) = org_ref.split(':')
if org_ref_type == 'rev':
org_ref_type = 'branch'
cs = org_repo.scm_instance.get_changeset(org_rev)
org_ref = '%s:%s:%s' % (org_ref_type, cs.branch, cs.raw_id)
other_repo_name = _form['other_repo']
other_ref = _form['other_ref'] # will have symbolic name and head revision
other_repo = RepoModel()._get_repo(other_repo_name)
(other_ref_type,
other_ref_name,
other_rev) = other_ref.split(':')
cs_ranges, _cs_ranges_not, ancestor_rev = \
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 ancestor_rev is None:
ancestor_rev = org_repo.scm_instance.EMPTY_CHANGESET
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)
reviewers = _form['review_members']
title = _form['pullrequest_title']
if not title:
if org_repo_name == other_repo_name:
title = '%s to %s' % (h.short_ref(org_ref_type, org_ref_name),
h.short_ref(other_ref_type, other_ref_name))
title = '%s#%s to %s#%s' % (org_repo_name, h.short_ref(org_ref_type, org_ref_name),
other_repo_name, h.short_ref(other_ref_type, other_ref_name))
description = _form['pullrequest_desc'].strip() or _('No description')
pull_request = PullRequestModel().create(
self.authuser.user_id, org_repo_name, org_ref, other_repo_name,
other_ref, revisions, reviewers, title, description
Session().commit()
h.flash(_('Successfully opened new pull request'),
category='success')
except UserInvalidException, u:
h.flash(_('Invalid reviewer "%s" specified') % u, category='error')
raise HTTPBadRequest()
except Exception:
h.flash(_('Error occurred while creating pull request'),
category='error')
return redirect(url('pullrequest_home', repo_name=repo_name))
return redirect(pull_request.url())
def create_update(self, old_pull_request, updaterev, title, description, reviewers_ids):
org_repo = RepoModel()._get_repo(old_pull_request.org_repo.repo_name)
org_ref_type, org_ref_name, org_rev = old_pull_request.org_ref.split(':')
new_org_rev = self._get_ref_rev(org_repo, 'rev', updaterev)
other_repo = RepoModel()._get_repo(old_pull_request.other_repo.repo_name)
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_other_rev = self._get_ref_rev(other_repo, other_ref_type, other_ref_name)
cs_ranges, _cs_ranges_not, ancestor_rev = CompareController._get_changesets(org_repo.scm_instance.alias,
other_repo.scm_instance, new_other_rev, # org and other "swapped"
org_repo.scm_instance, new_org_rev)
old_revisions = set(old_pull_request.revisions)
new_revisions = [r for r in revisions if r not in old_revisions]
lost = old_revisions.difference(revisions)
infos = ['This is an update 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 pull request:'))
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 pull request:') % (org_ref_type, org_ref_name))
for r in reversed(revisions):
if r in new_revisions:
infos.append(' %s %s' % (h.short_id(r), h.shorter(rev_desc, 80)))
if ancestor_rev == other_rev:
infos.append(_("Ancestor didn't change - show diff since previous version:"))
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
infos.append(_('This pull request is based on another %s revision and there is no simple diff.') % other_ref_name)
infos.append(_('No changes found on %s %s since previous version.') % (org_ref_type, org_ref_name))
# TODO: fail?
# hack: ancestor_rev is not an other_ref but we want to show the
new_other_ref = '%s:%s:%s' % (other_ref_type, other_ref_name, ancestor_rev)
new_org_ref = '%s:%s:%s' % (org_ref_type, org_ref_name, new_org_rev)
title, old_v = re.match(r'(.*)\(v(\d+)\)\s*$', title).groups()
v = int(old_v) + 1
except (AttributeError, ValueError):
v = 2
title = '%s (v%s)' % (title.strip(), v)
# using a mail-like separator, insert new update info at the top of the list
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.authuser.user_id,
old_pull_request.org_repo.repo_name, new_org_ref,
old_pull_request.other_repo.repo_name, new_other_ref,
revisions, reviewers_ids, title, description
return redirect(old_pull_request.url())
ChangesetCommentsModel().create(
text=_('Closed, replaced by %s .') % pull_request.url(canonical=True),
repo=old_pull_request.other_repo.repo_id,
user=c.authuser.user_id,
pull_request=old_pull_request.pull_request_id,
closing_pr=True)
PullRequestModel().close_pull_request(old_pull_request.pull_request_id)
h.flash(_('Pull request update created'),
# pullrequest_post for PR editing
def post(self, repo_name, pull_request_id):
pull_request = PullRequest.get_or_404(pull_request_id)
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):
_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)
h.flash(_('Pull request updated'), category='success')
def delete(self, repo_name, pull_request_id):
#only owner can delete it !
if pull_request.author.user_id == c.authuser.user_id:
PullRequestModel().delete(pull_request)
h.flash(_('Successfully deleted pull request'),
return redirect(url('my_pullrequests'))
Status change: