##// END OF EJS Templates
create a list out of generator....
marcink -
r3785:7d815483 beta
parent child Browse files
Show More
@@ -1,542 +1,541
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.pullrequests
3 rhodecode.controllers.pullrequests
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 pull requests controller for rhodecode for initializing pull requests
6 pull requests controller for rhodecode for initializing pull requests
7
7
8 :created_on: May 7, 2012
8 :created_on: May 7, 2012
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import logging
25 import logging
26 import traceback
26 import traceback
27 import formencode
27 import formencode
28
28
29 from webob.exc import HTTPNotFound, HTTPForbidden
29 from webob.exc import HTTPNotFound, HTTPForbidden
30 from collections import defaultdict
30 from collections import defaultdict
31 from itertools import groupby
31 from itertools import groupby
32
32
33 from pylons import request, response, session, tmpl_context as c, url
33 from pylons import request, response, session, tmpl_context as c, url
34 from pylons.controllers.util import abort, redirect
34 from pylons.controllers.util import abort, redirect
35 from pylons.i18n.translation import _
35 from pylons.i18n.translation import _
36
36
37 from rhodecode.lib.compat import json
37 from rhodecode.lib.compat import json
38 from rhodecode.lib.base import BaseRepoController, render
38 from rhodecode.lib.base import BaseRepoController, render
39 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
39 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
40 NotAnonymous
40 NotAnonymous
41 from rhodecode.lib.helpers import Page
41 from rhodecode.lib.helpers import Page
42 from rhodecode.lib import helpers as h
42 from rhodecode.lib import helpers as h
43 from rhodecode.lib import diffs
43 from rhodecode.lib import diffs
44 from rhodecode.lib.utils import action_logger, jsonify
44 from rhodecode.lib.utils import action_logger, jsonify
45 from rhodecode.lib.vcs.utils import safe_str
45 from rhodecode.lib.vcs.utils import safe_str
46 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
46 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
47 from rhodecode.lib.vcs.backends.base import EmptyChangeset
47 from rhodecode.lib.vcs.backends.base import EmptyChangeset
48 from rhodecode.lib.diffs import LimitedDiffContainer
48 from rhodecode.lib.diffs import LimitedDiffContainer
49 from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
49 from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
50 ChangesetComment
50 ChangesetComment
51 from rhodecode.model.pull_request import PullRequestModel
51 from rhodecode.model.pull_request import PullRequestModel
52 from rhodecode.model.meta import Session
52 from rhodecode.model.meta import Session
53 from rhodecode.model.repo import RepoModel
53 from rhodecode.model.repo import RepoModel
54 from rhodecode.model.comment import ChangesetCommentsModel
54 from rhodecode.model.comment import ChangesetCommentsModel
55 from rhodecode.model.changeset_status import ChangesetStatusModel
55 from rhodecode.model.changeset_status import ChangesetStatusModel
56 from rhodecode.model.forms import PullRequestForm
56 from rhodecode.model.forms import PullRequestForm
57 from mercurial import scmutil
57 from mercurial import scmutil
58 from rhodecode.lib.utils2 import safe_int
58 from rhodecode.lib.utils2 import safe_int
59
59
60 log = logging.getLogger(__name__)
60 log = logging.getLogger(__name__)
61
61
62
62
63 class PullrequestsController(BaseRepoController):
63 class PullrequestsController(BaseRepoController):
64
64
65 def __before__(self):
65 def __before__(self):
66 super(PullrequestsController, self).__before__()
66 super(PullrequestsController, self).__before__()
67 repo_model = RepoModel()
67 repo_model = RepoModel()
68 c.users_array = repo_model.get_users_js()
68 c.users_array = repo_model.get_users_js()
69 c.users_groups_array = repo_model.get_users_groups_js()
69 c.users_groups_array = repo_model.get_users_groups_js()
70
70
71 def _get_repo_refs(self, repo, rev=None, branch_rev=None):
71 def _get_repo_refs(self, repo, rev=None, branch_rev=None):
72 """return a structure with repo's interesting changesets, suitable for
72 """return a structure with repo's interesting changesets, suitable for
73 the selectors in pullrequest.html"""
73 the selectors in pullrequest.html"""
74 # list named branches that has been merged to this named branch - it should probably merge back
74 # list named branches that has been merged to this named branch - it should probably merge back
75 peers = []
75 peers = []
76
76
77 if rev:
77 if rev:
78 rev = safe_str(rev)
78 rev = safe_str(rev)
79
79
80 if branch_rev:
80 if branch_rev:
81 branch_rev = safe_str(branch_rev)
81 branch_rev = safe_str(branch_rev)
82 # not restricting to merge() would also get branch point and be better
82 # not restricting to merge() would also get branch point and be better
83 # (especially because it would get the branch point) ... but is currently too expensive
83 # (especially because it would get the branch point) ... but is currently too expensive
84 revs = ["sort(parents(branch(id('%s')) and merge()) - branch(id('%s')))" %
84 revs = ["sort(parents(branch(id('%s')) and merge()) - branch(id('%s')))" %
85 (branch_rev, branch_rev)]
85 (branch_rev, branch_rev)]
86 otherbranches = {}
86 otherbranches = {}
87 for i in scmutil.revrange(repo._repo, revs):
87 for i in scmutil.revrange(repo._repo, revs):
88 cs = repo.get_changeset(i)
88 cs = repo.get_changeset(i)
89 otherbranches[cs.branch] = cs.raw_id
89 otherbranches[cs.branch] = cs.raw_id
90 for branch, node in otherbranches.iteritems():
90 for branch, node in otherbranches.iteritems():
91 selected = 'branch:%s:%s' % (branch, node)
91 selected = 'branch:%s:%s' % (branch, node)
92 peers.append((selected, branch))
92 peers.append((selected, branch))
93
93
94 selected = None
94 selected = None
95 branches = []
95 branches = []
96 for branch, branchrev in repo.branches.iteritems():
96 for branch, branchrev in repo.branches.iteritems():
97 n = 'branch:%s:%s' % (branch, branchrev)
97 n = 'branch:%s:%s' % (branch, branchrev)
98 branches.append((n, branch))
98 branches.append((n, branch))
99 if rev == branchrev:
99 if rev == branchrev:
100 selected = n
100 selected = n
101 bookmarks = []
101 bookmarks = []
102 for bookmark, bookmarkrev in repo.bookmarks.iteritems():
102 for bookmark, bookmarkrev in repo.bookmarks.iteritems():
103 n = 'book:%s:%s' % (bookmark, bookmarkrev)
103 n = 'book:%s:%s' % (bookmark, bookmarkrev)
104 bookmarks.append((n, bookmark))
104 bookmarks.append((n, bookmark))
105 if rev == bookmarkrev:
105 if rev == bookmarkrev:
106 selected = n
106 selected = n
107 tags = []
107 tags = []
108 for tag, tagrev in repo.tags.iteritems():
108 for tag, tagrev in repo.tags.iteritems():
109 n = 'tag:%s:%s' % (tag, tagrev)
109 n = 'tag:%s:%s' % (tag, tagrev)
110 tags.append((n, tag))
110 tags.append((n, tag))
111 if rev == tagrev and tag != 'tip': # tip is not a real tag - and its branch is better
111 if rev == tagrev and tag != 'tip': # tip is not a real tag - and its branch is better
112 selected = n
112 selected = n
113
113
114 # prio 1: rev was selected as existing entry above
114 # prio 1: rev was selected as existing entry above
115
115
116 # prio 2: create special entry for rev; rev _must_ be used
116 # prio 2: create special entry for rev; rev _must_ be used
117 specials = []
117 specials = []
118 if rev and selected is None:
118 if rev and selected is None:
119 selected = 'rev:%s:%s' % (rev, rev)
119 selected = 'rev:%s:%s' % (rev, rev)
120 specials = [(selected, '%s: %s' % (_("Changeset"), rev[:12]))]
120 specials = [(selected, '%s: %s' % (_("Changeset"), rev[:12]))]
121
121
122 # prio 3: most recent peer branch
122 # prio 3: most recent peer branch
123 if peers and not selected:
123 if peers and not selected:
124 selected = peers[0][0][0]
124 selected = peers[0][0][0]
125
125
126 # prio 4: tip revision
126 # prio 4: tip revision
127 if not selected:
127 if not selected:
128 selected = 'tag:tip:%s' % repo.tags['tip']
128 selected = 'tag:tip:%s' % repo.tags['tip']
129
129
130 groups = [(specials, _("Special")),
130 groups = [(specials, _("Special")),
131 (peers, _("Peer branches")),
131 (peers, _("Peer branches")),
132 (bookmarks, _("Bookmarks")),
132 (bookmarks, _("Bookmarks")),
133 (branches, _("Branches")),
133 (branches, _("Branches")),
134 (tags, _("Tags")),
134 (tags, _("Tags")),
135 ]
135 ]
136 return [g for g in groups if g[0]], selected
136 return [g for g in groups if g[0]], selected
137
137
138 def _get_is_allowed_change_status(self, pull_request):
138 def _get_is_allowed_change_status(self, pull_request):
139 owner = self.rhodecode_user.user_id == pull_request.user_id
139 owner = self.rhodecode_user.user_id == pull_request.user_id
140 reviewer = self.rhodecode_user.user_id in [x.user_id for x in
140 reviewer = self.rhodecode_user.user_id in [x.user_id for x in
141 pull_request.reviewers]
141 pull_request.reviewers]
142 return (self.rhodecode_user.admin or owner or reviewer)
142 return (self.rhodecode_user.admin or owner or reviewer)
143
143
144 def _load_compare_data(self, pull_request, enable_comments=True):
144 def _load_compare_data(self, pull_request, enable_comments=True):
145 """
145 """
146 Load context data needed for generating compare diff
146 Load context data needed for generating compare diff
147
147
148 :param pull_request:
148 :param pull_request:
149 :type pull_request:
149 :type pull_request:
150 """
150 """
151 org_repo = pull_request.org_repo
151 org_repo = pull_request.org_repo
152 (org_ref_type,
152 (org_ref_type,
153 org_ref_name,
153 org_ref_name,
154 org_ref_rev) = pull_request.org_ref.split(':')
154 org_ref_rev) = pull_request.org_ref.split(':')
155
155
156 other_repo = org_repo
156 other_repo = org_repo
157 (other_ref_type,
157 (other_ref_type,
158 other_ref_name,
158 other_ref_name,
159 other_ref_rev) = pull_request.other_ref.split(':')
159 other_ref_rev) = pull_request.other_ref.split(':')
160
160
161 # despite opening revisions for bookmarks/branches/tags, we always
161 # despite opening revisions for bookmarks/branches/tags, we always
162 # convert this to rev to prevent changes after bookmark or branch change
162 # convert this to rev to prevent changes after bookmark or branch change
163 org_ref = ('rev', org_ref_rev)
163 org_ref = ('rev', org_ref_rev)
164 other_ref = ('rev', other_ref_rev)
164 other_ref = ('rev', other_ref_rev)
165
165
166 c.org_repo = org_repo
166 c.org_repo = org_repo
167 c.other_repo = other_repo
167 c.other_repo = other_repo
168
168
169 c.fulldiff = fulldiff = request.GET.get('fulldiff')
169 c.fulldiff = fulldiff = request.GET.get('fulldiff')
170
170
171 c.cs_ranges = [org_repo.get_changeset(x) for x in pull_request.revisions]
171 c.cs_ranges = [org_repo.get_changeset(x) for x in pull_request.revisions]
172
172
173 c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
173 c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
174
174
175 c.org_ref = org_ref[1]
175 c.org_ref = org_ref[1]
176 c.org_ref_type = org_ref[0]
176 c.org_ref_type = org_ref[0]
177 c.other_ref = other_ref[1]
177 c.other_ref = other_ref[1]
178 c.other_ref_type = other_ref[0]
178 c.other_ref_type = other_ref[0]
179
179
180 diff_limit = self.cut_off_limit if not fulldiff else None
180 diff_limit = self.cut_off_limit if not fulldiff else None
181
181
182 # we swap org/other ref since we run a simple diff on one repo
182 # we swap org/other ref since we run a simple diff on one repo
183 log.debug('running diff between %s@%s and %s@%s'
183 log.debug('running diff between %s@%s and %s@%s'
184 % (org_repo.scm_instance.path, org_ref,
184 % (org_repo.scm_instance.path, org_ref,
185 other_repo.scm_instance.path, other_ref))
185 other_repo.scm_instance.path, other_ref))
186 _diff = org_repo.scm_instance.get_diff(rev1=safe_str(other_ref[1]), rev2=safe_str(org_ref[1]))
186 _diff = org_repo.scm_instance.get_diff(rev1=safe_str(other_ref[1]), rev2=safe_str(org_ref[1]))
187
187
188 diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
188 diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
189 diff_limit=diff_limit)
189 diff_limit=diff_limit)
190 _parsed = diff_processor.prepare()
190 _parsed = diff_processor.prepare()
191
191
192 c.limited_diff = False
192 c.limited_diff = False
193 if isinstance(_parsed, LimitedDiffContainer):
193 if isinstance(_parsed, LimitedDiffContainer):
194 c.limited_diff = True
194 c.limited_diff = True
195
195
196 c.files = []
196 c.files = []
197 c.changes = {}
197 c.changes = {}
198 c.lines_added = 0
198 c.lines_added = 0
199 c.lines_deleted = 0
199 c.lines_deleted = 0
200 for f in _parsed:
200 for f in _parsed:
201 st = f['stats']
201 st = f['stats']
202 if st[0] != 'b':
202 if st[0] != 'b':
203 c.lines_added += st[0]
203 c.lines_added += st[0]
204 c.lines_deleted += st[1]
204 c.lines_deleted += st[1]
205 fid = h.FID('', f['filename'])
205 fid = h.FID('', f['filename'])
206 c.files.append([fid, f['operation'], f['filename'], f['stats']])
206 c.files.append([fid, f['operation'], f['filename'], f['stats']])
207 diff = diff_processor.as_html(enable_comments=enable_comments,
207 diff = diff_processor.as_html(enable_comments=enable_comments,
208 parsed_lines=[f])
208 parsed_lines=[f])
209 c.changes[fid] = [f['operation'], f['filename'], diff]
209 c.changes[fid] = [f['operation'], f['filename'], diff]
210
210
211 @LoginRequired()
211 @LoginRequired()
212 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
212 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
213 'repository.admin')
213 'repository.admin')
214 def show_all(self, repo_name):
214 def show_all(self, repo_name):
215 c.pull_requests = PullRequestModel().get_all(repo_name)
215 c.pull_requests = PullRequestModel().get_all(repo_name)
216 c.repo_name = repo_name
216 c.repo_name = repo_name
217 p = safe_int(request.GET.get('page', 1), 1)
217 p = safe_int(request.GET.get('page', 1), 1)
218
218
219 c.pullrequests_pager = Page(c.pull_requests, page=p, items_per_page=10)
219 c.pullrequests_pager = Page(c.pull_requests, page=p, items_per_page=10)
220
220
221 c.pullrequest_data = render('/pullrequests/pullrequest_data.html')
221 c.pullrequest_data = render('/pullrequests/pullrequest_data.html')
222
222
223 if request.environ.get('HTTP_X_PARTIAL_XHR'):
223 if request.environ.get('HTTP_X_PARTIAL_XHR'):
224 return c.pullrequest_data
224 return c.pullrequest_data
225
225
226 return render('/pullrequests/pullrequest_show_all.html')
226 return render('/pullrequests/pullrequest_show_all.html')
227
227
228 @LoginRequired()
228 @LoginRequired()
229 @NotAnonymous()
229 @NotAnonymous()
230 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
230 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
231 'repository.admin')
231 'repository.admin')
232 def index(self):
232 def index(self):
233 org_repo = c.rhodecode_db_repo
233 org_repo = c.rhodecode_db_repo
234
234
235 if org_repo.scm_instance.alias != 'hg':
235 if org_repo.scm_instance.alias != 'hg':
236 log.error('Review not available for GIT REPOS')
236 log.error('Review not available for GIT REPOS')
237 raise HTTPNotFound
237 raise HTTPNotFound
238
238
239 try:
239 try:
240 org_repo.scm_instance.get_changeset()
240 org_repo.scm_instance.get_changeset()
241 except EmptyRepositoryError, e:
241 except EmptyRepositoryError, e:
242 h.flash(h.literal(_('There are no changesets yet')),
242 h.flash(h.literal(_('There are no changesets yet')),
243 category='warning')
243 category='warning')
244 redirect(url('summary_home', repo_name=org_repo.repo_name))
244 redirect(url('summary_home', repo_name=org_repo.repo_name))
245
245
246 org_rev = request.GET.get('rev_end')
246 org_rev = request.GET.get('rev_end')
247 # rev_start is not directly useful - its parent could however be used
247 # rev_start is not directly useful - its parent could however be used
248 # as default for other and thus give a simple compare view
248 # as default for other and thus give a simple compare view
249 #other_rev = request.POST.get('rev_start')
249 #other_rev = request.POST.get('rev_start')
250
250
251 c.org_repos = []
251 c.org_repos = []
252 c.org_repos.append((org_repo.repo_name, org_repo.repo_name))
252 c.org_repos.append((org_repo.repo_name, org_repo.repo_name))
253 c.default_org_repo = org_repo.repo_name
253 c.default_org_repo = org_repo.repo_name
254 c.org_refs, c.default_org_ref = self._get_repo_refs(org_repo.scm_instance, org_rev)
254 c.org_refs, c.default_org_ref = self._get_repo_refs(org_repo.scm_instance, org_rev)
255
255
256 c.other_repos = []
256 c.other_repos = []
257 other_repos_info = {}
257 other_repos_info = {}
258
258
259 def add_other_repo(repo, branch_rev=None):
259 def add_other_repo(repo, branch_rev=None):
260 if repo.repo_name in other_repos_info: # shouldn't happen
260 if repo.repo_name in other_repos_info: # shouldn't happen
261 return
261 return
262 c.other_repos.append((repo.repo_name, repo.repo_name))
262 c.other_repos.append((repo.repo_name, repo.repo_name))
263 other_refs, selected_other_ref = self._get_repo_refs(repo.scm_instance, branch_rev=branch_rev)
263 other_refs, selected_other_ref = self._get_repo_refs(repo.scm_instance, branch_rev=branch_rev)
264 other_repos_info[repo.repo_name] = {
264 other_repos_info[repo.repo_name] = {
265 'user': dict(user_id=repo.user.user_id,
265 'user': dict(user_id=repo.user.user_id,
266 username=repo.user.username,
266 username=repo.user.username,
267 firstname=repo.user.firstname,
267 firstname=repo.user.firstname,
268 lastname=repo.user.lastname,
268 lastname=repo.user.lastname,
269 gravatar_link=h.gravatar_url(repo.user.email, 14)),
269 gravatar_link=h.gravatar_url(repo.user.email, 14)),
270 'description': repo.description.split('\n', 1)[0],
270 'description': repo.description.split('\n', 1)[0],
271 'revs': h.select('other_ref', selected_other_ref, other_refs, class_='refs')
271 'revs': h.select('other_ref', selected_other_ref, other_refs, class_='refs')
272 }
272 }
273
273
274 # add org repo to other so we can open pull request against peer branches on itself
274 # add org repo to other so we can open pull request against peer branches on itself
275 add_other_repo(org_repo, branch_rev=org_rev)
275 add_other_repo(org_repo, branch_rev=org_rev)
276 c.default_other_repo = org_repo.repo_name
276 c.default_other_repo = org_repo.repo_name
277
277
278 # gather forks and add to this list ... even though it is rare to
278 # gather forks and add to this list ... even though it is rare to
279 # request forks to pull from their parent
279 # request forks to pull from their parent
280 for fork in org_repo.forks:
280 for fork in org_repo.forks:
281 add_other_repo(fork)
281 add_other_repo(fork)
282
282
283 # add parents of this fork also, but only if it's not empty
283 # add parents of this fork also, but only if it's not empty
284 if org_repo.parent and org_repo.parent.scm_instance.revisions:
284 if org_repo.parent and org_repo.parent.scm_instance.revisions:
285 add_other_repo(org_repo.parent)
285 add_other_repo(org_repo.parent)
286 c.default_other_repo = org_repo.parent.repo_name
286 c.default_other_repo = org_repo.parent.repo_name
287
287
288 c.default_other_repo_info = other_repos_info[c.default_other_repo]
288 c.default_other_repo_info = other_repos_info[c.default_other_repo]
289 c.other_repos_info = json.dumps(other_repos_info)
289 c.other_repos_info = json.dumps(other_repos_info)
290
290
291 return render('/pullrequests/pullrequest.html')
291 return render('/pullrequests/pullrequest.html')
292
292
293 @LoginRequired()
293 @LoginRequired()
294 @NotAnonymous()
294 @NotAnonymous()
295 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
295 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
296 'repository.admin')
296 'repository.admin')
297 def create(self, repo_name):
297 def create(self, repo_name):
298 repo = RepoModel()._get_repo(repo_name)
298 repo = RepoModel()._get_repo(repo_name)
299 try:
299 try:
300 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
300 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
301 except formencode.Invalid, errors:
301 except formencode.Invalid, errors:
302 log.error(traceback.format_exc())
302 log.error(traceback.format_exc())
303 if errors.error_dict.get('revisions'):
303 if errors.error_dict.get('revisions'):
304 msg = 'Revisions: %s' % errors.error_dict['revisions']
304 msg = 'Revisions: %s' % errors.error_dict['revisions']
305 elif errors.error_dict.get('pullrequest_title'):
305 elif errors.error_dict.get('pullrequest_title'):
306 msg = _('Pull request requires a title with min. 3 chars')
306 msg = _('Pull request requires a title with min. 3 chars')
307 else:
307 else:
308 msg = _('Error creating pull request')
308 msg = _('Error creating pull request')
309
309
310 h.flash(msg, 'error')
310 h.flash(msg, 'error')
311 return redirect(url('pullrequest_home', repo_name=repo_name))
311 return redirect(url('pullrequest_home', repo_name=repo_name))
312
312
313 org_repo = _form['org_repo']
313 org_repo = _form['org_repo']
314 org_ref = 'rev:merge:%s' % _form['merge_rev']
314 org_ref = 'rev:merge:%s' % _form['merge_rev']
315 other_repo = _form['other_repo']
315 other_repo = _form['other_repo']
316 other_ref = 'rev:ancestor:%s' % _form['ancestor_rev']
316 other_ref = 'rev:ancestor:%s' % _form['ancestor_rev']
317 revisions = reversed(_form['revisions'])
317 revisions = [x for x in reversed(_form['revisions'])]
318 reviewers = _form['review_members']
318 reviewers = _form['review_members']
319
319
320 title = _form['pullrequest_title']
320 title = _form['pullrequest_title']
321 description = _form['pullrequest_desc']
321 description = _form['pullrequest_desc']
322
323 try:
322 try:
324 pull_request = PullRequestModel().create(
323 pull_request = PullRequestModel().create(
325 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
324 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
326 other_ref, revisions, reviewers, title, description
325 other_ref, revisions, reviewers, title, description
327 )
326 )
328 Session().commit()
327 Session().commit()
329 h.flash(_('Successfully opened new pull request'),
328 h.flash(_('Successfully opened new pull request'),
330 category='success')
329 category='success')
331 except Exception:
330 except Exception:
332 h.flash(_('Error occurred during sending pull request'),
331 h.flash(_('Error occurred during sending pull request'),
333 category='error')
332 category='error')
334 log.error(traceback.format_exc())
333 log.error(traceback.format_exc())
335 return redirect(url('pullrequest_home', repo_name=repo_name))
334 return redirect(url('pullrequest_home', repo_name=repo_name))
336
335
337 return redirect(url('pullrequest_show', repo_name=other_repo,
336 return redirect(url('pullrequest_show', repo_name=other_repo,
338 pull_request_id=pull_request.pull_request_id))
337 pull_request_id=pull_request.pull_request_id))
339
338
340 @LoginRequired()
339 @LoginRequired()
341 @NotAnonymous()
340 @NotAnonymous()
342 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
341 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
343 'repository.admin')
342 'repository.admin')
344 @jsonify
343 @jsonify
345 def update(self, repo_name, pull_request_id):
344 def update(self, repo_name, pull_request_id):
346 pull_request = PullRequest.get_or_404(pull_request_id)
345 pull_request = PullRequest.get_or_404(pull_request_id)
347 if pull_request.is_closed():
346 if pull_request.is_closed():
348 raise HTTPForbidden()
347 raise HTTPForbidden()
349 #only owner or admin can update it
348 #only owner or admin can update it
350 owner = pull_request.author.user_id == c.rhodecode_user.user_id
349 owner = pull_request.author.user_id == c.rhodecode_user.user_id
351 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
350 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
352 reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
351 reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
353 request.POST.get('reviewers_ids', '').split(',')))
352 request.POST.get('reviewers_ids', '').split(',')))
354
353
355 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
354 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
356 Session().commit()
355 Session().commit()
357 return True
356 return True
358 raise HTTPForbidden()
357 raise HTTPForbidden()
359
358
360 @LoginRequired()
359 @LoginRequired()
361 @NotAnonymous()
360 @NotAnonymous()
362 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
361 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
363 'repository.admin')
362 'repository.admin')
364 @jsonify
363 @jsonify
365 def delete(self, repo_name, pull_request_id):
364 def delete(self, repo_name, pull_request_id):
366 pull_request = PullRequest.get_or_404(pull_request_id)
365 pull_request = PullRequest.get_or_404(pull_request_id)
367 #only owner can delete it !
366 #only owner can delete it !
368 if pull_request.author.user_id == c.rhodecode_user.user_id:
367 if pull_request.author.user_id == c.rhodecode_user.user_id:
369 PullRequestModel().delete(pull_request)
368 PullRequestModel().delete(pull_request)
370 Session().commit()
369 Session().commit()
371 h.flash(_('Successfully deleted pull request'),
370 h.flash(_('Successfully deleted pull request'),
372 category='success')
371 category='success')
373 return redirect(url('admin_settings_my_account', anchor='pullrequests'))
372 return redirect(url('admin_settings_my_account', anchor='pullrequests'))
374 raise HTTPForbidden()
373 raise HTTPForbidden()
375
374
376 @LoginRequired()
375 @LoginRequired()
377 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
376 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
378 'repository.admin')
377 'repository.admin')
379 def show(self, repo_name, pull_request_id):
378 def show(self, repo_name, pull_request_id):
380 repo_model = RepoModel()
379 repo_model = RepoModel()
381 c.users_array = repo_model.get_users_js()
380 c.users_array = repo_model.get_users_js()
382 c.users_groups_array = repo_model.get_users_groups_js()
381 c.users_groups_array = repo_model.get_users_groups_js()
383 c.pull_request = PullRequest.get_or_404(pull_request_id)
382 c.pull_request = PullRequest.get_or_404(pull_request_id)
384 c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request)
383 c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request)
385 cc_model = ChangesetCommentsModel()
384 cc_model = ChangesetCommentsModel()
386 cs_model = ChangesetStatusModel()
385 cs_model = ChangesetStatusModel()
387 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
386 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
388 pull_request=c.pull_request,
387 pull_request=c.pull_request,
389 with_revisions=True)
388 with_revisions=True)
390
389
391 cs_statuses = defaultdict(list)
390 cs_statuses = defaultdict(list)
392 for st in _cs_statuses:
391 for st in _cs_statuses:
393 cs_statuses[st.author.username] += [st]
392 cs_statuses[st.author.username] += [st]
394
393
395 c.pull_request_reviewers = []
394 c.pull_request_reviewers = []
396 c.pull_request_pending_reviewers = []
395 c.pull_request_pending_reviewers = []
397 for o in c.pull_request.reviewers:
396 for o in c.pull_request.reviewers:
398 st = cs_statuses.get(o.user.username, None)
397 st = cs_statuses.get(o.user.username, None)
399 if st:
398 if st:
400 sorter = lambda k: k.version
399 sorter = lambda k: k.version
401 st = [(x, list(y)[0])
400 st = [(x, list(y)[0])
402 for x, y in (groupby(sorted(st, key=sorter), sorter))]
401 for x, y in (groupby(sorted(st, key=sorter), sorter))]
403 else:
402 else:
404 c.pull_request_pending_reviewers.append(o.user)
403 c.pull_request_pending_reviewers.append(o.user)
405 c.pull_request_reviewers.append([o.user, st])
404 c.pull_request_reviewers.append([o.user, st])
406
405
407 # pull_requests repo_name we opened it against
406 # pull_requests repo_name we opened it against
408 # ie. other_repo must match
407 # ie. other_repo must match
409 if repo_name != c.pull_request.other_repo.repo_name:
408 if repo_name != c.pull_request.other_repo.repo_name:
410 raise HTTPNotFound
409 raise HTTPNotFound
411
410
412 # load compare data into template context
411 # load compare data into template context
413 enable_comments = not c.pull_request.is_closed()
412 enable_comments = not c.pull_request.is_closed()
414 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
413 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
415
414
416 # inline comments
415 # inline comments
417 c.inline_cnt = 0
416 c.inline_cnt = 0
418 c.inline_comments = cc_model.get_inline_comments(
417 c.inline_comments = cc_model.get_inline_comments(
419 c.rhodecode_db_repo.repo_id,
418 c.rhodecode_db_repo.repo_id,
420 pull_request=pull_request_id)
419 pull_request=pull_request_id)
421 # count inline comments
420 # count inline comments
422 for __, lines in c.inline_comments:
421 for __, lines in c.inline_comments:
423 for comments in lines.values():
422 for comments in lines.values():
424 c.inline_cnt += len(comments)
423 c.inline_cnt += len(comments)
425 # comments
424 # comments
426 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
425 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
427 pull_request=pull_request_id)
426 pull_request=pull_request_id)
428
427
429 try:
428 try:
430 cur_status = c.statuses[c.pull_request.revisions[0]][0]
429 cur_status = c.statuses[c.pull_request.revisions[0]][0]
431 except Exception:
430 except Exception:
432 log.error(traceback.format_exc())
431 log.error(traceback.format_exc())
433 cur_status = 'undefined'
432 cur_status = 'undefined'
434 if c.pull_request.is_closed() and 0:
433 if c.pull_request.is_closed() and 0:
435 c.current_changeset_status = cur_status
434 c.current_changeset_status = cur_status
436 else:
435 else:
437 # changeset(pull-request) status calulation based on reviewers
436 # changeset(pull-request) status calulation based on reviewers
438 c.current_changeset_status = cs_model.calculate_status(
437 c.current_changeset_status = cs_model.calculate_status(
439 c.pull_request_reviewers,
438 c.pull_request_reviewers,
440 )
439 )
441 c.changeset_statuses = ChangesetStatus.STATUSES
440 c.changeset_statuses = ChangesetStatus.STATUSES
442
441
443 c.as_form = False
442 c.as_form = False
444 c.ancestor = None # there is one - but right here we don't know which
443 c.ancestor = None # there is one - but right here we don't know which
445 return render('/pullrequests/pullrequest_show.html')
444 return render('/pullrequests/pullrequest_show.html')
446
445
447 @LoginRequired()
446 @LoginRequired()
448 @NotAnonymous()
447 @NotAnonymous()
449 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
448 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
450 'repository.admin')
449 'repository.admin')
451 @jsonify
450 @jsonify
452 def comment(self, repo_name, pull_request_id):
451 def comment(self, repo_name, pull_request_id):
453 pull_request = PullRequest.get_or_404(pull_request_id)
452 pull_request = PullRequest.get_or_404(pull_request_id)
454 if pull_request.is_closed():
453 if pull_request.is_closed():
455 raise HTTPForbidden()
454 raise HTTPForbidden()
456
455
457 status = request.POST.get('changeset_status')
456 status = request.POST.get('changeset_status')
458 change_status = request.POST.get('change_changeset_status')
457 change_status = request.POST.get('change_changeset_status')
459 text = request.POST.get('text')
458 text = request.POST.get('text')
460 close_pr = request.POST.get('save_close')
459 close_pr = request.POST.get('save_close')
461
460
462 allowed_to_change_status = self._get_is_allowed_change_status(pull_request)
461 allowed_to_change_status = self._get_is_allowed_change_status(pull_request)
463 if status and change_status and allowed_to_change_status:
462 if status and change_status and allowed_to_change_status:
464 _def = (_('Status change -> %s')
463 _def = (_('Status change -> %s')
465 % ChangesetStatus.get_status_lbl(status))
464 % ChangesetStatus.get_status_lbl(status))
466 if close_pr:
465 if close_pr:
467 _def = _('Closing with') + ' ' + _def
466 _def = _('Closing with') + ' ' + _def
468 text = text or _def
467 text = text or _def
469 comm = ChangesetCommentsModel().create(
468 comm = ChangesetCommentsModel().create(
470 text=text,
469 text=text,
471 repo=c.rhodecode_db_repo.repo_id,
470 repo=c.rhodecode_db_repo.repo_id,
472 user=c.rhodecode_user.user_id,
471 user=c.rhodecode_user.user_id,
473 pull_request=pull_request_id,
472 pull_request=pull_request_id,
474 f_path=request.POST.get('f_path'),
473 f_path=request.POST.get('f_path'),
475 line_no=request.POST.get('line'),
474 line_no=request.POST.get('line'),
476 status_change=(ChangesetStatus.get_status_lbl(status)
475 status_change=(ChangesetStatus.get_status_lbl(status)
477 if status and change_status
476 if status and change_status
478 and allowed_to_change_status else None),
477 and allowed_to_change_status else None),
479 closing_pr=close_pr
478 closing_pr=close_pr
480 )
479 )
481
480
482 action_logger(self.rhodecode_user,
481 action_logger(self.rhodecode_user,
483 'user_commented_pull_request:%s' % pull_request_id,
482 'user_commented_pull_request:%s' % pull_request_id,
484 c.rhodecode_db_repo, self.ip_addr, self.sa)
483 c.rhodecode_db_repo, self.ip_addr, self.sa)
485
484
486 if allowed_to_change_status:
485 if allowed_to_change_status:
487 # get status if set !
486 # get status if set !
488 if status and change_status:
487 if status and change_status:
489 ChangesetStatusModel().set_status(
488 ChangesetStatusModel().set_status(
490 c.rhodecode_db_repo.repo_id,
489 c.rhodecode_db_repo.repo_id,
491 status,
490 status,
492 c.rhodecode_user.user_id,
491 c.rhodecode_user.user_id,
493 comm,
492 comm,
494 pull_request=pull_request_id
493 pull_request=pull_request_id
495 )
494 )
496
495
497 if close_pr:
496 if close_pr:
498 if status in ['rejected', 'approved']:
497 if status in ['rejected', 'approved']:
499 PullRequestModel().close_pull_request(pull_request_id)
498 PullRequestModel().close_pull_request(pull_request_id)
500 action_logger(self.rhodecode_user,
499 action_logger(self.rhodecode_user,
501 'user_closed_pull_request:%s' % pull_request_id,
500 'user_closed_pull_request:%s' % pull_request_id,
502 c.rhodecode_db_repo, self.ip_addr, self.sa)
501 c.rhodecode_db_repo, self.ip_addr, self.sa)
503 else:
502 else:
504 h.flash(_('Closing pull request on other statuses than '
503 h.flash(_('Closing pull request on other statuses than '
505 'rejected or approved forbidden'),
504 'rejected or approved forbidden'),
506 category='warning')
505 category='warning')
507
506
508 Session().commit()
507 Session().commit()
509
508
510 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
509 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
511 return redirect(h.url('pullrequest_show', repo_name=repo_name,
510 return redirect(h.url('pullrequest_show', repo_name=repo_name,
512 pull_request_id=pull_request_id))
511 pull_request_id=pull_request_id))
513
512
514 data = {
513 data = {
515 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
514 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
516 }
515 }
517 if comm:
516 if comm:
518 c.co = comm
517 c.co = comm
519 data.update(comm.get_dict())
518 data.update(comm.get_dict())
520 data.update({'rendered_text':
519 data.update({'rendered_text':
521 render('changeset/changeset_comment_block.html')})
520 render('changeset/changeset_comment_block.html')})
522
521
523 return data
522 return data
524
523
525 @LoginRequired()
524 @LoginRequired()
526 @NotAnonymous()
525 @NotAnonymous()
527 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
526 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
528 'repository.admin')
527 'repository.admin')
529 @jsonify
528 @jsonify
530 def delete_comment(self, repo_name, comment_id):
529 def delete_comment(self, repo_name, comment_id):
531 co = ChangesetComment.get(comment_id)
530 co = ChangesetComment.get(comment_id)
532 if co.pull_request.is_closed():
531 if co.pull_request.is_closed():
533 #don't allow deleting comments on closed pull request
532 #don't allow deleting comments on closed pull request
534 raise HTTPForbidden()
533 raise HTTPForbidden()
535
534
536 owner = co.author.user_id == c.rhodecode_user.user_id
535 owner = co.author.user_id == c.rhodecode_user.user_id
537 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
536 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
538 ChangesetCommentsModel().delete(comment=co)
537 ChangesetCommentsModel().delete(comment=co)
539 Session().commit()
538 Session().commit()
540 return True
539 return True
541 else:
540 else:
542 raise HTTPForbidden()
541 raise HTTPForbidden()
General Comments 0
You need to be logged in to leave comments. Login now