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