##// END OF EJS Templates
events: remove accidental raise
dan -
r380:364360c9 default
parent child Browse files
Show More
@@ -1,847 +1,846 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
3 # Copyright (C) 2012-2016 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
24
25 import formencode
25 import formencode
26 import logging
26 import logging
27
27
28 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPBadRequest
28 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPBadRequest
29 from pylons import request, tmpl_context as c, url
29 from pylons import request, tmpl_context as c, url
30 from pylons.controllers.util import redirect
30 from pylons.controllers.util import redirect
31 from pylons.i18n.translation import _
31 from pylons.i18n.translation import _
32 from sqlalchemy.sql import func
32 from sqlalchemy.sql import func
33 from sqlalchemy.sql.expression import or_
33 from sqlalchemy.sql.expression import or_
34
34
35 from rhodecode.lib import auth, diffs, helpers as h
35 from rhodecode.lib import auth, diffs, helpers as h
36 from rhodecode.lib.ext_json import json
36 from rhodecode.lib.ext_json import json
37 from rhodecode.lib.base import (
37 from rhodecode.lib.base import (
38 BaseRepoController, render, vcs_operation_context)
38 BaseRepoController, render, vcs_operation_context)
39 from rhodecode.lib.auth import (
39 from rhodecode.lib.auth import (
40 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous,
40 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous,
41 HasAcceptedRepoType, XHRRequired)
41 HasAcceptedRepoType, XHRRequired)
42 from rhodecode.lib.utils import jsonify
42 from rhodecode.lib.utils import jsonify
43 from rhodecode.lib.utils2 import safe_int, safe_str, str2bool, safe_unicode
43 from rhodecode.lib.utils2 import safe_int, safe_str, str2bool, safe_unicode
44 from rhodecode.lib.vcs.backends.base import EmptyCommit
44 from rhodecode.lib.vcs.backends.base import EmptyCommit
45 from rhodecode.lib.vcs.exceptions import (
45 from rhodecode.lib.vcs.exceptions import (
46 EmptyRepositoryError, CommitDoesNotExistError, RepositoryRequirementError)
46 EmptyRepositoryError, CommitDoesNotExistError, RepositoryRequirementError)
47 from rhodecode.lib.diffs import LimitedDiffContainer
47 from rhodecode.lib.diffs import LimitedDiffContainer
48 from rhodecode.model.changeset_status import ChangesetStatusModel
48 from rhodecode.model.changeset_status import ChangesetStatusModel
49 from rhodecode.model.comment import ChangesetCommentsModel
49 from rhodecode.model.comment import ChangesetCommentsModel
50 from rhodecode.model.db import PullRequest, ChangesetStatus, ChangesetComment, \
50 from rhodecode.model.db import PullRequest, ChangesetStatus, ChangesetComment, \
51 Repository
51 Repository
52 from rhodecode.model.forms import PullRequestForm
52 from rhodecode.model.forms import PullRequestForm
53 from rhodecode.model.meta import Session
53 from rhodecode.model.meta import Session
54 from rhodecode.model.pull_request import PullRequestModel
54 from rhodecode.model.pull_request import PullRequestModel
55
55
56 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
57
57
58
58
59 class PullrequestsController(BaseRepoController):
59 class PullrequestsController(BaseRepoController):
60 def __before__(self):
60 def __before__(self):
61 super(PullrequestsController, self).__before__()
61 super(PullrequestsController, self).__before__()
62
62
63 def _load_compare_data(self, pull_request, enable_comments=True):
63 def _load_compare_data(self, pull_request, enable_comments=True):
64 """
64 """
65 Load context data needed for generating compare diff
65 Load context data needed for generating compare diff
66
66
67 :param pull_request: object related to the request
67 :param pull_request: object related to the request
68 :param enable_comments: flag to determine if comments are included
68 :param enable_comments: flag to determine if comments are included
69 """
69 """
70 source_repo = pull_request.source_repo
70 source_repo = pull_request.source_repo
71 source_ref_id = pull_request.source_ref_parts.commit_id
71 source_ref_id = pull_request.source_ref_parts.commit_id
72
72
73 target_repo = pull_request.target_repo
73 target_repo = pull_request.target_repo
74 target_ref_id = pull_request.target_ref_parts.commit_id
74 target_ref_id = pull_request.target_ref_parts.commit_id
75
75
76 # despite opening commits for bookmarks/branches/tags, we always
76 # despite opening commits for bookmarks/branches/tags, we always
77 # convert this to rev to prevent changes after bookmark or branch change
77 # convert this to rev to prevent changes after bookmark or branch change
78 c.source_ref_type = 'rev'
78 c.source_ref_type = 'rev'
79 c.source_ref = source_ref_id
79 c.source_ref = source_ref_id
80
80
81 c.target_ref_type = 'rev'
81 c.target_ref_type = 'rev'
82 c.target_ref = target_ref_id
82 c.target_ref = target_ref_id
83
83
84 c.source_repo = source_repo
84 c.source_repo = source_repo
85 c.target_repo = target_repo
85 c.target_repo = target_repo
86
86
87 c.fulldiff = bool(request.GET.get('fulldiff'))
87 c.fulldiff = bool(request.GET.get('fulldiff'))
88
88
89 # diff_limit is the old behavior, will cut off the whole diff
89 # diff_limit is the old behavior, will cut off the whole diff
90 # if the limit is applied otherwise will just hide the
90 # if the limit is applied otherwise will just hide the
91 # big files from the front-end
91 # big files from the front-end
92 diff_limit = self.cut_off_limit_diff
92 diff_limit = self.cut_off_limit_diff
93 file_limit = self.cut_off_limit_file
93 file_limit = self.cut_off_limit_file
94
94
95 pre_load = ["author", "branch", "date", "message"]
95 pre_load = ["author", "branch", "date", "message"]
96
96
97 c.commit_ranges = []
97 c.commit_ranges = []
98 source_commit = EmptyCommit()
98 source_commit = EmptyCommit()
99 target_commit = EmptyCommit()
99 target_commit = EmptyCommit()
100 c.missing_requirements = False
100 c.missing_requirements = False
101 try:
101 try:
102 c.commit_ranges = [
102 c.commit_ranges = [
103 source_repo.get_commit(commit_id=rev, pre_load=pre_load)
103 source_repo.get_commit(commit_id=rev, pre_load=pre_load)
104 for rev in pull_request.revisions]
104 for rev in pull_request.revisions]
105
105
106 c.statuses = source_repo.statuses(
106 c.statuses = source_repo.statuses(
107 [x.raw_id for x in c.commit_ranges])
107 [x.raw_id for x in c.commit_ranges])
108
108
109 target_commit = source_repo.get_commit(
109 target_commit = source_repo.get_commit(
110 commit_id=safe_str(target_ref_id))
110 commit_id=safe_str(target_ref_id))
111 source_commit = source_repo.get_commit(
111 source_commit = source_repo.get_commit(
112 commit_id=safe_str(source_ref_id))
112 commit_id=safe_str(source_ref_id))
113 except RepositoryRequirementError:
113 except RepositoryRequirementError:
114 c.missing_requirements = True
114 c.missing_requirements = True
115
115
116 c.missing_commits = False
116 c.missing_commits = False
117 if (c.missing_requirements or
117 if (c.missing_requirements or
118 isinstance(source_commit, EmptyCommit) or
118 isinstance(source_commit, EmptyCommit) or
119 source_commit == target_commit):
119 source_commit == target_commit):
120 _parsed = []
120 _parsed = []
121 c.missing_commits = True
121 c.missing_commits = True
122 else:
122 else:
123 vcs_diff = PullRequestModel().get_diff(pull_request)
123 vcs_diff = PullRequestModel().get_diff(pull_request)
124 diff_processor = diffs.DiffProcessor(
124 diff_processor = diffs.DiffProcessor(
125 vcs_diff, format='gitdiff', diff_limit=diff_limit,
125 vcs_diff, format='gitdiff', diff_limit=diff_limit,
126 file_limit=file_limit, show_full_diff=c.fulldiff)
126 file_limit=file_limit, show_full_diff=c.fulldiff)
127 _parsed = diff_processor.prepare()
127 _parsed = diff_processor.prepare()
128
128
129 c.limited_diff = isinstance(_parsed, LimitedDiffContainer)
129 c.limited_diff = isinstance(_parsed, LimitedDiffContainer)
130
130
131 c.files = []
131 c.files = []
132 c.changes = {}
132 c.changes = {}
133 c.lines_added = 0
133 c.lines_added = 0
134 c.lines_deleted = 0
134 c.lines_deleted = 0
135 c.included_files = []
135 c.included_files = []
136 c.deleted_files = []
136 c.deleted_files = []
137
137
138 for f in _parsed:
138 for f in _parsed:
139 st = f['stats']
139 st = f['stats']
140 c.lines_added += st['added']
140 c.lines_added += st['added']
141 c.lines_deleted += st['deleted']
141 c.lines_deleted += st['deleted']
142
142
143 fid = h.FID('', f['filename'])
143 fid = h.FID('', f['filename'])
144 c.files.append([fid, f['operation'], f['filename'], f['stats']])
144 c.files.append([fid, f['operation'], f['filename'], f['stats']])
145 c.included_files.append(f['filename'])
145 c.included_files.append(f['filename'])
146 html_diff = diff_processor.as_html(enable_comments=enable_comments,
146 html_diff = diff_processor.as_html(enable_comments=enable_comments,
147 parsed_lines=[f])
147 parsed_lines=[f])
148 c.changes[fid] = [f['operation'], f['filename'], html_diff, f]
148 c.changes[fid] = [f['operation'], f['filename'], html_diff, f]
149
149
150 def _extract_ordering(self, request):
150 def _extract_ordering(self, request):
151 column_index = safe_int(request.GET.get('order[0][column]'))
151 column_index = safe_int(request.GET.get('order[0][column]'))
152 order_dir = request.GET.get('order[0][dir]', 'desc')
152 order_dir = request.GET.get('order[0][dir]', 'desc')
153 order_by = request.GET.get(
153 order_by = request.GET.get(
154 'columns[%s][data][sort]' % column_index, 'name_raw')
154 'columns[%s][data][sort]' % column_index, 'name_raw')
155 return order_by, order_dir
155 return order_by, order_dir
156
156
157 @LoginRequired()
157 @LoginRequired()
158 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
158 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
159 'repository.admin')
159 'repository.admin')
160 @HasAcceptedRepoType('git', 'hg')
160 @HasAcceptedRepoType('git', 'hg')
161 def show_all(self, repo_name):
161 def show_all(self, repo_name):
162 # filter types
162 # filter types
163 c.active = 'open'
163 c.active = 'open'
164 c.source = str2bool(request.GET.get('source'))
164 c.source = str2bool(request.GET.get('source'))
165 c.closed = str2bool(request.GET.get('closed'))
165 c.closed = str2bool(request.GET.get('closed'))
166 c.my = str2bool(request.GET.get('my'))
166 c.my = str2bool(request.GET.get('my'))
167 c.awaiting_review = str2bool(request.GET.get('awaiting_review'))
167 c.awaiting_review = str2bool(request.GET.get('awaiting_review'))
168 c.awaiting_my_review = str2bool(request.GET.get('awaiting_my_review'))
168 c.awaiting_my_review = str2bool(request.GET.get('awaiting_my_review'))
169 c.repo_name = repo_name
169 c.repo_name = repo_name
170
170
171 opened_by = None
171 opened_by = None
172 if c.my:
172 if c.my:
173 c.active = 'my'
173 c.active = 'my'
174 opened_by = [c.rhodecode_user.user_id]
174 opened_by = [c.rhodecode_user.user_id]
175
175
176 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
176 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
177 if c.closed:
177 if c.closed:
178 c.active = 'closed'
178 c.active = 'closed'
179 statuses = [PullRequest.STATUS_CLOSED]
179 statuses = [PullRequest.STATUS_CLOSED]
180
180
181 if c.awaiting_review and not c.source:
181 if c.awaiting_review and not c.source:
182 c.active = 'awaiting'
182 c.active = 'awaiting'
183 if c.source and not c.awaiting_review:
183 if c.source and not c.awaiting_review:
184 c.active = 'source'
184 c.active = 'source'
185 if c.awaiting_my_review:
185 if c.awaiting_my_review:
186 c.active = 'awaiting_my'
186 c.active = 'awaiting_my'
187
187
188 data = self._get_pull_requests_list(
188 data = self._get_pull_requests_list(
189 repo_name=repo_name, opened_by=opened_by, statuses=statuses)
189 repo_name=repo_name, opened_by=opened_by, statuses=statuses)
190 if not request.is_xhr:
190 if not request.is_xhr:
191 c.data = json.dumps(data['data'])
191 c.data = json.dumps(data['data'])
192 c.records_total = data['recordsTotal']
192 c.records_total = data['recordsTotal']
193 return render('/pullrequests/pullrequests.html')
193 return render('/pullrequests/pullrequests.html')
194 else:
194 else:
195 return json.dumps(data)
195 return json.dumps(data)
196
196
197 def _get_pull_requests_list(self, repo_name, opened_by, statuses):
197 def _get_pull_requests_list(self, repo_name, opened_by, statuses):
198 # pagination
198 # pagination
199 start = safe_int(request.GET.get('start'), 0)
199 start = safe_int(request.GET.get('start'), 0)
200 length = safe_int(request.GET.get('length'), c.visual.dashboard_items)
200 length = safe_int(request.GET.get('length'), c.visual.dashboard_items)
201 order_by, order_dir = self._extract_ordering(request)
201 order_by, order_dir = self._extract_ordering(request)
202
202
203 if c.awaiting_review:
203 if c.awaiting_review:
204 pull_requests = PullRequestModel().get_awaiting_review(
204 pull_requests = PullRequestModel().get_awaiting_review(
205 repo_name, source=c.source, opened_by=opened_by,
205 repo_name, source=c.source, opened_by=opened_by,
206 statuses=statuses, offset=start, length=length,
206 statuses=statuses, offset=start, length=length,
207 order_by=order_by, order_dir=order_dir)
207 order_by=order_by, order_dir=order_dir)
208 pull_requests_total_count = PullRequestModel(
208 pull_requests_total_count = PullRequestModel(
209 ).count_awaiting_review(
209 ).count_awaiting_review(
210 repo_name, source=c.source, statuses=statuses,
210 repo_name, source=c.source, statuses=statuses,
211 opened_by=opened_by)
211 opened_by=opened_by)
212 elif c.awaiting_my_review:
212 elif c.awaiting_my_review:
213 pull_requests = PullRequestModel().get_awaiting_my_review(
213 pull_requests = PullRequestModel().get_awaiting_my_review(
214 repo_name, source=c.source, opened_by=opened_by,
214 repo_name, source=c.source, opened_by=opened_by,
215 user_id=c.rhodecode_user.user_id, statuses=statuses,
215 user_id=c.rhodecode_user.user_id, statuses=statuses,
216 offset=start, length=length, order_by=order_by,
216 offset=start, length=length, order_by=order_by,
217 order_dir=order_dir)
217 order_dir=order_dir)
218 pull_requests_total_count = PullRequestModel(
218 pull_requests_total_count = PullRequestModel(
219 ).count_awaiting_my_review(
219 ).count_awaiting_my_review(
220 repo_name, source=c.source, user_id=c.rhodecode_user.user_id,
220 repo_name, source=c.source, user_id=c.rhodecode_user.user_id,
221 statuses=statuses, opened_by=opened_by)
221 statuses=statuses, opened_by=opened_by)
222 else:
222 else:
223 pull_requests = PullRequestModel().get_all(
223 pull_requests = PullRequestModel().get_all(
224 repo_name, source=c.source, opened_by=opened_by,
224 repo_name, source=c.source, opened_by=opened_by,
225 statuses=statuses, offset=start, length=length,
225 statuses=statuses, offset=start, length=length,
226 order_by=order_by, order_dir=order_dir)
226 order_by=order_by, order_dir=order_dir)
227 pull_requests_total_count = PullRequestModel().count_all(
227 pull_requests_total_count = PullRequestModel().count_all(
228 repo_name, source=c.source, statuses=statuses,
228 repo_name, source=c.source, statuses=statuses,
229 opened_by=opened_by)
229 opened_by=opened_by)
230
230
231 from rhodecode.lib.utils import PartialRenderer
231 from rhodecode.lib.utils import PartialRenderer
232 _render = PartialRenderer('data_table/_dt_elements.html')
232 _render = PartialRenderer('data_table/_dt_elements.html')
233 data = []
233 data = []
234 for pr in pull_requests:
234 for pr in pull_requests:
235 comments = ChangesetCommentsModel().get_all_comments(
235 comments = ChangesetCommentsModel().get_all_comments(
236 c.rhodecode_db_repo.repo_id, pull_request=pr)
236 c.rhodecode_db_repo.repo_id, pull_request=pr)
237
237
238 data.append({
238 data.append({
239 'name': _render('pullrequest_name',
239 'name': _render('pullrequest_name',
240 pr.pull_request_id, pr.target_repo.repo_name),
240 pr.pull_request_id, pr.target_repo.repo_name),
241 'name_raw': pr.pull_request_id,
241 'name_raw': pr.pull_request_id,
242 'status': _render('pullrequest_status',
242 'status': _render('pullrequest_status',
243 pr.calculated_review_status()),
243 pr.calculated_review_status()),
244 'title': _render(
244 'title': _render(
245 'pullrequest_title', pr.title, pr.description),
245 'pullrequest_title', pr.title, pr.description),
246 'description': h.escape(pr.description),
246 'description': h.escape(pr.description),
247 'updated_on': _render('pullrequest_updated_on',
247 'updated_on': _render('pullrequest_updated_on',
248 h.datetime_to_time(pr.updated_on)),
248 h.datetime_to_time(pr.updated_on)),
249 'updated_on_raw': h.datetime_to_time(pr.updated_on),
249 'updated_on_raw': h.datetime_to_time(pr.updated_on),
250 'created_on': _render('pullrequest_updated_on',
250 'created_on': _render('pullrequest_updated_on',
251 h.datetime_to_time(pr.created_on)),
251 h.datetime_to_time(pr.created_on)),
252 'created_on_raw': h.datetime_to_time(pr.created_on),
252 'created_on_raw': h.datetime_to_time(pr.created_on),
253 'author': _render('pullrequest_author',
253 'author': _render('pullrequest_author',
254 pr.author.full_contact, ),
254 pr.author.full_contact, ),
255 'author_raw': pr.author.full_name,
255 'author_raw': pr.author.full_name,
256 'comments': _render('pullrequest_comments', len(comments)),
256 'comments': _render('pullrequest_comments', len(comments)),
257 'comments_raw': len(comments),
257 'comments_raw': len(comments),
258 'closed': pr.is_closed(),
258 'closed': pr.is_closed(),
259 })
259 })
260 # json used to render the grid
260 # json used to render the grid
261 data = ({
261 data = ({
262 'data': data,
262 'data': data,
263 'recordsTotal': pull_requests_total_count,
263 'recordsTotal': pull_requests_total_count,
264 'recordsFiltered': pull_requests_total_count,
264 'recordsFiltered': pull_requests_total_count,
265 })
265 })
266 return data
266 return data
267
267
268 @LoginRequired()
268 @LoginRequired()
269 @NotAnonymous()
269 @NotAnonymous()
270 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
270 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
271 'repository.admin')
271 'repository.admin')
272 @HasAcceptedRepoType('git', 'hg')
272 @HasAcceptedRepoType('git', 'hg')
273 def index(self):
273 def index(self):
274 source_repo = c.rhodecode_db_repo
274 source_repo = c.rhodecode_db_repo
275
275
276 try:
276 try:
277 source_repo.scm_instance().get_commit()
277 source_repo.scm_instance().get_commit()
278 except EmptyRepositoryError:
278 except EmptyRepositoryError:
279 h.flash(h.literal(_('There are no commits yet')),
279 h.flash(h.literal(_('There are no commits yet')),
280 category='warning')
280 category='warning')
281 redirect(url('summary_home', repo_name=source_repo.repo_name))
281 redirect(url('summary_home', repo_name=source_repo.repo_name))
282
282
283 commit_id = request.GET.get('commit')
283 commit_id = request.GET.get('commit')
284 branch_ref = request.GET.get('branch')
284 branch_ref = request.GET.get('branch')
285 bookmark_ref = request.GET.get('bookmark')
285 bookmark_ref = request.GET.get('bookmark')
286
286
287 try:
287 try:
288 source_repo_data = PullRequestModel().generate_repo_data(
288 source_repo_data = PullRequestModel().generate_repo_data(
289 source_repo, commit_id=commit_id,
289 source_repo, commit_id=commit_id,
290 branch=branch_ref, bookmark=bookmark_ref)
290 branch=branch_ref, bookmark=bookmark_ref)
291 except CommitDoesNotExistError as e:
291 except CommitDoesNotExistError as e:
292 log.exception(e)
292 log.exception(e)
293 h.flash(_('Commit does not exist'), 'error')
293 h.flash(_('Commit does not exist'), 'error')
294 redirect(url('pullrequest_home', repo_name=source_repo.repo_name))
294 redirect(url('pullrequest_home', repo_name=source_repo.repo_name))
295
295
296 default_target_repo = source_repo
296 default_target_repo = source_repo
297 if (source_repo.parent and
297 if (source_repo.parent and
298 not source_repo.parent.scm_instance().is_empty()):
298 not source_repo.parent.scm_instance().is_empty()):
299 # change default if we have a parent repo
299 # change default if we have a parent repo
300 default_target_repo = source_repo.parent
300 default_target_repo = source_repo.parent
301
301
302 target_repo_data = PullRequestModel().generate_repo_data(
302 target_repo_data = PullRequestModel().generate_repo_data(
303 default_target_repo)
303 default_target_repo)
304
304
305 selected_source_ref = source_repo_data['refs']['selected_ref']
305 selected_source_ref = source_repo_data['refs']['selected_ref']
306
306
307 title_source_ref = selected_source_ref.split(':', 2)[1]
307 title_source_ref = selected_source_ref.split(':', 2)[1]
308 c.default_title = PullRequestModel().generate_pullrequest_title(
308 c.default_title = PullRequestModel().generate_pullrequest_title(
309 source=source_repo.repo_name,
309 source=source_repo.repo_name,
310 source_ref=title_source_ref,
310 source_ref=title_source_ref,
311 target=default_target_repo.repo_name
311 target=default_target_repo.repo_name
312 )
312 )
313
313
314 c.default_repo_data = {
314 c.default_repo_data = {
315 'source_repo_name': source_repo.repo_name,
315 'source_repo_name': source_repo.repo_name,
316 'source_refs_json': json.dumps(source_repo_data),
316 'source_refs_json': json.dumps(source_repo_data),
317 'target_repo_name': default_target_repo.repo_name,
317 'target_repo_name': default_target_repo.repo_name,
318 'target_refs_json': json.dumps(target_repo_data),
318 'target_refs_json': json.dumps(target_repo_data),
319 }
319 }
320 c.default_source_ref = selected_source_ref
320 c.default_source_ref = selected_source_ref
321
321
322 return render('/pullrequests/pullrequest.html')
322 return render('/pullrequests/pullrequest.html')
323
323
324 @LoginRequired()
324 @LoginRequired()
325 @NotAnonymous()
325 @NotAnonymous()
326 @XHRRequired()
326 @XHRRequired()
327 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
327 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
328 'repository.admin')
328 'repository.admin')
329 @jsonify
329 @jsonify
330 def get_repo_refs(self, repo_name, target_repo_name):
330 def get_repo_refs(self, repo_name, target_repo_name):
331 repo = Repository.get_by_repo_name(target_repo_name)
331 repo = Repository.get_by_repo_name(target_repo_name)
332 if not repo:
332 if not repo:
333 raise HTTPNotFound
333 raise HTTPNotFound
334 return PullRequestModel().generate_repo_data(repo)
334 return PullRequestModel().generate_repo_data(repo)
335
335
336 @LoginRequired()
336 @LoginRequired()
337 @NotAnonymous()
337 @NotAnonymous()
338 @XHRRequired()
338 @XHRRequired()
339 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
339 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
340 'repository.admin')
340 'repository.admin')
341 @jsonify
341 @jsonify
342 def get_repo_destinations(self, repo_name):
342 def get_repo_destinations(self, repo_name):
343 repo = Repository.get_by_repo_name(repo_name)
343 repo = Repository.get_by_repo_name(repo_name)
344 if not repo:
344 if not repo:
345 raise HTTPNotFound
345 raise HTTPNotFound
346 filter_query = request.GET.get('query')
346 filter_query = request.GET.get('query')
347
347
348 query = Repository.query() \
348 query = Repository.query() \
349 .order_by(func.length(Repository.repo_name)) \
349 .order_by(func.length(Repository.repo_name)) \
350 .filter(or_(
350 .filter(or_(
351 Repository.repo_name == repo.repo_name,
351 Repository.repo_name == repo.repo_name,
352 Repository.fork_id == repo.repo_id))
352 Repository.fork_id == repo.repo_id))
353
353
354 if filter_query:
354 if filter_query:
355 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
355 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
356 query = query.filter(
356 query = query.filter(
357 Repository.repo_name.ilike(ilike_expression))
357 Repository.repo_name.ilike(ilike_expression))
358
358
359 add_parent = False
359 add_parent = False
360 if repo.parent:
360 if repo.parent:
361 if filter_query in repo.parent.repo_name:
361 if filter_query in repo.parent.repo_name:
362 if not repo.parent.scm_instance().is_empty():
362 if not repo.parent.scm_instance().is_empty():
363 add_parent = True
363 add_parent = True
364
364
365 limit = 20 - 1 if add_parent else 20
365 limit = 20 - 1 if add_parent else 20
366 all_repos = query.limit(limit).all()
366 all_repos = query.limit(limit).all()
367 if add_parent:
367 if add_parent:
368 all_repos += [repo.parent]
368 all_repos += [repo.parent]
369
369
370 repos = []
370 repos = []
371 for obj in self.scm_model.get_repos(all_repos):
371 for obj in self.scm_model.get_repos(all_repos):
372 repos.append({
372 repos.append({
373 'id': obj['name'],
373 'id': obj['name'],
374 'text': obj['name'],
374 'text': obj['name'],
375 'type': 'repo',
375 'type': 'repo',
376 'obj': obj['dbrepo']
376 'obj': obj['dbrepo']
377 })
377 })
378
378
379 data = {
379 data = {
380 'more': False,
380 'more': False,
381 'results': [{
381 'results': [{
382 'text': _('Repositories'),
382 'text': _('Repositories'),
383 'children': repos
383 'children': repos
384 }] if repos else []
384 }] if repos else []
385 }
385 }
386 return data
386 return data
387
387
388 @LoginRequired()
388 @LoginRequired()
389 @NotAnonymous()
389 @NotAnonymous()
390 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
390 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
391 'repository.admin')
391 'repository.admin')
392 @HasAcceptedRepoType('git', 'hg')
392 @HasAcceptedRepoType('git', 'hg')
393 @auth.CSRFRequired()
393 @auth.CSRFRequired()
394 def create(self, repo_name):
394 def create(self, repo_name):
395 repo = Repository.get_by_repo_name(repo_name)
395 repo = Repository.get_by_repo_name(repo_name)
396 if not repo:
396 if not repo:
397 raise HTTPNotFound
397 raise HTTPNotFound
398
398
399 try:
399 try:
400 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
400 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
401 except formencode.Invalid as errors:
401 except formencode.Invalid as errors:
402 if errors.error_dict.get('revisions'):
402 if errors.error_dict.get('revisions'):
403 msg = 'Revisions: %s' % errors.error_dict['revisions']
403 msg = 'Revisions: %s' % errors.error_dict['revisions']
404 elif errors.error_dict.get('pullrequest_title'):
404 elif errors.error_dict.get('pullrequest_title'):
405 msg = _('Pull request requires a title with min. 3 chars')
405 msg = _('Pull request requires a title with min. 3 chars')
406 else:
406 else:
407 msg = _('Error creating pull request: {}').format(errors)
407 msg = _('Error creating pull request: {}').format(errors)
408 log.exception(msg)
408 log.exception(msg)
409 h.flash(msg, 'error')
409 h.flash(msg, 'error')
410
410
411 # would rather just go back to form ...
411 # would rather just go back to form ...
412 return redirect(url('pullrequest_home', repo_name=repo_name))
412 return redirect(url('pullrequest_home', repo_name=repo_name))
413
413
414 source_repo = _form['source_repo']
414 source_repo = _form['source_repo']
415 source_ref = _form['source_ref']
415 source_ref = _form['source_ref']
416 target_repo = _form['target_repo']
416 target_repo = _form['target_repo']
417 target_ref = _form['target_ref']
417 target_ref = _form['target_ref']
418 commit_ids = _form['revisions'][::-1]
418 commit_ids = _form['revisions'][::-1]
419 reviewers = _form['review_members']
419 reviewers = _form['review_members']
420
420
421 # find the ancestor for this pr
421 # find the ancestor for this pr
422 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
422 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
423 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
423 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
424
424
425 source_scm = source_db_repo.scm_instance()
425 source_scm = source_db_repo.scm_instance()
426 target_scm = target_db_repo.scm_instance()
426 target_scm = target_db_repo.scm_instance()
427
427
428 source_commit = source_scm.get_commit(source_ref.split(':')[-1])
428 source_commit = source_scm.get_commit(source_ref.split(':')[-1])
429 target_commit = target_scm.get_commit(target_ref.split(':')[-1])
429 target_commit = target_scm.get_commit(target_ref.split(':')[-1])
430
430
431 ancestor = source_scm.get_common_ancestor(
431 ancestor = source_scm.get_common_ancestor(
432 source_commit.raw_id, target_commit.raw_id, target_scm)
432 source_commit.raw_id, target_commit.raw_id, target_scm)
433
433
434 target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
434 target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
435 target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
435 target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
436
436
437 pullrequest_title = _form['pullrequest_title']
437 pullrequest_title = _form['pullrequest_title']
438 title_source_ref = source_ref.split(':', 2)[1]
438 title_source_ref = source_ref.split(':', 2)[1]
439 if not pullrequest_title:
439 if not pullrequest_title:
440 pullrequest_title = PullRequestModel().generate_pullrequest_title(
440 pullrequest_title = PullRequestModel().generate_pullrequest_title(
441 source=source_repo,
441 source=source_repo,
442 source_ref=title_source_ref,
442 source_ref=title_source_ref,
443 target=target_repo
443 target=target_repo
444 )
444 )
445
445
446 description = _form['pullrequest_desc']
446 description = _form['pullrequest_desc']
447 try:
447 try:
448 pull_request = PullRequestModel().create(
448 pull_request = PullRequestModel().create(
449 c.rhodecode_user.user_id, source_repo, source_ref, target_repo,
449 c.rhodecode_user.user_id, source_repo, source_ref, target_repo,
450 target_ref, commit_ids, reviewers, pullrequest_title,
450 target_ref, commit_ids, reviewers, pullrequest_title,
451 description
451 description
452 )
452 )
453 Session().commit()
453 Session().commit()
454 h.flash(_('Successfully opened new pull request'),
454 h.flash(_('Successfully opened new pull request'),
455 category='success')
455 category='success')
456 except Exception as e:
456 except Exception as e:
457 raise
458 msg = _('Error occurred during sending pull request')
457 msg = _('Error occurred during sending pull request')
459 log.exception(msg)
458 log.exception(msg)
460 h.flash(msg, category='error')
459 h.flash(msg, category='error')
461 return redirect(url('pullrequest_home', repo_name=repo_name))
460 return redirect(url('pullrequest_home', repo_name=repo_name))
462
461
463 return redirect(url('pullrequest_show', repo_name=target_repo,
462 return redirect(url('pullrequest_show', repo_name=target_repo,
464 pull_request_id=pull_request.pull_request_id))
463 pull_request_id=pull_request.pull_request_id))
465
464
466 @LoginRequired()
465 @LoginRequired()
467 @NotAnonymous()
466 @NotAnonymous()
468 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
467 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
469 'repository.admin')
468 'repository.admin')
470 @auth.CSRFRequired()
469 @auth.CSRFRequired()
471 @jsonify
470 @jsonify
472 def update(self, repo_name, pull_request_id):
471 def update(self, repo_name, pull_request_id):
473 pull_request_id = safe_int(pull_request_id)
472 pull_request_id = safe_int(pull_request_id)
474 pull_request = PullRequest.get_or_404(pull_request_id)
473 pull_request = PullRequest.get_or_404(pull_request_id)
475 # only owner or admin can update it
474 # only owner or admin can update it
476 allowed_to_update = PullRequestModel().check_user_update(
475 allowed_to_update = PullRequestModel().check_user_update(
477 pull_request, c.rhodecode_user)
476 pull_request, c.rhodecode_user)
478 if allowed_to_update:
477 if allowed_to_update:
479 if 'reviewers_ids' in request.POST:
478 if 'reviewers_ids' in request.POST:
480 self._update_reviewers(pull_request_id)
479 self._update_reviewers(pull_request_id)
481 elif str2bool(request.POST.get('update_commits', 'false')):
480 elif str2bool(request.POST.get('update_commits', 'false')):
482 self._update_commits(pull_request)
481 self._update_commits(pull_request)
483 elif str2bool(request.POST.get('close_pull_request', 'false')):
482 elif str2bool(request.POST.get('close_pull_request', 'false')):
484 self._reject_close(pull_request)
483 self._reject_close(pull_request)
485 elif str2bool(request.POST.get('edit_pull_request', 'false')):
484 elif str2bool(request.POST.get('edit_pull_request', 'false')):
486 self._edit_pull_request(pull_request)
485 self._edit_pull_request(pull_request)
487 else:
486 else:
488 raise HTTPBadRequest()
487 raise HTTPBadRequest()
489 return True
488 return True
490 raise HTTPForbidden()
489 raise HTTPForbidden()
491
490
492 def _edit_pull_request(self, pull_request):
491 def _edit_pull_request(self, pull_request):
493 try:
492 try:
494 PullRequestModel().edit(
493 PullRequestModel().edit(
495 pull_request, request.POST.get('title'),
494 pull_request, request.POST.get('title'),
496 request.POST.get('description'))
495 request.POST.get('description'))
497 except ValueError:
496 except ValueError:
498 msg = _(u'Cannot update closed pull requests.')
497 msg = _(u'Cannot update closed pull requests.')
499 h.flash(msg, category='error')
498 h.flash(msg, category='error')
500 return
499 return
501 else:
500 else:
502 Session().commit()
501 Session().commit()
503
502
504 msg = _(u'Pull request title & description updated.')
503 msg = _(u'Pull request title & description updated.')
505 h.flash(msg, category='success')
504 h.flash(msg, category='success')
506 return
505 return
507
506
508 def _update_commits(self, pull_request):
507 def _update_commits(self, pull_request):
509 try:
508 try:
510 if PullRequestModel().has_valid_update_type(pull_request):
509 if PullRequestModel().has_valid_update_type(pull_request):
511 updated_version, changes = PullRequestModel().update_commits(
510 updated_version, changes = PullRequestModel().update_commits(
512 pull_request)
511 pull_request)
513 if updated_version:
512 if updated_version:
514 msg = _(
513 msg = _(
515 u'Pull request updated to "{source_commit_id}" with '
514 u'Pull request updated to "{source_commit_id}" with '
516 u'{count_added} added, {count_removed} removed '
515 u'{count_added} added, {count_removed} removed '
517 u'commits.'
516 u'commits.'
518 ).format(
517 ).format(
519 source_commit_id=pull_request.source_ref_parts.commit_id,
518 source_commit_id=pull_request.source_ref_parts.commit_id,
520 count_added=len(changes.added),
519 count_added=len(changes.added),
521 count_removed=len(changes.removed))
520 count_removed=len(changes.removed))
522 h.flash(msg, category='success')
521 h.flash(msg, category='success')
523 else:
522 else:
524 h.flash(_("Nothing changed in pull request."),
523 h.flash(_("Nothing changed in pull request."),
525 category='warning')
524 category='warning')
526 else:
525 else:
527 msg = _(
526 msg = _(
528 u"Skipping update of pull request due to reference "
527 u"Skipping update of pull request due to reference "
529 u"type: {reference_type}"
528 u"type: {reference_type}"
530 ).format(reference_type=pull_request.source_ref_parts.type)
529 ).format(reference_type=pull_request.source_ref_parts.type)
531 h.flash(msg, category='warning')
530 h.flash(msg, category='warning')
532 except CommitDoesNotExistError:
531 except CommitDoesNotExistError:
533 h.flash(
532 h.flash(
534 _(u'Update failed due to missing commits.'), category='error')
533 _(u'Update failed due to missing commits.'), category='error')
535
534
536 @auth.CSRFRequired()
535 @auth.CSRFRequired()
537 @LoginRequired()
536 @LoginRequired()
538 @NotAnonymous()
537 @NotAnonymous()
539 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
538 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
540 'repository.admin')
539 'repository.admin')
541 def merge(self, repo_name, pull_request_id):
540 def merge(self, repo_name, pull_request_id):
542 """
541 """
543 POST /{repo_name}/pull-request/{pull_request_id}
542 POST /{repo_name}/pull-request/{pull_request_id}
544
543
545 Merge will perform a server-side merge of the specified
544 Merge will perform a server-side merge of the specified
546 pull request, if the pull request is approved and mergeable.
545 pull request, if the pull request is approved and mergeable.
547 After succesfull merging, the pull request is automatically
546 After succesfull merging, the pull request is automatically
548 closed, with a relevant comment.
547 closed, with a relevant comment.
549 """
548 """
550 pull_request_id = safe_int(pull_request_id)
549 pull_request_id = safe_int(pull_request_id)
551 pull_request = PullRequest.get_or_404(pull_request_id)
550 pull_request = PullRequest.get_or_404(pull_request_id)
552 user = c.rhodecode_user
551 user = c.rhodecode_user
553
552
554 if self._meets_merge_pre_conditions(pull_request, user):
553 if self._meets_merge_pre_conditions(pull_request, user):
555 log.debug("Pre-conditions checked, trying to merge.")
554 log.debug("Pre-conditions checked, trying to merge.")
556 extras = vcs_operation_context(
555 extras = vcs_operation_context(
557 request.environ, repo_name=pull_request.target_repo.repo_name,
556 request.environ, repo_name=pull_request.target_repo.repo_name,
558 username=user.username, action='push',
557 username=user.username, action='push',
559 scm=pull_request.target_repo.repo_type)
558 scm=pull_request.target_repo.repo_type)
560 self._merge_pull_request(pull_request, user, extras)
559 self._merge_pull_request(pull_request, user, extras)
561
560
562 return redirect(url(
561 return redirect(url(
563 'pullrequest_show',
562 'pullrequest_show',
564 repo_name=pull_request.target_repo.repo_name,
563 repo_name=pull_request.target_repo.repo_name,
565 pull_request_id=pull_request.pull_request_id))
564 pull_request_id=pull_request.pull_request_id))
566
565
567 def _meets_merge_pre_conditions(self, pull_request, user):
566 def _meets_merge_pre_conditions(self, pull_request, user):
568 if not PullRequestModel().check_user_merge(pull_request, user):
567 if not PullRequestModel().check_user_merge(pull_request, user):
569 raise HTTPForbidden()
568 raise HTTPForbidden()
570
569
571 merge_status, msg = PullRequestModel().merge_status(pull_request)
570 merge_status, msg = PullRequestModel().merge_status(pull_request)
572 if not merge_status:
571 if not merge_status:
573 log.debug("Cannot merge, not mergeable.")
572 log.debug("Cannot merge, not mergeable.")
574 h.flash(msg, category='error')
573 h.flash(msg, category='error')
575 return False
574 return False
576
575
577 if (pull_request.calculated_review_status()
576 if (pull_request.calculated_review_status()
578 is not ChangesetStatus.STATUS_APPROVED):
577 is not ChangesetStatus.STATUS_APPROVED):
579 log.debug("Cannot merge, approval is pending.")
578 log.debug("Cannot merge, approval is pending.")
580 msg = _('Pull request reviewer approval is pending.')
579 msg = _('Pull request reviewer approval is pending.')
581 h.flash(msg, category='error')
580 h.flash(msg, category='error')
582 return False
581 return False
583 return True
582 return True
584
583
585 def _merge_pull_request(self, pull_request, user, extras):
584 def _merge_pull_request(self, pull_request, user, extras):
586 merge_resp = PullRequestModel().merge(
585 merge_resp = PullRequestModel().merge(
587 pull_request, user, extras=extras)
586 pull_request, user, extras=extras)
588
587
589 if merge_resp.executed:
588 if merge_resp.executed:
590 log.debug("The merge was successful, closing the pull request.")
589 log.debug("The merge was successful, closing the pull request.")
591 PullRequestModel().close_pull_request(
590 PullRequestModel().close_pull_request(
592 pull_request.pull_request_id, user)
591 pull_request.pull_request_id, user)
593 Session().commit()
592 Session().commit()
594 msg = _('Pull request was successfully merged and closed.')
593 msg = _('Pull request was successfully merged and closed.')
595 h.flash(msg, category='success')
594 h.flash(msg, category='success')
596 else:
595 else:
597 log.debug(
596 log.debug(
598 "The merge was not successful. Merge response: %s",
597 "The merge was not successful. Merge response: %s",
599 merge_resp)
598 merge_resp)
600 msg = PullRequestModel().merge_status_message(
599 msg = PullRequestModel().merge_status_message(
601 merge_resp.failure_reason)
600 merge_resp.failure_reason)
602 h.flash(msg, category='error')
601 h.flash(msg, category='error')
603
602
604 def _update_reviewers(self, pull_request_id):
603 def _update_reviewers(self, pull_request_id):
605 reviewers_ids = map(int, filter(
604 reviewers_ids = map(int, filter(
606 lambda v: v not in [None, ''],
605 lambda v: v not in [None, ''],
607 request.POST.get('reviewers_ids', '').split(',')))
606 request.POST.get('reviewers_ids', '').split(',')))
608 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
607 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
609 Session().commit()
608 Session().commit()
610
609
611 def _reject_close(self, pull_request):
610 def _reject_close(self, pull_request):
612 if pull_request.is_closed():
611 if pull_request.is_closed():
613 raise HTTPForbidden()
612 raise HTTPForbidden()
614
613
615 PullRequestModel().close_pull_request_with_comment(
614 PullRequestModel().close_pull_request_with_comment(
616 pull_request, c.rhodecode_user, c.rhodecode_db_repo)
615 pull_request, c.rhodecode_user, c.rhodecode_db_repo)
617 Session().commit()
616 Session().commit()
618
617
619 @LoginRequired()
618 @LoginRequired()
620 @NotAnonymous()
619 @NotAnonymous()
621 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
620 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
622 'repository.admin')
621 'repository.admin')
623 @auth.CSRFRequired()
622 @auth.CSRFRequired()
624 @jsonify
623 @jsonify
625 def delete(self, repo_name, pull_request_id):
624 def delete(self, repo_name, pull_request_id):
626 pull_request_id = safe_int(pull_request_id)
625 pull_request_id = safe_int(pull_request_id)
627 pull_request = PullRequest.get_or_404(pull_request_id)
626 pull_request = PullRequest.get_or_404(pull_request_id)
628 # only owner can delete it !
627 # only owner can delete it !
629 if pull_request.author.user_id == c.rhodecode_user.user_id:
628 if pull_request.author.user_id == c.rhodecode_user.user_id:
630 PullRequestModel().delete(pull_request)
629 PullRequestModel().delete(pull_request)
631 Session().commit()
630 Session().commit()
632 h.flash(_('Successfully deleted pull request'),
631 h.flash(_('Successfully deleted pull request'),
633 category='success')
632 category='success')
634 return redirect(url('my_account_pullrequests'))
633 return redirect(url('my_account_pullrequests'))
635 raise HTTPForbidden()
634 raise HTTPForbidden()
636
635
637 @LoginRequired()
636 @LoginRequired()
638 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
637 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
639 'repository.admin')
638 'repository.admin')
640 def show(self, repo_name, pull_request_id):
639 def show(self, repo_name, pull_request_id):
641 pull_request_id = safe_int(pull_request_id)
640 pull_request_id = safe_int(pull_request_id)
642 c.pull_request = PullRequest.get_or_404(pull_request_id)
641 c.pull_request = PullRequest.get_or_404(pull_request_id)
643
642
644 # pull_requests repo_name we opened it against
643 # pull_requests repo_name we opened it against
645 # ie. target_repo must match
644 # ie. target_repo must match
646 if repo_name != c.pull_request.target_repo.repo_name:
645 if repo_name != c.pull_request.target_repo.repo_name:
647 raise HTTPNotFound
646 raise HTTPNotFound
648
647
649 c.allowed_to_change_status = PullRequestModel(). \
648 c.allowed_to_change_status = PullRequestModel(). \
650 check_user_change_status(c.pull_request, c.rhodecode_user)
649 check_user_change_status(c.pull_request, c.rhodecode_user)
651 c.allowed_to_update = PullRequestModel().check_user_update(
650 c.allowed_to_update = PullRequestModel().check_user_update(
652 c.pull_request, c.rhodecode_user) and not c.pull_request.is_closed()
651 c.pull_request, c.rhodecode_user) and not c.pull_request.is_closed()
653 c.allowed_to_merge = PullRequestModel().check_user_merge(
652 c.allowed_to_merge = PullRequestModel().check_user_merge(
654 c.pull_request, c.rhodecode_user) and not c.pull_request.is_closed()
653 c.pull_request, c.rhodecode_user) and not c.pull_request.is_closed()
655
654
656 cc_model = ChangesetCommentsModel()
655 cc_model = ChangesetCommentsModel()
657
656
658 c.pull_request_reviewers = c.pull_request.reviewers_statuses()
657 c.pull_request_reviewers = c.pull_request.reviewers_statuses()
659
658
660 c.pull_request_review_status = c.pull_request.calculated_review_status()
659 c.pull_request_review_status = c.pull_request.calculated_review_status()
661 c.pr_merge_status, c.pr_merge_msg = PullRequestModel().merge_status(
660 c.pr_merge_status, c.pr_merge_msg = PullRequestModel().merge_status(
662 c.pull_request)
661 c.pull_request)
663 c.approval_msg = None
662 c.approval_msg = None
664 if c.pull_request_review_status != ChangesetStatus.STATUS_APPROVED:
663 if c.pull_request_review_status != ChangesetStatus.STATUS_APPROVED:
665 c.approval_msg = _('Reviewer approval is pending.')
664 c.approval_msg = _('Reviewer approval is pending.')
666 c.pr_merge_status = False
665 c.pr_merge_status = False
667 # load compare data into template context
666 # load compare data into template context
668 enable_comments = not c.pull_request.is_closed()
667 enable_comments = not c.pull_request.is_closed()
669 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
668 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
670
669
671 # this is a hack to properly display links, when creating PR, the
670 # this is a hack to properly display links, when creating PR, the
672 # compare view and others uses different notation, and
671 # compare view and others uses different notation, and
673 # compare_commits.html renders links based on the target_repo.
672 # compare_commits.html renders links based on the target_repo.
674 # We need to swap that here to generate it properly on the html side
673 # We need to swap that here to generate it properly on the html side
675 c.target_repo = c.source_repo
674 c.target_repo = c.source_repo
676
675
677 # inline comments
676 # inline comments
678 c.inline_cnt = 0
677 c.inline_cnt = 0
679 c.inline_comments = cc_model.get_inline_comments(
678 c.inline_comments = cc_model.get_inline_comments(
680 c.rhodecode_db_repo.repo_id,
679 c.rhodecode_db_repo.repo_id,
681 pull_request=pull_request_id).items()
680 pull_request=pull_request_id).items()
682 # count inline comments
681 # count inline comments
683 for __, lines in c.inline_comments:
682 for __, lines in c.inline_comments:
684 for comments in lines.values():
683 for comments in lines.values():
685 c.inline_cnt += len(comments)
684 c.inline_cnt += len(comments)
686
685
687 # outdated comments
686 # outdated comments
688 c.outdated_cnt = 0
687 c.outdated_cnt = 0
689 if ChangesetCommentsModel.use_outdated_comments(c.pull_request):
688 if ChangesetCommentsModel.use_outdated_comments(c.pull_request):
690 c.outdated_comments = cc_model.get_outdated_comments(
689 c.outdated_comments = cc_model.get_outdated_comments(
691 c.rhodecode_db_repo.repo_id,
690 c.rhodecode_db_repo.repo_id,
692 pull_request=c.pull_request)
691 pull_request=c.pull_request)
693 # Count outdated comments and check for deleted files
692 # Count outdated comments and check for deleted files
694 for file_name, lines in c.outdated_comments.iteritems():
693 for file_name, lines in c.outdated_comments.iteritems():
695 for comments in lines.values():
694 for comments in lines.values():
696 c.outdated_cnt += len(comments)
695 c.outdated_cnt += len(comments)
697 if file_name not in c.included_files:
696 if file_name not in c.included_files:
698 c.deleted_files.append(file_name)
697 c.deleted_files.append(file_name)
699 else:
698 else:
700 c.outdated_comments = {}
699 c.outdated_comments = {}
701
700
702 # comments
701 # comments
703 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
702 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
704 pull_request=pull_request_id)
703 pull_request=pull_request_id)
705
704
706 if c.allowed_to_update:
705 if c.allowed_to_update:
707 force_close = ('forced_closed', _('Close Pull Request'))
706 force_close = ('forced_closed', _('Close Pull Request'))
708 statuses = ChangesetStatus.STATUSES + [force_close]
707 statuses = ChangesetStatus.STATUSES + [force_close]
709 else:
708 else:
710 statuses = ChangesetStatus.STATUSES
709 statuses = ChangesetStatus.STATUSES
711 c.commit_statuses = statuses
710 c.commit_statuses = statuses
712
711
713 c.ancestor = None # TODO: add ancestor here
712 c.ancestor = None # TODO: add ancestor here
714
713
715 return render('/pullrequests/pullrequest_show.html')
714 return render('/pullrequests/pullrequest_show.html')
716
715
717 @LoginRequired()
716 @LoginRequired()
718 @NotAnonymous()
717 @NotAnonymous()
719 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
718 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
720 'repository.admin')
719 'repository.admin')
721 @auth.CSRFRequired()
720 @auth.CSRFRequired()
722 @jsonify
721 @jsonify
723 def comment(self, repo_name, pull_request_id):
722 def comment(self, repo_name, pull_request_id):
724 pull_request_id = safe_int(pull_request_id)
723 pull_request_id = safe_int(pull_request_id)
725 pull_request = PullRequest.get_or_404(pull_request_id)
724 pull_request = PullRequest.get_or_404(pull_request_id)
726 if pull_request.is_closed():
725 if pull_request.is_closed():
727 raise HTTPForbidden()
726 raise HTTPForbidden()
728
727
729 # TODO: johbo: Re-think this bit, "approved_closed" does not exist
728 # TODO: johbo: Re-think this bit, "approved_closed" does not exist
730 # as a changeset status, still we want to send it in one value.
729 # as a changeset status, still we want to send it in one value.
731 status = request.POST.get('changeset_status', None)
730 status = request.POST.get('changeset_status', None)
732 text = request.POST.get('text')
731 text = request.POST.get('text')
733 if status and '_closed' in status:
732 if status and '_closed' in status:
734 close_pr = True
733 close_pr = True
735 status = status.replace('_closed', '')
734 status = status.replace('_closed', '')
736 else:
735 else:
737 close_pr = False
736 close_pr = False
738
737
739 forced = (status == 'forced')
738 forced = (status == 'forced')
740 if forced:
739 if forced:
741 status = 'rejected'
740 status = 'rejected'
742
741
743 allowed_to_change_status = PullRequestModel().check_user_change_status(
742 allowed_to_change_status = PullRequestModel().check_user_change_status(
744 pull_request, c.rhodecode_user)
743 pull_request, c.rhodecode_user)
745
744
746 if status and allowed_to_change_status:
745 if status and allowed_to_change_status:
747 message = (_('Status change %(transition_icon)s %(status)s')
746 message = (_('Status change %(transition_icon)s %(status)s')
748 % {'transition_icon': '>',
747 % {'transition_icon': '>',
749 'status': ChangesetStatus.get_status_lbl(status)})
748 'status': ChangesetStatus.get_status_lbl(status)})
750 if close_pr:
749 if close_pr:
751 message = _('Closing with') + ' ' + message
750 message = _('Closing with') + ' ' + message
752 text = text or message
751 text = text or message
753 comm = ChangesetCommentsModel().create(
752 comm = ChangesetCommentsModel().create(
754 text=text,
753 text=text,
755 repo=c.rhodecode_db_repo.repo_id,
754 repo=c.rhodecode_db_repo.repo_id,
756 user=c.rhodecode_user.user_id,
755 user=c.rhodecode_user.user_id,
757 pull_request=pull_request_id,
756 pull_request=pull_request_id,
758 f_path=request.POST.get('f_path'),
757 f_path=request.POST.get('f_path'),
759 line_no=request.POST.get('line'),
758 line_no=request.POST.get('line'),
760 status_change=(ChangesetStatus.get_status_lbl(status)
759 status_change=(ChangesetStatus.get_status_lbl(status)
761 if status and allowed_to_change_status else None),
760 if status and allowed_to_change_status else None),
762 closing_pr=close_pr
761 closing_pr=close_pr
763 )
762 )
764
763
765 if allowed_to_change_status:
764 if allowed_to_change_status:
766 old_calculated_status = pull_request.calculated_review_status()
765 old_calculated_status = pull_request.calculated_review_status()
767 # get status if set !
766 # get status if set !
768 if status:
767 if status:
769 ChangesetStatusModel().set_status(
768 ChangesetStatusModel().set_status(
770 c.rhodecode_db_repo.repo_id,
769 c.rhodecode_db_repo.repo_id,
771 status,
770 status,
772 c.rhodecode_user.user_id,
771 c.rhodecode_user.user_id,
773 comm,
772 comm,
774 pull_request=pull_request_id
773 pull_request=pull_request_id
775 )
774 )
776
775
777 Session().flush()
776 Session().flush()
778 # we now calculate the status of pull request, and based on that
777 # we now calculate the status of pull request, and based on that
779 # calculation we set the commits status
778 # calculation we set the commits status
780 calculated_status = pull_request.calculated_review_status()
779 calculated_status = pull_request.calculated_review_status()
781 if old_calculated_status != calculated_status:
780 if old_calculated_status != calculated_status:
782 PullRequestModel()._trigger_pull_request_hook(
781 PullRequestModel()._trigger_pull_request_hook(
783 pull_request, c.rhodecode_user, 'review_status_change')
782 pull_request, c.rhodecode_user, 'review_status_change')
784
783
785 calculated_status_lbl = ChangesetStatus.get_status_lbl(
784 calculated_status_lbl = ChangesetStatus.get_status_lbl(
786 calculated_status)
785 calculated_status)
787
786
788 if close_pr:
787 if close_pr:
789 status_completed = (
788 status_completed = (
790 calculated_status in [ChangesetStatus.STATUS_APPROVED,
789 calculated_status in [ChangesetStatus.STATUS_APPROVED,
791 ChangesetStatus.STATUS_REJECTED])
790 ChangesetStatus.STATUS_REJECTED])
792 if forced or status_completed:
791 if forced or status_completed:
793 PullRequestModel().close_pull_request(
792 PullRequestModel().close_pull_request(
794 pull_request_id, c.rhodecode_user)
793 pull_request_id, c.rhodecode_user)
795 else:
794 else:
796 h.flash(_('Closing pull request on other statuses than '
795 h.flash(_('Closing pull request on other statuses than '
797 'rejected or approved is forbidden. '
796 'rejected or approved is forbidden. '
798 'Calculated status from all reviewers '
797 'Calculated status from all reviewers '
799 'is currently: %s') % calculated_status_lbl,
798 'is currently: %s') % calculated_status_lbl,
800 category='warning')
799 category='warning')
801
800
802 Session().commit()
801 Session().commit()
803
802
804 if not request.is_xhr:
803 if not request.is_xhr:
805 return redirect(h.url('pullrequest_show', repo_name=repo_name,
804 return redirect(h.url('pullrequest_show', repo_name=repo_name,
806 pull_request_id=pull_request_id))
805 pull_request_id=pull_request_id))
807
806
808 data = {
807 data = {
809 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
808 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
810 }
809 }
811 if comm:
810 if comm:
812 c.co = comm
811 c.co = comm
813 data.update(comm.get_dict())
812 data.update(comm.get_dict())
814 data.update({'rendered_text':
813 data.update({'rendered_text':
815 render('changeset/changeset_comment_block.html')})
814 render('changeset/changeset_comment_block.html')})
816
815
817 return data
816 return data
818
817
819 @LoginRequired()
818 @LoginRequired()
820 @NotAnonymous()
819 @NotAnonymous()
821 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
820 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
822 'repository.admin')
821 'repository.admin')
823 @auth.CSRFRequired()
822 @auth.CSRFRequired()
824 @jsonify
823 @jsonify
825 def delete_comment(self, repo_name, comment_id):
824 def delete_comment(self, repo_name, comment_id):
826 return self._delete_comment(comment_id)
825 return self._delete_comment(comment_id)
827
826
828 def _delete_comment(self, comment_id):
827 def _delete_comment(self, comment_id):
829 comment_id = safe_int(comment_id)
828 comment_id = safe_int(comment_id)
830 co = ChangesetComment.get_or_404(comment_id)
829 co = ChangesetComment.get_or_404(comment_id)
831 if co.pull_request.is_closed():
830 if co.pull_request.is_closed():
832 # don't allow deleting comments on closed pull request
831 # don't allow deleting comments on closed pull request
833 raise HTTPForbidden()
832 raise HTTPForbidden()
834
833
835 is_owner = co.author.user_id == c.rhodecode_user.user_id
834 is_owner = co.author.user_id == c.rhodecode_user.user_id
836 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name)
835 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name)
837 if h.HasPermissionAny('hg.admin')() or is_repo_admin or is_owner:
836 if h.HasPermissionAny('hg.admin')() or is_repo_admin or is_owner:
838 old_calculated_status = co.pull_request.calculated_review_status()
837 old_calculated_status = co.pull_request.calculated_review_status()
839 ChangesetCommentsModel().delete(comment=co)
838 ChangesetCommentsModel().delete(comment=co)
840 Session().commit()
839 Session().commit()
841 calculated_status = co.pull_request.calculated_review_status()
840 calculated_status = co.pull_request.calculated_review_status()
842 if old_calculated_status != calculated_status:
841 if old_calculated_status != calculated_status:
843 PullRequestModel()._trigger_pull_request_hook(
842 PullRequestModel()._trigger_pull_request_hook(
844 co.pull_request, c.rhodecode_user, 'review_status_change')
843 co.pull_request, c.rhodecode_user, 'review_status_change')
845 return True
844 return True
846 else:
845 else:
847 raise HTTPForbidden()
846 raise HTTPForbidden()
General Comments 0
You need to be logged in to leave comments. Login now