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