##// END OF EJS Templates
pull-requests: remove template context registration as it's not...
marcink -
r1376:c68619c4 default
parent child Browse files
Show More
@@ -1,1046 +1,1042 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2017 RhodeCode GmbH
3 # Copyright (C) 2012-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 pull requests controller for rhodecode for initializing pull requests
22 pull requests controller for rhodecode for initializing pull requests
23 """
23 """
24 import types
24 import types
25
25
26 import peppercorn
26 import peppercorn
27 import formencode
27 import formencode
28 import logging
28 import logging
29 import collections
29 import collections
30
30
31 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPBadRequest
31 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPBadRequest
32 from pylons import request, tmpl_context as c, url
32 from pylons import request, tmpl_context as c, url
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35 from pyramid.threadlocal import get_current_registry
35 from pyramid.threadlocal import get_current_registry
36 from sqlalchemy.sql import func
36 from sqlalchemy.sql import func
37 from sqlalchemy.sql.expression import or_
37 from sqlalchemy.sql.expression import or_
38
38
39 from rhodecode import events
39 from rhodecode import events
40 from rhodecode.lib import auth, diffs, helpers as h, codeblocks
40 from rhodecode.lib import auth, diffs, helpers as h, codeblocks
41 from rhodecode.lib.ext_json import json
41 from rhodecode.lib.ext_json import json
42 from rhodecode.lib.base import (
42 from rhodecode.lib.base import (
43 BaseRepoController, render, vcs_operation_context)
43 BaseRepoController, render, vcs_operation_context)
44 from rhodecode.lib.auth import (
44 from rhodecode.lib.auth import (
45 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous,
45 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous,
46 HasAcceptedRepoType, XHRRequired)
46 HasAcceptedRepoType, XHRRequired)
47 from rhodecode.lib.channelstream import channelstream_request
47 from rhodecode.lib.channelstream import channelstream_request
48 from rhodecode.lib.utils import jsonify
48 from rhodecode.lib.utils import jsonify
49 from rhodecode.lib.utils2 import (
49 from rhodecode.lib.utils2 import (
50 safe_int, safe_str, str2bool, safe_unicode)
50 safe_int, safe_str, str2bool, safe_unicode)
51 from rhodecode.lib.vcs.backends.base import (
51 from rhodecode.lib.vcs.backends.base import (
52 EmptyCommit, UpdateFailureReason, EmptyRepository)
52 EmptyCommit, UpdateFailureReason, EmptyRepository)
53 from rhodecode.lib.vcs.exceptions import (
53 from rhodecode.lib.vcs.exceptions import (
54 EmptyRepositoryError, CommitDoesNotExistError, RepositoryRequirementError,
54 EmptyRepositoryError, CommitDoesNotExistError, RepositoryRequirementError,
55 NodeDoesNotExistError)
55 NodeDoesNotExistError)
56
56
57 from rhodecode.model.changeset_status import ChangesetStatusModel
57 from rhodecode.model.changeset_status import ChangesetStatusModel
58 from rhodecode.model.comment import CommentsModel
58 from rhodecode.model.comment import CommentsModel
59 from rhodecode.model.db import (PullRequest, ChangesetStatus, ChangesetComment,
59 from rhodecode.model.db import (PullRequest, ChangesetStatus, ChangesetComment,
60 Repository, PullRequestVersion)
60 Repository, PullRequestVersion)
61 from rhodecode.model.forms import PullRequestForm
61 from rhodecode.model.forms import PullRequestForm
62 from rhodecode.model.meta import Session
62 from rhodecode.model.meta import Session
63 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
63 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
64
64
65 log = logging.getLogger(__name__)
65 log = logging.getLogger(__name__)
66
66
67
67
68 class PullrequestsController(BaseRepoController):
68 class PullrequestsController(BaseRepoController):
69
69
70 def __before__(self):
70 def __before__(self):
71 super(PullrequestsController, self).__before__()
71 super(PullrequestsController, self).__before__()
72
72
73 def _extract_ordering(self, request):
73 def _extract_ordering(self, request):
74 column_index = safe_int(request.GET.get('order[0][column]'))
74 column_index = safe_int(request.GET.get('order[0][column]'))
75 order_dir = request.GET.get('order[0][dir]', 'desc')
75 order_dir = request.GET.get('order[0][dir]', 'desc')
76 order_by = request.GET.get(
76 order_by = request.GET.get(
77 'columns[%s][data][sort]' % column_index, 'name_raw')
77 'columns[%s][data][sort]' % column_index, 'name_raw')
78 return order_by, order_dir
78 return order_by, order_dir
79
79
80 @LoginRequired()
80 @LoginRequired()
81 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
81 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
82 'repository.admin')
82 'repository.admin')
83 @HasAcceptedRepoType('git', 'hg')
83 @HasAcceptedRepoType('git', 'hg')
84 def show_all(self, repo_name):
84 def show_all(self, repo_name):
85 # filter types
85 # filter types
86 c.active = 'open'
86 c.active = 'open'
87 c.source = str2bool(request.GET.get('source'))
87 c.source = str2bool(request.GET.get('source'))
88 c.closed = str2bool(request.GET.get('closed'))
88 c.closed = str2bool(request.GET.get('closed'))
89 c.my = str2bool(request.GET.get('my'))
89 c.my = str2bool(request.GET.get('my'))
90 c.awaiting_review = str2bool(request.GET.get('awaiting_review'))
90 c.awaiting_review = str2bool(request.GET.get('awaiting_review'))
91 c.awaiting_my_review = str2bool(request.GET.get('awaiting_my_review'))
91 c.awaiting_my_review = str2bool(request.GET.get('awaiting_my_review'))
92 c.repo_name = repo_name
92 c.repo_name = repo_name
93
93
94 opened_by = None
94 opened_by = None
95 if c.my:
95 if c.my:
96 c.active = 'my'
96 c.active = 'my'
97 opened_by = [c.rhodecode_user.user_id]
97 opened_by = [c.rhodecode_user.user_id]
98
98
99 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
99 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
100 if c.closed:
100 if c.closed:
101 c.active = 'closed'
101 c.active = 'closed'
102 statuses = [PullRequest.STATUS_CLOSED]
102 statuses = [PullRequest.STATUS_CLOSED]
103
103
104 if c.awaiting_review and not c.source:
104 if c.awaiting_review and not c.source:
105 c.active = 'awaiting'
105 c.active = 'awaiting'
106 if c.source and not c.awaiting_review:
106 if c.source and not c.awaiting_review:
107 c.active = 'source'
107 c.active = 'source'
108 if c.awaiting_my_review:
108 if c.awaiting_my_review:
109 c.active = 'awaiting_my'
109 c.active = 'awaiting_my'
110
110
111 data = self._get_pull_requests_list(
111 data = self._get_pull_requests_list(
112 repo_name=repo_name, opened_by=opened_by, statuses=statuses)
112 repo_name=repo_name, opened_by=opened_by, statuses=statuses)
113 if not request.is_xhr:
113 if not request.is_xhr:
114 c.data = json.dumps(data['data'])
114 c.data = json.dumps(data['data'])
115 c.records_total = data['recordsTotal']
115 c.records_total = data['recordsTotal']
116 return render('/pullrequests/pullrequests.mako')
116 return render('/pullrequests/pullrequests.mako')
117 else:
117 else:
118 return json.dumps(data)
118 return json.dumps(data)
119
119
120 def _get_pull_requests_list(self, repo_name, opened_by, statuses):
120 def _get_pull_requests_list(self, repo_name, opened_by, statuses):
121 # pagination
121 # pagination
122 start = safe_int(request.GET.get('start'), 0)
122 start = safe_int(request.GET.get('start'), 0)
123 length = safe_int(request.GET.get('length'), c.visual.dashboard_items)
123 length = safe_int(request.GET.get('length'), c.visual.dashboard_items)
124 order_by, order_dir = self._extract_ordering(request)
124 order_by, order_dir = self._extract_ordering(request)
125
125
126 if c.awaiting_review:
126 if c.awaiting_review:
127 pull_requests = PullRequestModel().get_awaiting_review(
127 pull_requests = PullRequestModel().get_awaiting_review(
128 repo_name, source=c.source, opened_by=opened_by,
128 repo_name, source=c.source, opened_by=opened_by,
129 statuses=statuses, offset=start, length=length,
129 statuses=statuses, offset=start, length=length,
130 order_by=order_by, order_dir=order_dir)
130 order_by=order_by, order_dir=order_dir)
131 pull_requests_total_count = PullRequestModel(
131 pull_requests_total_count = PullRequestModel(
132 ).count_awaiting_review(
132 ).count_awaiting_review(
133 repo_name, source=c.source, statuses=statuses,
133 repo_name, source=c.source, statuses=statuses,
134 opened_by=opened_by)
134 opened_by=opened_by)
135 elif c.awaiting_my_review:
135 elif c.awaiting_my_review:
136 pull_requests = PullRequestModel().get_awaiting_my_review(
136 pull_requests = PullRequestModel().get_awaiting_my_review(
137 repo_name, source=c.source, opened_by=opened_by,
137 repo_name, source=c.source, opened_by=opened_by,
138 user_id=c.rhodecode_user.user_id, statuses=statuses,
138 user_id=c.rhodecode_user.user_id, statuses=statuses,
139 offset=start, length=length, order_by=order_by,
139 offset=start, length=length, order_by=order_by,
140 order_dir=order_dir)
140 order_dir=order_dir)
141 pull_requests_total_count = PullRequestModel(
141 pull_requests_total_count = PullRequestModel(
142 ).count_awaiting_my_review(
142 ).count_awaiting_my_review(
143 repo_name, source=c.source, user_id=c.rhodecode_user.user_id,
143 repo_name, source=c.source, user_id=c.rhodecode_user.user_id,
144 statuses=statuses, opened_by=opened_by)
144 statuses=statuses, opened_by=opened_by)
145 else:
145 else:
146 pull_requests = PullRequestModel().get_all(
146 pull_requests = PullRequestModel().get_all(
147 repo_name, source=c.source, opened_by=opened_by,
147 repo_name, source=c.source, opened_by=opened_by,
148 statuses=statuses, offset=start, length=length,
148 statuses=statuses, offset=start, length=length,
149 order_by=order_by, order_dir=order_dir)
149 order_by=order_by, order_dir=order_dir)
150 pull_requests_total_count = PullRequestModel().count_all(
150 pull_requests_total_count = PullRequestModel().count_all(
151 repo_name, source=c.source, statuses=statuses,
151 repo_name, source=c.source, statuses=statuses,
152 opened_by=opened_by)
152 opened_by=opened_by)
153
153
154 from rhodecode.lib.utils import PartialRenderer
154 from rhodecode.lib.utils import PartialRenderer
155 _render = PartialRenderer('data_table/_dt_elements.mako')
155 _render = PartialRenderer('data_table/_dt_elements.mako')
156 data = []
156 data = []
157 for pr in pull_requests:
157 for pr in pull_requests:
158 comments = CommentsModel().get_all_comments(
158 comments = CommentsModel().get_all_comments(
159 c.rhodecode_db_repo.repo_id, pull_request=pr)
159 c.rhodecode_db_repo.repo_id, pull_request=pr)
160
160
161 data.append({
161 data.append({
162 'name': _render('pullrequest_name',
162 'name': _render('pullrequest_name',
163 pr.pull_request_id, pr.target_repo.repo_name),
163 pr.pull_request_id, pr.target_repo.repo_name),
164 'name_raw': pr.pull_request_id,
164 'name_raw': pr.pull_request_id,
165 'status': _render('pullrequest_status',
165 'status': _render('pullrequest_status',
166 pr.calculated_review_status()),
166 pr.calculated_review_status()),
167 'title': _render(
167 'title': _render(
168 'pullrequest_title', pr.title, pr.description),
168 'pullrequest_title', pr.title, pr.description),
169 'description': h.escape(pr.description),
169 'description': h.escape(pr.description),
170 'updated_on': _render('pullrequest_updated_on',
170 'updated_on': _render('pullrequest_updated_on',
171 h.datetime_to_time(pr.updated_on)),
171 h.datetime_to_time(pr.updated_on)),
172 'updated_on_raw': h.datetime_to_time(pr.updated_on),
172 'updated_on_raw': h.datetime_to_time(pr.updated_on),
173 'created_on': _render('pullrequest_updated_on',
173 'created_on': _render('pullrequest_updated_on',
174 h.datetime_to_time(pr.created_on)),
174 h.datetime_to_time(pr.created_on)),
175 'created_on_raw': h.datetime_to_time(pr.created_on),
175 'created_on_raw': h.datetime_to_time(pr.created_on),
176 'author': _render('pullrequest_author',
176 'author': _render('pullrequest_author',
177 pr.author.full_contact, ),
177 pr.author.full_contact, ),
178 'author_raw': pr.author.full_name,
178 'author_raw': pr.author.full_name,
179 'comments': _render('pullrequest_comments', len(comments)),
179 'comments': _render('pullrequest_comments', len(comments)),
180 'comments_raw': len(comments),
180 'comments_raw': len(comments),
181 'closed': pr.is_closed(),
181 'closed': pr.is_closed(),
182 })
182 })
183 # json used to render the grid
183 # json used to render the grid
184 data = ({
184 data = ({
185 'data': data,
185 'data': data,
186 'recordsTotal': pull_requests_total_count,
186 'recordsTotal': pull_requests_total_count,
187 'recordsFiltered': pull_requests_total_count,
187 'recordsFiltered': pull_requests_total_count,
188 })
188 })
189 return data
189 return data
190
190
191 @LoginRequired()
191 @LoginRequired()
192 @NotAnonymous()
192 @NotAnonymous()
193 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
193 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
194 'repository.admin')
194 'repository.admin')
195 @HasAcceptedRepoType('git', 'hg')
195 @HasAcceptedRepoType('git', 'hg')
196 def index(self):
196 def index(self):
197 source_repo = c.rhodecode_db_repo
197 source_repo = c.rhodecode_db_repo
198
198
199 try:
199 try:
200 source_repo.scm_instance().get_commit()
200 source_repo.scm_instance().get_commit()
201 except EmptyRepositoryError:
201 except EmptyRepositoryError:
202 h.flash(h.literal(_('There are no commits yet')),
202 h.flash(h.literal(_('There are no commits yet')),
203 category='warning')
203 category='warning')
204 redirect(url('summary_home', repo_name=source_repo.repo_name))
204 redirect(url('summary_home', repo_name=source_repo.repo_name))
205
205
206 commit_id = request.GET.get('commit')
206 commit_id = request.GET.get('commit')
207 branch_ref = request.GET.get('branch')
207 branch_ref = request.GET.get('branch')
208 bookmark_ref = request.GET.get('bookmark')
208 bookmark_ref = request.GET.get('bookmark')
209
209
210 try:
210 try:
211 source_repo_data = PullRequestModel().generate_repo_data(
211 source_repo_data = PullRequestModel().generate_repo_data(
212 source_repo, commit_id=commit_id,
212 source_repo, commit_id=commit_id,
213 branch=branch_ref, bookmark=bookmark_ref)
213 branch=branch_ref, bookmark=bookmark_ref)
214 except CommitDoesNotExistError as e:
214 except CommitDoesNotExistError as e:
215 log.exception(e)
215 log.exception(e)
216 h.flash(_('Commit does not exist'), 'error')
216 h.flash(_('Commit does not exist'), 'error')
217 redirect(url('pullrequest_home', repo_name=source_repo.repo_name))
217 redirect(url('pullrequest_home', repo_name=source_repo.repo_name))
218
218
219 default_target_repo = source_repo
219 default_target_repo = source_repo
220
220
221 if source_repo.parent:
221 if source_repo.parent:
222 parent_vcs_obj = source_repo.parent.scm_instance()
222 parent_vcs_obj = source_repo.parent.scm_instance()
223 if parent_vcs_obj and not parent_vcs_obj.is_empty():
223 if parent_vcs_obj and not parent_vcs_obj.is_empty():
224 # change default if we have a parent repo
224 # change default if we have a parent repo
225 default_target_repo = source_repo.parent
225 default_target_repo = source_repo.parent
226
226
227 target_repo_data = PullRequestModel().generate_repo_data(
227 target_repo_data = PullRequestModel().generate_repo_data(
228 default_target_repo)
228 default_target_repo)
229
229
230 selected_source_ref = source_repo_data['refs']['selected_ref']
230 selected_source_ref = source_repo_data['refs']['selected_ref']
231
231
232 title_source_ref = selected_source_ref.split(':', 2)[1]
232 title_source_ref = selected_source_ref.split(':', 2)[1]
233 c.default_title = PullRequestModel().generate_pullrequest_title(
233 c.default_title = PullRequestModel().generate_pullrequest_title(
234 source=source_repo.repo_name,
234 source=source_repo.repo_name,
235 source_ref=title_source_ref,
235 source_ref=title_source_ref,
236 target=default_target_repo.repo_name
236 target=default_target_repo.repo_name
237 )
237 )
238
238
239 c.default_repo_data = {
239 c.default_repo_data = {
240 'source_repo_name': source_repo.repo_name,
240 'source_repo_name': source_repo.repo_name,
241 'source_refs_json': json.dumps(source_repo_data),
241 'source_refs_json': json.dumps(source_repo_data),
242 'target_repo_name': default_target_repo.repo_name,
242 'target_repo_name': default_target_repo.repo_name,
243 'target_refs_json': json.dumps(target_repo_data),
243 'target_refs_json': json.dumps(target_repo_data),
244 }
244 }
245 c.default_source_ref = selected_source_ref
245 c.default_source_ref = selected_source_ref
246
246
247 return render('/pullrequests/pullrequest.mako')
247 return render('/pullrequests/pullrequest.mako')
248
248
249 @LoginRequired()
249 @LoginRequired()
250 @NotAnonymous()
250 @NotAnonymous()
251 @XHRRequired()
251 @XHRRequired()
252 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
252 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
253 'repository.admin')
253 'repository.admin')
254 @jsonify
254 @jsonify
255 def get_repo_refs(self, repo_name, target_repo_name):
255 def get_repo_refs(self, repo_name, target_repo_name):
256 repo = Repository.get_by_repo_name(target_repo_name)
256 repo = Repository.get_by_repo_name(target_repo_name)
257 if not repo:
257 if not repo:
258 raise HTTPNotFound
258 raise HTTPNotFound
259 return PullRequestModel().generate_repo_data(repo)
259 return PullRequestModel().generate_repo_data(repo)
260
260
261 @LoginRequired()
261 @LoginRequired()
262 @NotAnonymous()
262 @NotAnonymous()
263 @XHRRequired()
263 @XHRRequired()
264 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
264 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
265 'repository.admin')
265 'repository.admin')
266 @jsonify
266 @jsonify
267 def get_repo_destinations(self, repo_name):
267 def get_repo_destinations(self, repo_name):
268 repo = Repository.get_by_repo_name(repo_name)
268 repo = Repository.get_by_repo_name(repo_name)
269 if not repo:
269 if not repo:
270 raise HTTPNotFound
270 raise HTTPNotFound
271 filter_query = request.GET.get('query')
271 filter_query = request.GET.get('query')
272
272
273 query = Repository.query() \
273 query = Repository.query() \
274 .order_by(func.length(Repository.repo_name)) \
274 .order_by(func.length(Repository.repo_name)) \
275 .filter(or_(
275 .filter(or_(
276 Repository.repo_name == repo.repo_name,
276 Repository.repo_name == repo.repo_name,
277 Repository.fork_id == repo.repo_id))
277 Repository.fork_id == repo.repo_id))
278
278
279 if filter_query:
279 if filter_query:
280 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
280 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
281 query = query.filter(
281 query = query.filter(
282 Repository.repo_name.ilike(ilike_expression))
282 Repository.repo_name.ilike(ilike_expression))
283
283
284 add_parent = False
284 add_parent = False
285 if repo.parent:
285 if repo.parent:
286 if filter_query in repo.parent.repo_name:
286 if filter_query in repo.parent.repo_name:
287 parent_vcs_obj = repo.parent.scm_instance()
287 parent_vcs_obj = repo.parent.scm_instance()
288 if parent_vcs_obj and not parent_vcs_obj.is_empty():
288 if parent_vcs_obj and not parent_vcs_obj.is_empty():
289 add_parent = True
289 add_parent = True
290
290
291 limit = 20 - 1 if add_parent else 20
291 limit = 20 - 1 if add_parent else 20
292 all_repos = query.limit(limit).all()
292 all_repos = query.limit(limit).all()
293 if add_parent:
293 if add_parent:
294 all_repos += [repo.parent]
294 all_repos += [repo.parent]
295
295
296 repos = []
296 repos = []
297 for obj in self.scm_model.get_repos(all_repos):
297 for obj in self.scm_model.get_repos(all_repos):
298 repos.append({
298 repos.append({
299 'id': obj['name'],
299 'id': obj['name'],
300 'text': obj['name'],
300 'text': obj['name'],
301 'type': 'repo',
301 'type': 'repo',
302 'obj': obj['dbrepo']
302 'obj': obj['dbrepo']
303 })
303 })
304
304
305 data = {
305 data = {
306 'more': False,
306 'more': False,
307 'results': [{
307 'results': [{
308 'text': _('Repositories'),
308 'text': _('Repositories'),
309 'children': repos
309 'children': repos
310 }] if repos else []
310 }] if repos else []
311 }
311 }
312 return data
312 return data
313
313
314 @LoginRequired()
314 @LoginRequired()
315 @NotAnonymous()
315 @NotAnonymous()
316 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
316 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
317 'repository.admin')
317 'repository.admin')
318 @HasAcceptedRepoType('git', 'hg')
318 @HasAcceptedRepoType('git', 'hg')
319 @auth.CSRFRequired()
319 @auth.CSRFRequired()
320 def create(self, repo_name):
320 def create(self, repo_name):
321 repo = Repository.get_by_repo_name(repo_name)
321 repo = Repository.get_by_repo_name(repo_name)
322 if not repo:
322 if not repo:
323 raise HTTPNotFound
323 raise HTTPNotFound
324
324
325 controls = peppercorn.parse(request.POST.items())
325 controls = peppercorn.parse(request.POST.items())
326
326
327 try:
327 try:
328 _form = PullRequestForm(repo.repo_id)().to_python(controls)
328 _form = PullRequestForm(repo.repo_id)().to_python(controls)
329 except formencode.Invalid as errors:
329 except formencode.Invalid as errors:
330 if errors.error_dict.get('revisions'):
330 if errors.error_dict.get('revisions'):
331 msg = 'Revisions: %s' % errors.error_dict['revisions']
331 msg = 'Revisions: %s' % errors.error_dict['revisions']
332 elif errors.error_dict.get('pullrequest_title'):
332 elif errors.error_dict.get('pullrequest_title'):
333 msg = _('Pull request requires a title with min. 3 chars')
333 msg = _('Pull request requires a title with min. 3 chars')
334 else:
334 else:
335 msg = _('Error creating pull request: {}').format(errors)
335 msg = _('Error creating pull request: {}').format(errors)
336 log.exception(msg)
336 log.exception(msg)
337 h.flash(msg, 'error')
337 h.flash(msg, 'error')
338
338
339 # would rather just go back to form ...
339 # would rather just go back to form ...
340 return redirect(url('pullrequest_home', repo_name=repo_name))
340 return redirect(url('pullrequest_home', repo_name=repo_name))
341
341
342 source_repo = _form['source_repo']
342 source_repo = _form['source_repo']
343 source_ref = _form['source_ref']
343 source_ref = _form['source_ref']
344 target_repo = _form['target_repo']
344 target_repo = _form['target_repo']
345 target_ref = _form['target_ref']
345 target_ref = _form['target_ref']
346 commit_ids = _form['revisions'][::-1]
346 commit_ids = _form['revisions'][::-1]
347 reviewers = [
347 reviewers = [
348 (r['user_id'], r['reasons']) for r in _form['review_members']]
348 (r['user_id'], r['reasons']) for r in _form['review_members']]
349
349
350 # find the ancestor for this pr
350 # find the ancestor for this pr
351 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
351 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
352 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
352 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
353
353
354 source_scm = source_db_repo.scm_instance()
354 source_scm = source_db_repo.scm_instance()
355 target_scm = target_db_repo.scm_instance()
355 target_scm = target_db_repo.scm_instance()
356
356
357 source_commit = source_scm.get_commit(source_ref.split(':')[-1])
357 source_commit = source_scm.get_commit(source_ref.split(':')[-1])
358 target_commit = target_scm.get_commit(target_ref.split(':')[-1])
358 target_commit = target_scm.get_commit(target_ref.split(':')[-1])
359
359
360 ancestor = source_scm.get_common_ancestor(
360 ancestor = source_scm.get_common_ancestor(
361 source_commit.raw_id, target_commit.raw_id, target_scm)
361 source_commit.raw_id, target_commit.raw_id, target_scm)
362
362
363 target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
363 target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
364 target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
364 target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
365
365
366 pullrequest_title = _form['pullrequest_title']
366 pullrequest_title = _form['pullrequest_title']
367 title_source_ref = source_ref.split(':', 2)[1]
367 title_source_ref = source_ref.split(':', 2)[1]
368 if not pullrequest_title:
368 if not pullrequest_title:
369 pullrequest_title = PullRequestModel().generate_pullrequest_title(
369 pullrequest_title = PullRequestModel().generate_pullrequest_title(
370 source=source_repo,
370 source=source_repo,
371 source_ref=title_source_ref,
371 source_ref=title_source_ref,
372 target=target_repo
372 target=target_repo
373 )
373 )
374
374
375 description = _form['pullrequest_desc']
375 description = _form['pullrequest_desc']
376 try:
376 try:
377 pull_request = PullRequestModel().create(
377 pull_request = PullRequestModel().create(
378 c.rhodecode_user.user_id, source_repo, source_ref, target_repo,
378 c.rhodecode_user.user_id, source_repo, source_ref, target_repo,
379 target_ref, commit_ids, reviewers, pullrequest_title,
379 target_ref, commit_ids, reviewers, pullrequest_title,
380 description
380 description
381 )
381 )
382 Session().commit()
382 Session().commit()
383 h.flash(_('Successfully opened new pull request'),
383 h.flash(_('Successfully opened new pull request'),
384 category='success')
384 category='success')
385 except Exception as e:
385 except Exception as e:
386 msg = _('Error occurred during sending pull request')
386 msg = _('Error occurred during sending pull request')
387 log.exception(msg)
387 log.exception(msg)
388 h.flash(msg, category='error')
388 h.flash(msg, category='error')
389 return redirect(url('pullrequest_home', repo_name=repo_name))
389 return redirect(url('pullrequest_home', repo_name=repo_name))
390
390
391 return redirect(url('pullrequest_show', repo_name=target_repo,
391 return redirect(url('pullrequest_show', repo_name=target_repo,
392 pull_request_id=pull_request.pull_request_id))
392 pull_request_id=pull_request.pull_request_id))
393
393
394 @LoginRequired()
394 @LoginRequired()
395 @NotAnonymous()
395 @NotAnonymous()
396 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
396 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
397 'repository.admin')
397 'repository.admin')
398 @auth.CSRFRequired()
398 @auth.CSRFRequired()
399 @jsonify
399 @jsonify
400 def update(self, repo_name, pull_request_id):
400 def update(self, repo_name, pull_request_id):
401 pull_request_id = safe_int(pull_request_id)
401 pull_request_id = safe_int(pull_request_id)
402 pull_request = PullRequest.get_or_404(pull_request_id)
402 pull_request = PullRequest.get_or_404(pull_request_id)
403 # only owner or admin can update it
403 # only owner or admin can update it
404 allowed_to_update = PullRequestModel().check_user_update(
404 allowed_to_update = PullRequestModel().check_user_update(
405 pull_request, c.rhodecode_user)
405 pull_request, c.rhodecode_user)
406 if allowed_to_update:
406 if allowed_to_update:
407 controls = peppercorn.parse(request.POST.items())
407 controls = peppercorn.parse(request.POST.items())
408
408
409 if 'review_members' in controls:
409 if 'review_members' in controls:
410 self._update_reviewers(
410 self._update_reviewers(
411 pull_request_id, controls['review_members'])
411 pull_request_id, controls['review_members'])
412 elif str2bool(request.POST.get('update_commits', 'false')):
412 elif str2bool(request.POST.get('update_commits', 'false')):
413 self._update_commits(pull_request)
413 self._update_commits(pull_request)
414 elif str2bool(request.POST.get('close_pull_request', 'false')):
414 elif str2bool(request.POST.get('close_pull_request', 'false')):
415 self._reject_close(pull_request)
415 self._reject_close(pull_request)
416 elif str2bool(request.POST.get('edit_pull_request', 'false')):
416 elif str2bool(request.POST.get('edit_pull_request', 'false')):
417 self._edit_pull_request(pull_request)
417 self._edit_pull_request(pull_request)
418 else:
418 else:
419 raise HTTPBadRequest()
419 raise HTTPBadRequest()
420 return True
420 return True
421 raise HTTPForbidden()
421 raise HTTPForbidden()
422
422
423 def _edit_pull_request(self, pull_request):
423 def _edit_pull_request(self, pull_request):
424 try:
424 try:
425 PullRequestModel().edit(
425 PullRequestModel().edit(
426 pull_request, request.POST.get('title'),
426 pull_request, request.POST.get('title'),
427 request.POST.get('description'))
427 request.POST.get('description'))
428 except ValueError:
428 except ValueError:
429 msg = _(u'Cannot update closed pull requests.')
429 msg = _(u'Cannot update closed pull requests.')
430 h.flash(msg, category='error')
430 h.flash(msg, category='error')
431 return
431 return
432 else:
432 else:
433 Session().commit()
433 Session().commit()
434
434
435 msg = _(u'Pull request title & description updated.')
435 msg = _(u'Pull request title & description updated.')
436 h.flash(msg, category='success')
436 h.flash(msg, category='success')
437 return
437 return
438
438
439 def _update_commits(self, pull_request):
439 def _update_commits(self, pull_request):
440 resp = PullRequestModel().update_commits(pull_request)
440 resp = PullRequestModel().update_commits(pull_request)
441
441
442 if resp.executed:
442 if resp.executed:
443 msg = _(
443 msg = _(
444 u'Pull request updated to "{source_commit_id}" with '
444 u'Pull request updated to "{source_commit_id}" with '
445 u'{count_added} added, {count_removed} removed commits.')
445 u'{count_added} added, {count_removed} removed commits.')
446 msg = msg.format(
446 msg = msg.format(
447 source_commit_id=pull_request.source_ref_parts.commit_id,
447 source_commit_id=pull_request.source_ref_parts.commit_id,
448 count_added=len(resp.changes.added),
448 count_added=len(resp.changes.added),
449 count_removed=len(resp.changes.removed))
449 count_removed=len(resp.changes.removed))
450 h.flash(msg, category='success')
450 h.flash(msg, category='success')
451
451
452 registry = get_current_registry()
452 registry = get_current_registry()
453 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
453 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
454 channelstream_config = rhodecode_plugins.get('channelstream', {})
454 channelstream_config = rhodecode_plugins.get('channelstream', {})
455 if channelstream_config.get('enabled'):
455 if channelstream_config.get('enabled'):
456 message = msg + (
456 message = msg + (
457 ' - <a onclick="window.location.reload()">'
457 ' - <a onclick="window.location.reload()">'
458 '<strong>{}</strong></a>'.format(_('Reload page')))
458 '<strong>{}</strong></a>'.format(_('Reload page')))
459 channel = '/repo${}$/pr/{}'.format(
459 channel = '/repo${}$/pr/{}'.format(
460 pull_request.target_repo.repo_name,
460 pull_request.target_repo.repo_name,
461 pull_request.pull_request_id
461 pull_request.pull_request_id
462 )
462 )
463 payload = {
463 payload = {
464 'type': 'message',
464 'type': 'message',
465 'user': 'system',
465 'user': 'system',
466 'exclude_users': [request.user.username],
466 'exclude_users': [request.user.username],
467 'channel': channel,
467 'channel': channel,
468 'message': {
468 'message': {
469 'message': message,
469 'message': message,
470 'level': 'success',
470 'level': 'success',
471 'topic': '/notifications'
471 'topic': '/notifications'
472 }
472 }
473 }
473 }
474 channelstream_request(
474 channelstream_request(
475 channelstream_config, [payload], '/message',
475 channelstream_config, [payload], '/message',
476 raise_exc=False)
476 raise_exc=False)
477 else:
477 else:
478 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
478 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
479 warning_reasons = [
479 warning_reasons = [
480 UpdateFailureReason.NO_CHANGE,
480 UpdateFailureReason.NO_CHANGE,
481 UpdateFailureReason.WRONG_REF_TPYE,
481 UpdateFailureReason.WRONG_REF_TPYE,
482 ]
482 ]
483 category = 'warning' if resp.reason in warning_reasons else 'error'
483 category = 'warning' if resp.reason in warning_reasons else 'error'
484 h.flash(msg, category=category)
484 h.flash(msg, category=category)
485
485
486 @auth.CSRFRequired()
486 @auth.CSRFRequired()
487 @LoginRequired()
487 @LoginRequired()
488 @NotAnonymous()
488 @NotAnonymous()
489 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
489 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
490 'repository.admin')
490 'repository.admin')
491 def merge(self, repo_name, pull_request_id):
491 def merge(self, repo_name, pull_request_id):
492 """
492 """
493 POST /{repo_name}/pull-request/{pull_request_id}
493 POST /{repo_name}/pull-request/{pull_request_id}
494
494
495 Merge will perform a server-side merge of the specified
495 Merge will perform a server-side merge of the specified
496 pull request, if the pull request is approved and mergeable.
496 pull request, if the pull request is approved and mergeable.
497 After successful merging, the pull request is automatically
497 After successful merging, the pull request is automatically
498 closed, with a relevant comment.
498 closed, with a relevant comment.
499 """
499 """
500 pull_request_id = safe_int(pull_request_id)
500 pull_request_id = safe_int(pull_request_id)
501 pull_request = PullRequest.get_or_404(pull_request_id)
501 pull_request = PullRequest.get_or_404(pull_request_id)
502 user = c.rhodecode_user
502 user = c.rhodecode_user
503
503
504 check = MergeCheck.validate(pull_request, user)
504 check = MergeCheck.validate(pull_request, user)
505 merge_possible = not check.failed
505 merge_possible = not check.failed
506
506
507 for err_type, error_msg in check.errors:
507 for err_type, error_msg in check.errors:
508 h.flash(error_msg, category=err_type)
508 h.flash(error_msg, category=err_type)
509
509
510 if merge_possible:
510 if merge_possible:
511 log.debug("Pre-conditions checked, trying to merge.")
511 log.debug("Pre-conditions checked, trying to merge.")
512 extras = vcs_operation_context(
512 extras = vcs_operation_context(
513 request.environ, repo_name=pull_request.target_repo.repo_name,
513 request.environ, repo_name=pull_request.target_repo.repo_name,
514 username=user.username, action='push',
514 username=user.username, action='push',
515 scm=pull_request.target_repo.repo_type)
515 scm=pull_request.target_repo.repo_type)
516 self._merge_pull_request(pull_request, user, extras)
516 self._merge_pull_request(pull_request, user, extras)
517
517
518 return redirect(url(
518 return redirect(url(
519 'pullrequest_show',
519 'pullrequest_show',
520 repo_name=pull_request.target_repo.repo_name,
520 repo_name=pull_request.target_repo.repo_name,
521 pull_request_id=pull_request.pull_request_id))
521 pull_request_id=pull_request.pull_request_id))
522
522
523 def _merge_pull_request(self, pull_request, user, extras):
523 def _merge_pull_request(self, pull_request, user, extras):
524 merge_resp = PullRequestModel().merge(
524 merge_resp = PullRequestModel().merge(
525 pull_request, user, extras=extras)
525 pull_request, user, extras=extras)
526
526
527 if merge_resp.executed:
527 if merge_resp.executed:
528 log.debug("The merge was successful, closing the pull request.")
528 log.debug("The merge was successful, closing the pull request.")
529 PullRequestModel().close_pull_request(
529 PullRequestModel().close_pull_request(
530 pull_request.pull_request_id, user)
530 pull_request.pull_request_id, user)
531 Session().commit()
531 Session().commit()
532 msg = _('Pull request was successfully merged and closed.')
532 msg = _('Pull request was successfully merged and closed.')
533 h.flash(msg, category='success')
533 h.flash(msg, category='success')
534 else:
534 else:
535 log.debug(
535 log.debug(
536 "The merge was not successful. Merge response: %s",
536 "The merge was not successful. Merge response: %s",
537 merge_resp)
537 merge_resp)
538 msg = PullRequestModel().merge_status_message(
538 msg = PullRequestModel().merge_status_message(
539 merge_resp.failure_reason)
539 merge_resp.failure_reason)
540 h.flash(msg, category='error')
540 h.flash(msg, category='error')
541
541
542 def _update_reviewers(self, pull_request_id, review_members):
542 def _update_reviewers(self, pull_request_id, review_members):
543 reviewers = [
543 reviewers = [
544 (int(r['user_id']), r['reasons']) for r in review_members]
544 (int(r['user_id']), r['reasons']) for r in review_members]
545 PullRequestModel().update_reviewers(pull_request_id, reviewers)
545 PullRequestModel().update_reviewers(pull_request_id, reviewers)
546 Session().commit()
546 Session().commit()
547
547
548 def _reject_close(self, pull_request):
548 def _reject_close(self, pull_request):
549 if pull_request.is_closed():
549 if pull_request.is_closed():
550 raise HTTPForbidden()
550 raise HTTPForbidden()
551
551
552 PullRequestModel().close_pull_request_with_comment(
552 PullRequestModel().close_pull_request_with_comment(
553 pull_request, c.rhodecode_user, c.rhodecode_db_repo)
553 pull_request, c.rhodecode_user, c.rhodecode_db_repo)
554 Session().commit()
554 Session().commit()
555
555
556 @LoginRequired()
556 @LoginRequired()
557 @NotAnonymous()
557 @NotAnonymous()
558 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
558 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
559 'repository.admin')
559 'repository.admin')
560 @auth.CSRFRequired()
560 @auth.CSRFRequired()
561 @jsonify
561 @jsonify
562 def delete(self, repo_name, pull_request_id):
562 def delete(self, repo_name, pull_request_id):
563 pull_request_id = safe_int(pull_request_id)
563 pull_request_id = safe_int(pull_request_id)
564 pull_request = PullRequest.get_or_404(pull_request_id)
564 pull_request = PullRequest.get_or_404(pull_request_id)
565 # only owner can delete it !
565 # only owner can delete it !
566 if pull_request.author.user_id == c.rhodecode_user.user_id:
566 if pull_request.author.user_id == c.rhodecode_user.user_id:
567 PullRequestModel().delete(pull_request)
567 PullRequestModel().delete(pull_request)
568 Session().commit()
568 Session().commit()
569 h.flash(_('Successfully deleted pull request'),
569 h.flash(_('Successfully deleted pull request'),
570 category='success')
570 category='success')
571 return redirect(url('my_account_pullrequests'))
571 return redirect(url('my_account_pullrequests'))
572 raise HTTPForbidden()
572 raise HTTPForbidden()
573
573
574 def _get_pr_version(self, pull_request_id, version=None):
574 def _get_pr_version(self, pull_request_id, version=None):
575 pull_request_id = safe_int(pull_request_id)
575 pull_request_id = safe_int(pull_request_id)
576 at_version = None
576 at_version = None
577
577
578 if version and version == 'latest':
578 if version and version == 'latest':
579 pull_request_ver = PullRequest.get(pull_request_id)
579 pull_request_ver = PullRequest.get(pull_request_id)
580 pull_request_obj = pull_request_ver
580 pull_request_obj = pull_request_ver
581 _org_pull_request_obj = pull_request_obj
581 _org_pull_request_obj = pull_request_obj
582 at_version = 'latest'
582 at_version = 'latest'
583 elif version:
583 elif version:
584 pull_request_ver = PullRequestVersion.get_or_404(version)
584 pull_request_ver = PullRequestVersion.get_or_404(version)
585 pull_request_obj = pull_request_ver
585 pull_request_obj = pull_request_ver
586 _org_pull_request_obj = pull_request_ver.pull_request
586 _org_pull_request_obj = pull_request_ver.pull_request
587 at_version = pull_request_ver.pull_request_version_id
587 at_version = pull_request_ver.pull_request_version_id
588 else:
588 else:
589 _org_pull_request_obj = pull_request_obj = PullRequest.get_or_404(pull_request_id)
589 _org_pull_request_obj = pull_request_obj = PullRequest.get_or_404(pull_request_id)
590
590
591 pull_request_display_obj = PullRequest.get_pr_display_object(
591 pull_request_display_obj = PullRequest.get_pr_display_object(
592 pull_request_obj, _org_pull_request_obj)
592 pull_request_obj, _org_pull_request_obj)
593
593
594 return _org_pull_request_obj, pull_request_obj, \
594 return _org_pull_request_obj, pull_request_obj, \
595 pull_request_display_obj, at_version
595 pull_request_display_obj, at_version
596
596
597 @LoginRequired()
597 @LoginRequired()
598 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
598 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
599 'repository.admin')
599 'repository.admin')
600 def show(self, repo_name, pull_request_id):
600 def show(self, repo_name, pull_request_id):
601 pull_request_id = safe_int(pull_request_id)
601 pull_request_id = safe_int(pull_request_id)
602 version = request.GET.get('version')
602 version = request.GET.get('version')
603 from_version = request.GET.get('from_version') or version
603 from_version = request.GET.get('from_version') or version
604 merge_checks = request.GET.get('merge_checks')
604 merge_checks = request.GET.get('merge_checks')
605 c.fulldiff = str2bool(request.GET.get('fulldiff'))
605 c.fulldiff = str2bool(request.GET.get('fulldiff'))
606
606
607 # register for JS templates
608 c.template_context['pull_request_data']['pull_request_id'] = \
609 pull_request_id
610
611 (pull_request_latest,
607 (pull_request_latest,
612 pull_request_at_ver,
608 pull_request_at_ver,
613 pull_request_display_obj,
609 pull_request_display_obj,
614 at_version) = self._get_pr_version(
610 at_version) = self._get_pr_version(
615 pull_request_id, version=version)
611 pull_request_id, version=version)
616 versions = pull_request_display_obj.versions()
612 versions = pull_request_display_obj.versions()
617
613
618 c.at_version = at_version
614 c.at_version = at_version
619 c.at_version_num = (at_version
615 c.at_version_num = (at_version
620 if at_version and at_version != 'latest'
616 if at_version and at_version != 'latest'
621 else None)
617 else None)
622 c.at_version_pos = ChangesetComment.get_index_from_version(
618 c.at_version_pos = ChangesetComment.get_index_from_version(
623 c.at_version_num, versions)
619 c.at_version_num, versions)
624
620
625 (prev_pull_request_latest,
621 (prev_pull_request_latest,
626 prev_pull_request_at_ver,
622 prev_pull_request_at_ver,
627 prev_pull_request_display_obj,
623 prev_pull_request_display_obj,
628 prev_at_version) = self._get_pr_version(
624 prev_at_version) = self._get_pr_version(
629 pull_request_id, version=from_version)
625 pull_request_id, version=from_version)
630
626
631 c.from_version = prev_at_version
627 c.from_version = prev_at_version
632 c.from_version_num = (prev_at_version
628 c.from_version_num = (prev_at_version
633 if prev_at_version and prev_at_version != 'latest'
629 if prev_at_version and prev_at_version != 'latest'
634 else None)
630 else None)
635 c.from_version_pos = ChangesetComment.get_index_from_version(
631 c.from_version_pos = ChangesetComment.get_index_from_version(
636 c.from_version_num, versions)
632 c.from_version_num, versions)
637
633
638 # define if we're in COMPARE mode or VIEW at version mode
634 # define if we're in COMPARE mode or VIEW at version mode
639 compare = at_version != prev_at_version
635 compare = at_version != prev_at_version
640
636
641 # pull_requests repo_name we opened it against
637 # pull_requests repo_name we opened it against
642 # ie. target_repo must match
638 # ie. target_repo must match
643 if repo_name != pull_request_at_ver.target_repo.repo_name:
639 if repo_name != pull_request_at_ver.target_repo.repo_name:
644 raise HTTPNotFound
640 raise HTTPNotFound
645
641
646 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
642 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
647 pull_request_at_ver)
643 pull_request_at_ver)
648
644
649 c.ancestor = None # empty ancestor hidden in display
645 c.ancestor = None # empty ancestor hidden in display
650 c.pull_request = pull_request_display_obj
646 c.pull_request = pull_request_display_obj
651 c.pull_request_latest = pull_request_latest
647 c.pull_request_latest = pull_request_latest
652
648
653 pr_closed = pull_request_latest.is_closed()
649 pr_closed = pull_request_latest.is_closed()
654 if compare or (at_version and not at_version == 'latest'):
650 if compare or (at_version and not at_version == 'latest'):
655 c.allowed_to_change_status = False
651 c.allowed_to_change_status = False
656 c.allowed_to_update = False
652 c.allowed_to_update = False
657 c.allowed_to_merge = False
653 c.allowed_to_merge = False
658 c.allowed_to_delete = False
654 c.allowed_to_delete = False
659 c.allowed_to_comment = False
655 c.allowed_to_comment = False
660 else:
656 else:
661 c.allowed_to_change_status = PullRequestModel(). \
657 c.allowed_to_change_status = PullRequestModel(). \
662 check_user_change_status(pull_request_at_ver, c.rhodecode_user)
658 check_user_change_status(pull_request_at_ver, c.rhodecode_user)
663 c.allowed_to_update = PullRequestModel().check_user_update(
659 c.allowed_to_update = PullRequestModel().check_user_update(
664 pull_request_latest, c.rhodecode_user) and not pr_closed
660 pull_request_latest, c.rhodecode_user) and not pr_closed
665 c.allowed_to_merge = PullRequestModel().check_user_merge(
661 c.allowed_to_merge = PullRequestModel().check_user_merge(
666 pull_request_latest, c.rhodecode_user) and not pr_closed
662 pull_request_latest, c.rhodecode_user) and not pr_closed
667 c.allowed_to_delete = PullRequestModel().check_user_delete(
663 c.allowed_to_delete = PullRequestModel().check_user_delete(
668 pull_request_latest, c.rhodecode_user) and not pr_closed
664 pull_request_latest, c.rhodecode_user) and not pr_closed
669 c.allowed_to_comment = not pr_closed
665 c.allowed_to_comment = not pr_closed
670
666
671 # check merge capabilities
667 # check merge capabilities
672 _merge_check = MergeCheck.validate(
668 _merge_check = MergeCheck.validate(
673 pull_request_latest, user=c.rhodecode_user)
669 pull_request_latest, user=c.rhodecode_user)
674 c.pr_merge_errors = _merge_check.error_details
670 c.pr_merge_errors = _merge_check.error_details
675 c.pr_merge_possible = not _merge_check.failed
671 c.pr_merge_possible = not _merge_check.failed
676 c.pr_merge_message = _merge_check.merge_msg
672 c.pr_merge_message = _merge_check.merge_msg
677
673
678 if merge_checks:
674 if merge_checks:
679 return render('/pullrequests/pullrequest_merge_checks.mako')
675 return render('/pullrequests/pullrequest_merge_checks.mako')
680
676
681 comments_model = CommentsModel()
677 comments_model = CommentsModel()
682
678
683 # reviewers and statuses
679 # reviewers and statuses
684 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
680 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
685 allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
681 allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
686 c.pull_request_review_status = pull_request_at_ver.calculated_review_status()
682 c.pull_request_review_status = pull_request_at_ver.calculated_review_status()
687
683
688 # GENERAL COMMENTS with versions #
684 # GENERAL COMMENTS with versions #
689 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
685 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
690 q = q.order_by(ChangesetComment.comment_id.asc())
686 q = q.order_by(ChangesetComment.comment_id.asc())
691 general_comments = q.order_by(ChangesetComment.pull_request_version_id.asc())
687 general_comments = q.order_by(ChangesetComment.pull_request_version_id.asc())
692
688
693 # pick comments we want to render at current version
689 # pick comments we want to render at current version
694 c.comment_versions = comments_model.aggregate_comments(
690 c.comment_versions = comments_model.aggregate_comments(
695 general_comments, versions, c.at_version_num)
691 general_comments, versions, c.at_version_num)
696 c.comments = c.comment_versions[c.at_version_num]['until']
692 c.comments = c.comment_versions[c.at_version_num]['until']
697
693
698 # INLINE COMMENTS with versions #
694 # INLINE COMMENTS with versions #
699 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
695 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
700 q = q.order_by(ChangesetComment.comment_id.asc())
696 q = q.order_by(ChangesetComment.comment_id.asc())
701 inline_comments = q.order_by(ChangesetComment.pull_request_version_id.asc())
697 inline_comments = q.order_by(ChangesetComment.pull_request_version_id.asc())
702 c.inline_versions = comments_model.aggregate_comments(
698 c.inline_versions = comments_model.aggregate_comments(
703 inline_comments, versions, c.at_version_num, inline=True)
699 inline_comments, versions, c.at_version_num, inline=True)
704
700
705 # inject latest version
701 # inject latest version
706 latest_ver = PullRequest.get_pr_display_object(
702 latest_ver = PullRequest.get_pr_display_object(
707 pull_request_latest, pull_request_latest)
703 pull_request_latest, pull_request_latest)
708
704
709 c.versions = versions + [latest_ver]
705 c.versions = versions + [latest_ver]
710
706
711 # if we use version, then do not show later comments
707 # if we use version, then do not show later comments
712 # than current version
708 # than current version
713 display_inline_comments = collections.defaultdict(
709 display_inline_comments = collections.defaultdict(
714 lambda: collections.defaultdict(list))
710 lambda: collections.defaultdict(list))
715 for co in inline_comments:
711 for co in inline_comments:
716 if c.at_version_num:
712 if c.at_version_num:
717 # pick comments that are at least UPTO given version, so we
713 # pick comments that are at least UPTO given version, so we
718 # don't render comments for higher version
714 # don't render comments for higher version
719 should_render = co.pull_request_version_id and \
715 should_render = co.pull_request_version_id and \
720 co.pull_request_version_id <= c.at_version_num
716 co.pull_request_version_id <= c.at_version_num
721 else:
717 else:
722 # showing all, for 'latest'
718 # showing all, for 'latest'
723 should_render = True
719 should_render = True
724
720
725 if should_render:
721 if should_render:
726 display_inline_comments[co.f_path][co.line_no].append(co)
722 display_inline_comments[co.f_path][co.line_no].append(co)
727
723
728 # load diff data into template context, if we use compare mode then
724 # load diff data into template context, if we use compare mode then
729 # diff is calculated based on changes between versions of PR
725 # diff is calculated based on changes between versions of PR
730
726
731 source_repo = pull_request_at_ver.source_repo
727 source_repo = pull_request_at_ver.source_repo
732 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
728 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
733
729
734 target_repo = pull_request_at_ver.target_repo
730 target_repo = pull_request_at_ver.target_repo
735 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
731 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
736
732
737 if compare:
733 if compare:
738 # in compare switch the diff base to latest commit from prev version
734 # in compare switch the diff base to latest commit from prev version
739 target_ref_id = prev_pull_request_display_obj.revisions[0]
735 target_ref_id = prev_pull_request_display_obj.revisions[0]
740
736
741 # despite opening commits for bookmarks/branches/tags, we always
737 # despite opening commits for bookmarks/branches/tags, we always
742 # convert this to rev to prevent changes after bookmark or branch change
738 # convert this to rev to prevent changes after bookmark or branch change
743 c.source_ref_type = 'rev'
739 c.source_ref_type = 'rev'
744 c.source_ref = source_ref_id
740 c.source_ref = source_ref_id
745
741
746 c.target_ref_type = 'rev'
742 c.target_ref_type = 'rev'
747 c.target_ref = target_ref_id
743 c.target_ref = target_ref_id
748
744
749 c.source_repo = source_repo
745 c.source_repo = source_repo
750 c.target_repo = target_repo
746 c.target_repo = target_repo
751
747
752 # diff_limit is the old behavior, will cut off the whole diff
748 # diff_limit is the old behavior, will cut off the whole diff
753 # if the limit is applied otherwise will just hide the
749 # if the limit is applied otherwise will just hide the
754 # big files from the front-end
750 # big files from the front-end
755 diff_limit = self.cut_off_limit_diff
751 diff_limit = self.cut_off_limit_diff
756 file_limit = self.cut_off_limit_file
752 file_limit = self.cut_off_limit_file
757
753
758 c.commit_ranges = []
754 c.commit_ranges = []
759 source_commit = EmptyCommit()
755 source_commit = EmptyCommit()
760 target_commit = EmptyCommit()
756 target_commit = EmptyCommit()
761 c.missing_requirements = False
757 c.missing_requirements = False
762
758
763 # try first shadow repo, fallback to regular repo
759 # try first shadow repo, fallback to regular repo
764 try:
760 try:
765 commits_source_repo = pull_request_latest.get_shadow_repo()
761 commits_source_repo = pull_request_latest.get_shadow_repo()
766 except Exception:
762 except Exception:
767 log.debug('Failed to get shadow repo', exc_info=True)
763 log.debug('Failed to get shadow repo', exc_info=True)
768 commits_source_repo = source_repo.scm_instance()
764 commits_source_repo = source_repo.scm_instance()
769
765
770 c.commits_source_repo = commits_source_repo
766 c.commits_source_repo = commits_source_repo
771 commit_cache = {}
767 commit_cache = {}
772 try:
768 try:
773 pre_load = ["author", "branch", "date", "message"]
769 pre_load = ["author", "branch", "date", "message"]
774 show_revs = pull_request_at_ver.revisions
770 show_revs = pull_request_at_ver.revisions
775 for rev in show_revs:
771 for rev in show_revs:
776 comm = commits_source_repo.get_commit(
772 comm = commits_source_repo.get_commit(
777 commit_id=rev, pre_load=pre_load)
773 commit_id=rev, pre_load=pre_load)
778 c.commit_ranges.append(comm)
774 c.commit_ranges.append(comm)
779 commit_cache[comm.raw_id] = comm
775 commit_cache[comm.raw_id] = comm
780
776
781 target_commit = commits_source_repo.get_commit(
777 target_commit = commits_source_repo.get_commit(
782 commit_id=safe_str(target_ref_id))
778 commit_id=safe_str(target_ref_id))
783 source_commit = commits_source_repo.get_commit(
779 source_commit = commits_source_repo.get_commit(
784 commit_id=safe_str(source_ref_id))
780 commit_id=safe_str(source_ref_id))
785 except CommitDoesNotExistError:
781 except CommitDoesNotExistError:
786 pass
782 pass
787 except RepositoryRequirementError:
783 except RepositoryRequirementError:
788 log.warning(
784 log.warning(
789 'Failed to get all required data from repo', exc_info=True)
785 'Failed to get all required data from repo', exc_info=True)
790 c.missing_requirements = True
786 c.missing_requirements = True
791
787
792 c.statuses = source_repo.statuses(
788 c.statuses = source_repo.statuses(
793 [x.raw_id for x in c.commit_ranges])
789 [x.raw_id for x in c.commit_ranges])
794
790
795 # auto collapse if we have more than limit
791 # auto collapse if we have more than limit
796 collapse_limit = diffs.DiffProcessor._collapse_commits_over
792 collapse_limit = diffs.DiffProcessor._collapse_commits_over
797 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
793 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
798 c.compare_mode = compare
794 c.compare_mode = compare
799
795
800 c.missing_commits = False
796 c.missing_commits = False
801 if (c.missing_requirements or isinstance(source_commit, EmptyCommit)
797 if (c.missing_requirements or isinstance(source_commit, EmptyCommit)
802 or source_commit == target_commit):
798 or source_commit == target_commit):
803
799
804 c.missing_commits = True
800 c.missing_commits = True
805 else:
801 else:
806 vcs_diff = PullRequestModel().get_diff(
802 vcs_diff = PullRequestModel().get_diff(
807 commits_source_repo, source_ref_id, target_ref_id)
803 commits_source_repo, source_ref_id, target_ref_id)
808
804
809 diff_processor = diffs.DiffProcessor(
805 diff_processor = diffs.DiffProcessor(
810 vcs_diff, format='newdiff', diff_limit=diff_limit,
806 vcs_diff, format='newdiff', diff_limit=diff_limit,
811 file_limit=file_limit, show_full_diff=c.fulldiff)
807 file_limit=file_limit, show_full_diff=c.fulldiff)
812
808
813 _parsed = diff_processor.prepare()
809 _parsed = diff_processor.prepare()
814 c.limited_diff = isinstance(_parsed, diffs.LimitedDiffContainer)
810 c.limited_diff = isinstance(_parsed, diffs.LimitedDiffContainer)
815
811
816 def _node_getter(commit):
812 def _node_getter(commit):
817 def get_node(fname):
813 def get_node(fname):
818 try:
814 try:
819 return commit.get_node(fname)
815 return commit.get_node(fname)
820 except NodeDoesNotExistError:
816 except NodeDoesNotExistError:
821 return None
817 return None
822
818
823 return get_node
819 return get_node
824
820
825 diffset = codeblocks.DiffSet(
821 diffset = codeblocks.DiffSet(
826 repo_name=c.repo_name,
822 repo_name=c.repo_name,
827 source_repo_name=c.source_repo.repo_name,
823 source_repo_name=c.source_repo.repo_name,
828 source_node_getter=_node_getter(target_commit),
824 source_node_getter=_node_getter(target_commit),
829 target_node_getter=_node_getter(source_commit),
825 target_node_getter=_node_getter(source_commit),
830 comments=display_inline_comments
826 comments=display_inline_comments
831 )
827 )
832 c.diffset = diffset.render_patchset(
828 c.diffset = diffset.render_patchset(
833 _parsed, target_commit.raw_id, source_commit.raw_id)
829 _parsed, target_commit.raw_id, source_commit.raw_id)
834
830
835 # calculate removed files that are bound to comments
831 # calculate removed files that are bound to comments
836 comment_deleted_files = [
832 comment_deleted_files = [
837 fname for fname in display_inline_comments
833 fname for fname in display_inline_comments
838 if fname not in c.diffset.file_stats]
834 if fname not in c.diffset.file_stats]
839
835
840 c.deleted_files_comments = collections.defaultdict(dict)
836 c.deleted_files_comments = collections.defaultdict(dict)
841 for fname, per_line_comments in display_inline_comments.items():
837 for fname, per_line_comments in display_inline_comments.items():
842 if fname in comment_deleted_files:
838 if fname in comment_deleted_files:
843 c.deleted_files_comments[fname]['stats'] = 0
839 c.deleted_files_comments[fname]['stats'] = 0
844 c.deleted_files_comments[fname]['comments'] = list()
840 c.deleted_files_comments[fname]['comments'] = list()
845 for lno, comments in per_line_comments.items():
841 for lno, comments in per_line_comments.items():
846 c.deleted_files_comments[fname]['comments'].extend(
842 c.deleted_files_comments[fname]['comments'].extend(
847 comments)
843 comments)
848
844
849 # this is a hack to properly display links, when creating PR, the
845 # this is a hack to properly display links, when creating PR, the
850 # compare view and others uses different notation, and
846 # compare view and others uses different notation, and
851 # compare_commits.mako renders links based on the target_repo.
847 # compare_commits.mako renders links based on the target_repo.
852 # We need to swap that here to generate it properly on the html side
848 # We need to swap that here to generate it properly on the html side
853 c.target_repo = c.source_repo
849 c.target_repo = c.source_repo
854
850
855 if c.allowed_to_update:
851 if c.allowed_to_update:
856 force_close = ('forced_closed', _('Close Pull Request'))
852 force_close = ('forced_closed', _('Close Pull Request'))
857 statuses = ChangesetStatus.STATUSES + [force_close]
853 statuses = ChangesetStatus.STATUSES + [force_close]
858 else:
854 else:
859 statuses = ChangesetStatus.STATUSES
855 statuses = ChangesetStatus.STATUSES
860 c.commit_statuses = statuses
856 c.commit_statuses = statuses
861
857
862 c.show_version_changes = not pr_closed
858 c.show_version_changes = not pr_closed
863 if c.show_version_changes:
859 if c.show_version_changes:
864 cur_obj = pull_request_at_ver
860 cur_obj = pull_request_at_ver
865 prev_obj = prev_pull_request_at_ver
861 prev_obj = prev_pull_request_at_ver
866
862
867 old_commit_ids = prev_obj.revisions
863 old_commit_ids = prev_obj.revisions
868 new_commit_ids = cur_obj.revisions
864 new_commit_ids = cur_obj.revisions
869 commit_changes = PullRequestModel()._calculate_commit_id_changes(
865 commit_changes = PullRequestModel()._calculate_commit_id_changes(
870 old_commit_ids, new_commit_ids)
866 old_commit_ids, new_commit_ids)
871 c.commit_changes_summary = commit_changes
867 c.commit_changes_summary = commit_changes
872
868
873 # calculate the diff for commits between versions
869 # calculate the diff for commits between versions
874 c.commit_changes = []
870 c.commit_changes = []
875 mark = lambda cs, fw: list(
871 mark = lambda cs, fw: list(
876 h.itertools.izip_longest([], cs, fillvalue=fw))
872 h.itertools.izip_longest([], cs, fillvalue=fw))
877 for c_type, raw_id in mark(commit_changes.added, 'a') \
873 for c_type, raw_id in mark(commit_changes.added, 'a') \
878 + mark(commit_changes.removed, 'r') \
874 + mark(commit_changes.removed, 'r') \
879 + mark(commit_changes.common, 'c'):
875 + mark(commit_changes.common, 'c'):
880
876
881 if raw_id in commit_cache:
877 if raw_id in commit_cache:
882 commit = commit_cache[raw_id]
878 commit = commit_cache[raw_id]
883 else:
879 else:
884 try:
880 try:
885 commit = commits_source_repo.get_commit(raw_id)
881 commit = commits_source_repo.get_commit(raw_id)
886 except CommitDoesNotExistError:
882 except CommitDoesNotExistError:
887 # in case we fail extracting still use "dummy" commit
883 # in case we fail extracting still use "dummy" commit
888 # for display in commit diff
884 # for display in commit diff
889 commit = h.AttributeDict(
885 commit = h.AttributeDict(
890 {'raw_id': raw_id,
886 {'raw_id': raw_id,
891 'message': 'EMPTY or MISSING COMMIT'})
887 'message': 'EMPTY or MISSING COMMIT'})
892 c.commit_changes.append([c_type, commit])
888 c.commit_changes.append([c_type, commit])
893
889
894 # current user review statuses for each version
890 # current user review statuses for each version
895 c.review_versions = {}
891 c.review_versions = {}
896 if c.rhodecode_user.user_id in allowed_reviewers:
892 if c.rhodecode_user.user_id in allowed_reviewers:
897 for co in general_comments:
893 for co in general_comments:
898 if co.author.user_id == c.rhodecode_user.user_id:
894 if co.author.user_id == c.rhodecode_user.user_id:
899 # each comment has a status change
895 # each comment has a status change
900 status = co.status_change
896 status = co.status_change
901 if status:
897 if status:
902 _ver_pr = status[0].comment.pull_request_version_id
898 _ver_pr = status[0].comment.pull_request_version_id
903 c.review_versions[_ver_pr] = status[0]
899 c.review_versions[_ver_pr] = status[0]
904
900
905 return render('/pullrequests/pullrequest_show.mako')
901 return render('/pullrequests/pullrequest_show.mako')
906
902
907 @LoginRequired()
903 @LoginRequired()
908 @NotAnonymous()
904 @NotAnonymous()
909 @HasRepoPermissionAnyDecorator(
905 @HasRepoPermissionAnyDecorator(
910 'repository.read', 'repository.write', 'repository.admin')
906 'repository.read', 'repository.write', 'repository.admin')
911 @auth.CSRFRequired()
907 @auth.CSRFRequired()
912 @jsonify
908 @jsonify
913 def comment(self, repo_name, pull_request_id):
909 def comment(self, repo_name, pull_request_id):
914 pull_request_id = safe_int(pull_request_id)
910 pull_request_id = safe_int(pull_request_id)
915 pull_request = PullRequest.get_or_404(pull_request_id)
911 pull_request = PullRequest.get_or_404(pull_request_id)
916 if pull_request.is_closed():
912 if pull_request.is_closed():
917 raise HTTPForbidden()
913 raise HTTPForbidden()
918
914
919 # TODO: johbo: Re-think this bit, "approved_closed" does not exist
915 # TODO: johbo: Re-think this bit, "approved_closed" does not exist
920 # as a changeset status, still we want to send it in one value.
916 # as a changeset status, still we want to send it in one value.
921 status = request.POST.get('changeset_status', None)
917 status = request.POST.get('changeset_status', None)
922 text = request.POST.get('text')
918 text = request.POST.get('text')
923 comment_type = request.POST.get('comment_type')
919 comment_type = request.POST.get('comment_type')
924 resolves_comment_id = request.POST.get('resolves_comment_id', None)
920 resolves_comment_id = request.POST.get('resolves_comment_id', None)
925
921
926 if status and '_closed' in status:
922 if status and '_closed' in status:
927 close_pr = True
923 close_pr = True
928 status = status.replace('_closed', '')
924 status = status.replace('_closed', '')
929 else:
925 else:
930 close_pr = False
926 close_pr = False
931
927
932 forced = (status == 'forced')
928 forced = (status == 'forced')
933 if forced:
929 if forced:
934 status = 'rejected'
930 status = 'rejected'
935
931
936 allowed_to_change_status = PullRequestModel().check_user_change_status(
932 allowed_to_change_status = PullRequestModel().check_user_change_status(
937 pull_request, c.rhodecode_user)
933 pull_request, c.rhodecode_user)
938
934
939 if status and allowed_to_change_status:
935 if status and allowed_to_change_status:
940 message = (_('Status change %(transition_icon)s %(status)s')
936 message = (_('Status change %(transition_icon)s %(status)s')
941 % {'transition_icon': '>',
937 % {'transition_icon': '>',
942 'status': ChangesetStatus.get_status_lbl(status)})
938 'status': ChangesetStatus.get_status_lbl(status)})
943 if close_pr:
939 if close_pr:
944 message = _('Closing with') + ' ' + message
940 message = _('Closing with') + ' ' + message
945 text = text or message
941 text = text or message
946 comm = CommentsModel().create(
942 comm = CommentsModel().create(
947 text=text,
943 text=text,
948 repo=c.rhodecode_db_repo.repo_id,
944 repo=c.rhodecode_db_repo.repo_id,
949 user=c.rhodecode_user.user_id,
945 user=c.rhodecode_user.user_id,
950 pull_request=pull_request_id,
946 pull_request=pull_request_id,
951 f_path=request.POST.get('f_path'),
947 f_path=request.POST.get('f_path'),
952 line_no=request.POST.get('line'),
948 line_no=request.POST.get('line'),
953 status_change=(ChangesetStatus.get_status_lbl(status)
949 status_change=(ChangesetStatus.get_status_lbl(status)
954 if status and allowed_to_change_status else None),
950 if status and allowed_to_change_status else None),
955 status_change_type=(status
951 status_change_type=(status
956 if status and allowed_to_change_status else None),
952 if status and allowed_to_change_status else None),
957 closing_pr=close_pr,
953 closing_pr=close_pr,
958 comment_type=comment_type,
954 comment_type=comment_type,
959 resolves_comment_id=resolves_comment_id
955 resolves_comment_id=resolves_comment_id
960 )
956 )
961
957
962 if allowed_to_change_status:
958 if allowed_to_change_status:
963 old_calculated_status = pull_request.calculated_review_status()
959 old_calculated_status = pull_request.calculated_review_status()
964 # get status if set !
960 # get status if set !
965 if status:
961 if status:
966 ChangesetStatusModel().set_status(
962 ChangesetStatusModel().set_status(
967 c.rhodecode_db_repo.repo_id,
963 c.rhodecode_db_repo.repo_id,
968 status,
964 status,
969 c.rhodecode_user.user_id,
965 c.rhodecode_user.user_id,
970 comm,
966 comm,
971 pull_request=pull_request_id
967 pull_request=pull_request_id
972 )
968 )
973
969
974 Session().flush()
970 Session().flush()
975 events.trigger(events.PullRequestCommentEvent(pull_request, comm))
971 events.trigger(events.PullRequestCommentEvent(pull_request, comm))
976 # we now calculate the status of pull request, and based on that
972 # we now calculate the status of pull request, and based on that
977 # calculation we set the commits status
973 # calculation we set the commits status
978 calculated_status = pull_request.calculated_review_status()
974 calculated_status = pull_request.calculated_review_status()
979 if old_calculated_status != calculated_status:
975 if old_calculated_status != calculated_status:
980 PullRequestModel()._trigger_pull_request_hook(
976 PullRequestModel()._trigger_pull_request_hook(
981 pull_request, c.rhodecode_user, 'review_status_change')
977 pull_request, c.rhodecode_user, 'review_status_change')
982
978
983 calculated_status_lbl = ChangesetStatus.get_status_lbl(
979 calculated_status_lbl = ChangesetStatus.get_status_lbl(
984 calculated_status)
980 calculated_status)
985
981
986 if close_pr:
982 if close_pr:
987 status_completed = (
983 status_completed = (
988 calculated_status in [ChangesetStatus.STATUS_APPROVED,
984 calculated_status in [ChangesetStatus.STATUS_APPROVED,
989 ChangesetStatus.STATUS_REJECTED])
985 ChangesetStatus.STATUS_REJECTED])
990 if forced or status_completed:
986 if forced or status_completed:
991 PullRequestModel().close_pull_request(
987 PullRequestModel().close_pull_request(
992 pull_request_id, c.rhodecode_user)
988 pull_request_id, c.rhodecode_user)
993 else:
989 else:
994 h.flash(_('Closing pull request on other statuses than '
990 h.flash(_('Closing pull request on other statuses than '
995 'rejected or approved is forbidden. '
991 'rejected or approved is forbidden. '
996 'Calculated status from all reviewers '
992 'Calculated status from all reviewers '
997 'is currently: %s') % calculated_status_lbl,
993 'is currently: %s') % calculated_status_lbl,
998 category='warning')
994 category='warning')
999
995
1000 Session().commit()
996 Session().commit()
1001
997
1002 if not request.is_xhr:
998 if not request.is_xhr:
1003 return redirect(h.url('pullrequest_show', repo_name=repo_name,
999 return redirect(h.url('pullrequest_show', repo_name=repo_name,
1004 pull_request_id=pull_request_id))
1000 pull_request_id=pull_request_id))
1005
1001
1006 data = {
1002 data = {
1007 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
1003 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
1008 }
1004 }
1009 if comm:
1005 if comm:
1010 c.co = comm
1006 c.co = comm
1011 c.inline_comment = True if comm.line_no else False
1007 c.inline_comment = True if comm.line_no else False
1012 data.update(comm.get_dict())
1008 data.update(comm.get_dict())
1013 data.update({'rendered_text':
1009 data.update({'rendered_text':
1014 render('changeset/changeset_comment_block.mako')})
1010 render('changeset/changeset_comment_block.mako')})
1015
1011
1016 return data
1012 return data
1017
1013
1018 @LoginRequired()
1014 @LoginRequired()
1019 @NotAnonymous()
1015 @NotAnonymous()
1020 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
1016 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
1021 'repository.admin')
1017 'repository.admin')
1022 @auth.CSRFRequired()
1018 @auth.CSRFRequired()
1023 @jsonify
1019 @jsonify
1024 def delete_comment(self, repo_name, comment_id):
1020 def delete_comment(self, repo_name, comment_id):
1025 return self._delete_comment(comment_id)
1021 return self._delete_comment(comment_id)
1026
1022
1027 def _delete_comment(self, comment_id):
1023 def _delete_comment(self, comment_id):
1028 comment_id = safe_int(comment_id)
1024 comment_id = safe_int(comment_id)
1029 co = ChangesetComment.get_or_404(comment_id)
1025 co = ChangesetComment.get_or_404(comment_id)
1030 if co.pull_request.is_closed():
1026 if co.pull_request.is_closed():
1031 # don't allow deleting comments on closed pull request
1027 # don't allow deleting comments on closed pull request
1032 raise HTTPForbidden()
1028 raise HTTPForbidden()
1033
1029
1034 is_owner = co.author.user_id == c.rhodecode_user.user_id
1030 is_owner = co.author.user_id == c.rhodecode_user.user_id
1035 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name)
1031 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name)
1036 if h.HasPermissionAny('hg.admin')() or is_repo_admin or is_owner:
1032 if h.HasPermissionAny('hg.admin')() or is_repo_admin or is_owner:
1037 old_calculated_status = co.pull_request.calculated_review_status()
1033 old_calculated_status = co.pull_request.calculated_review_status()
1038 CommentsModel().delete(comment=co)
1034 CommentsModel().delete(comment=co)
1039 Session().commit()
1035 Session().commit()
1040 calculated_status = co.pull_request.calculated_review_status()
1036 calculated_status = co.pull_request.calculated_review_status()
1041 if old_calculated_status != calculated_status:
1037 if old_calculated_status != calculated_status:
1042 PullRequestModel()._trigger_pull_request_hook(
1038 PullRequestModel()._trigger_pull_request_hook(
1043 co.pull_request, c.rhodecode_user, 'review_status_change')
1039 co.pull_request, c.rhodecode_user, 'review_status_change')
1044 return True
1040 return True
1045 else:
1041 else:
1046 raise HTTPForbidden()
1042 raise HTTPForbidden()
General Comments 0
You need to be logged in to leave comments. Login now