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