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