##// END OF EJS Templates
pull-requests: use proper validation of pull request title to prevent from bad errors.
marcink -
r2479:2b695b6e default
parent child Browse files
Show More
@@ -1,1240 +1,1240 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-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 import logging
21 import logging
22 import collections
22 import collections
23
23
24 import formencode
24 import formencode
25 import formencode.htmlfill
25 import formencode.htmlfill
26 import peppercorn
26 import peppercorn
27 from pyramid.httpexceptions import (
27 from pyramid.httpexceptions import (
28 HTTPFound, HTTPNotFound, HTTPForbidden, HTTPBadRequest)
28 HTTPFound, HTTPNotFound, HTTPForbidden, HTTPBadRequest)
29 from pyramid.view import view_config
29 from pyramid.view import view_config
30 from pyramid.renderers import render
30 from pyramid.renderers import render
31
31
32 from rhodecode import events
32 from rhodecode import events
33 from rhodecode.apps._base import RepoAppView, DataGridAppView
33 from rhodecode.apps._base import RepoAppView, DataGridAppView
34
34
35 from rhodecode.lib import helpers as h, diffs, codeblocks, channelstream
35 from rhodecode.lib import helpers as h, diffs, codeblocks, channelstream
36 from rhodecode.lib.base import vcs_operation_context
36 from rhodecode.lib.base import vcs_operation_context
37 from rhodecode.lib.ext_json import json
37 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.auth import (
38 from rhodecode.lib.auth import (
39 LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator,
39 LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator,
40 NotAnonymous, CSRFRequired)
40 NotAnonymous, CSRFRequired)
41 from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode
41 from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode
42 from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason
42 from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason
43 from rhodecode.lib.vcs.exceptions import (CommitDoesNotExistError,
43 from rhodecode.lib.vcs.exceptions import (CommitDoesNotExistError,
44 RepositoryRequirementError, NodeDoesNotExistError, EmptyRepositoryError)
44 RepositoryRequirementError, NodeDoesNotExistError, EmptyRepositoryError)
45 from rhodecode.model.changeset_status import ChangesetStatusModel
45 from rhodecode.model.changeset_status import ChangesetStatusModel
46 from rhodecode.model.comment import CommentsModel
46 from rhodecode.model.comment import CommentsModel
47 from rhodecode.model.db import (func, or_, PullRequest, PullRequestVersion,
47 from rhodecode.model.db import (func, or_, PullRequest, PullRequestVersion,
48 ChangesetComment, ChangesetStatus, Repository)
48 ChangesetComment, ChangesetStatus, Repository)
49 from rhodecode.model.forms import PullRequestForm
49 from rhodecode.model.forms import PullRequestForm
50 from rhodecode.model.meta import Session
50 from rhodecode.model.meta import Session
51 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
51 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
52 from rhodecode.model.scm import ScmModel
52 from rhodecode.model.scm import ScmModel
53
53
54 log = logging.getLogger(__name__)
54 log = logging.getLogger(__name__)
55
55
56
56
57 class RepoPullRequestsView(RepoAppView, DataGridAppView):
57 class RepoPullRequestsView(RepoAppView, DataGridAppView):
58
58
59 def load_default_context(self):
59 def load_default_context(self):
60 c = self._get_local_tmpl_context(include_app_defaults=True)
60 c = self._get_local_tmpl_context(include_app_defaults=True)
61 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
61 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
62 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
62 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
63
63
64 return c
64 return c
65
65
66 def _get_pull_requests_list(
66 def _get_pull_requests_list(
67 self, repo_name, source, filter_type, opened_by, statuses):
67 self, repo_name, source, filter_type, opened_by, statuses):
68
68
69 draw, start, limit = self._extract_chunk(self.request)
69 draw, start, limit = self._extract_chunk(self.request)
70 search_q, order_by, order_dir = self._extract_ordering(self.request)
70 search_q, order_by, order_dir = self._extract_ordering(self.request)
71 _render = self.request.get_partial_renderer(
71 _render = self.request.get_partial_renderer(
72 'rhodecode:templates/data_table/_dt_elements.mako')
72 'rhodecode:templates/data_table/_dt_elements.mako')
73
73
74 # pagination
74 # pagination
75
75
76 if filter_type == 'awaiting_review':
76 if filter_type == 'awaiting_review':
77 pull_requests = PullRequestModel().get_awaiting_review(
77 pull_requests = PullRequestModel().get_awaiting_review(
78 repo_name, source=source, opened_by=opened_by,
78 repo_name, source=source, opened_by=opened_by,
79 statuses=statuses, offset=start, length=limit,
79 statuses=statuses, offset=start, length=limit,
80 order_by=order_by, order_dir=order_dir)
80 order_by=order_by, order_dir=order_dir)
81 pull_requests_total_count = PullRequestModel().count_awaiting_review(
81 pull_requests_total_count = PullRequestModel().count_awaiting_review(
82 repo_name, source=source, statuses=statuses,
82 repo_name, source=source, statuses=statuses,
83 opened_by=opened_by)
83 opened_by=opened_by)
84 elif filter_type == 'awaiting_my_review':
84 elif filter_type == 'awaiting_my_review':
85 pull_requests = PullRequestModel().get_awaiting_my_review(
85 pull_requests = PullRequestModel().get_awaiting_my_review(
86 repo_name, source=source, opened_by=opened_by,
86 repo_name, source=source, opened_by=opened_by,
87 user_id=self._rhodecode_user.user_id, statuses=statuses,
87 user_id=self._rhodecode_user.user_id, statuses=statuses,
88 offset=start, length=limit, order_by=order_by,
88 offset=start, length=limit, order_by=order_by,
89 order_dir=order_dir)
89 order_dir=order_dir)
90 pull_requests_total_count = PullRequestModel().count_awaiting_my_review(
90 pull_requests_total_count = PullRequestModel().count_awaiting_my_review(
91 repo_name, source=source, user_id=self._rhodecode_user.user_id,
91 repo_name, source=source, user_id=self._rhodecode_user.user_id,
92 statuses=statuses, opened_by=opened_by)
92 statuses=statuses, opened_by=opened_by)
93 else:
93 else:
94 pull_requests = PullRequestModel().get_all(
94 pull_requests = PullRequestModel().get_all(
95 repo_name, source=source, opened_by=opened_by,
95 repo_name, source=source, opened_by=opened_by,
96 statuses=statuses, offset=start, length=limit,
96 statuses=statuses, offset=start, length=limit,
97 order_by=order_by, order_dir=order_dir)
97 order_by=order_by, order_dir=order_dir)
98 pull_requests_total_count = PullRequestModel().count_all(
98 pull_requests_total_count = PullRequestModel().count_all(
99 repo_name, source=source, statuses=statuses,
99 repo_name, source=source, statuses=statuses,
100 opened_by=opened_by)
100 opened_by=opened_by)
101
101
102 data = []
102 data = []
103 comments_model = CommentsModel()
103 comments_model = CommentsModel()
104 for pr in pull_requests:
104 for pr in pull_requests:
105 comments = comments_model.get_all_comments(
105 comments = comments_model.get_all_comments(
106 self.db_repo.repo_id, pull_request=pr)
106 self.db_repo.repo_id, pull_request=pr)
107
107
108 data.append({
108 data.append({
109 'name': _render('pullrequest_name',
109 'name': _render('pullrequest_name',
110 pr.pull_request_id, pr.target_repo.repo_name),
110 pr.pull_request_id, pr.target_repo.repo_name),
111 'name_raw': pr.pull_request_id,
111 'name_raw': pr.pull_request_id,
112 'status': _render('pullrequest_status',
112 'status': _render('pullrequest_status',
113 pr.calculated_review_status()),
113 pr.calculated_review_status()),
114 'title': _render(
114 'title': _render(
115 'pullrequest_title', pr.title, pr.description),
115 'pullrequest_title', pr.title, pr.description),
116 'description': h.escape(pr.description),
116 'description': h.escape(pr.description),
117 'updated_on': _render('pullrequest_updated_on',
117 'updated_on': _render('pullrequest_updated_on',
118 h.datetime_to_time(pr.updated_on)),
118 h.datetime_to_time(pr.updated_on)),
119 'updated_on_raw': h.datetime_to_time(pr.updated_on),
119 'updated_on_raw': h.datetime_to_time(pr.updated_on),
120 'created_on': _render('pullrequest_updated_on',
120 'created_on': _render('pullrequest_updated_on',
121 h.datetime_to_time(pr.created_on)),
121 h.datetime_to_time(pr.created_on)),
122 'created_on_raw': h.datetime_to_time(pr.created_on),
122 'created_on_raw': h.datetime_to_time(pr.created_on),
123 'author': _render('pullrequest_author',
123 'author': _render('pullrequest_author',
124 pr.author.full_contact, ),
124 pr.author.full_contact, ),
125 'author_raw': pr.author.full_name,
125 'author_raw': pr.author.full_name,
126 'comments': _render('pullrequest_comments', len(comments)),
126 'comments': _render('pullrequest_comments', len(comments)),
127 'comments_raw': len(comments),
127 'comments_raw': len(comments),
128 'closed': pr.is_closed(),
128 'closed': pr.is_closed(),
129 })
129 })
130
130
131 data = ({
131 data = ({
132 'draw': draw,
132 'draw': draw,
133 'data': data,
133 'data': data,
134 'recordsTotal': pull_requests_total_count,
134 'recordsTotal': pull_requests_total_count,
135 'recordsFiltered': pull_requests_total_count,
135 'recordsFiltered': pull_requests_total_count,
136 })
136 })
137 return data
137 return data
138
138
139 @LoginRequired()
139 @LoginRequired()
140 @HasRepoPermissionAnyDecorator(
140 @HasRepoPermissionAnyDecorator(
141 'repository.read', 'repository.write', 'repository.admin')
141 'repository.read', 'repository.write', 'repository.admin')
142 @view_config(
142 @view_config(
143 route_name='pullrequest_show_all', request_method='GET',
143 route_name='pullrequest_show_all', request_method='GET',
144 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
144 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
145 def pull_request_list(self):
145 def pull_request_list(self):
146 c = self.load_default_context()
146 c = self.load_default_context()
147
147
148 req_get = self.request.GET
148 req_get = self.request.GET
149 c.source = str2bool(req_get.get('source'))
149 c.source = str2bool(req_get.get('source'))
150 c.closed = str2bool(req_get.get('closed'))
150 c.closed = str2bool(req_get.get('closed'))
151 c.my = str2bool(req_get.get('my'))
151 c.my = str2bool(req_get.get('my'))
152 c.awaiting_review = str2bool(req_get.get('awaiting_review'))
152 c.awaiting_review = str2bool(req_get.get('awaiting_review'))
153 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
153 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
154
154
155 c.active = 'open'
155 c.active = 'open'
156 if c.my:
156 if c.my:
157 c.active = 'my'
157 c.active = 'my'
158 if c.closed:
158 if c.closed:
159 c.active = 'closed'
159 c.active = 'closed'
160 if c.awaiting_review and not c.source:
160 if c.awaiting_review and not c.source:
161 c.active = 'awaiting'
161 c.active = 'awaiting'
162 if c.source and not c.awaiting_review:
162 if c.source and not c.awaiting_review:
163 c.active = 'source'
163 c.active = 'source'
164 if c.awaiting_my_review:
164 if c.awaiting_my_review:
165 c.active = 'awaiting_my'
165 c.active = 'awaiting_my'
166
166
167 return self._get_template_context(c)
167 return self._get_template_context(c)
168
168
169 @LoginRequired()
169 @LoginRequired()
170 @HasRepoPermissionAnyDecorator(
170 @HasRepoPermissionAnyDecorator(
171 'repository.read', 'repository.write', 'repository.admin')
171 'repository.read', 'repository.write', 'repository.admin')
172 @view_config(
172 @view_config(
173 route_name='pullrequest_show_all_data', request_method='GET',
173 route_name='pullrequest_show_all_data', request_method='GET',
174 renderer='json_ext', xhr=True)
174 renderer='json_ext', xhr=True)
175 def pull_request_list_data(self):
175 def pull_request_list_data(self):
176 self.load_default_context()
176 self.load_default_context()
177
177
178 # additional filters
178 # additional filters
179 req_get = self.request.GET
179 req_get = self.request.GET
180 source = str2bool(req_get.get('source'))
180 source = str2bool(req_get.get('source'))
181 closed = str2bool(req_get.get('closed'))
181 closed = str2bool(req_get.get('closed'))
182 my = str2bool(req_get.get('my'))
182 my = str2bool(req_get.get('my'))
183 awaiting_review = str2bool(req_get.get('awaiting_review'))
183 awaiting_review = str2bool(req_get.get('awaiting_review'))
184 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
184 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
185
185
186 filter_type = 'awaiting_review' if awaiting_review \
186 filter_type = 'awaiting_review' if awaiting_review \
187 else 'awaiting_my_review' if awaiting_my_review \
187 else 'awaiting_my_review' if awaiting_my_review \
188 else None
188 else None
189
189
190 opened_by = None
190 opened_by = None
191 if my:
191 if my:
192 opened_by = [self._rhodecode_user.user_id]
192 opened_by = [self._rhodecode_user.user_id]
193
193
194 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
194 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
195 if closed:
195 if closed:
196 statuses = [PullRequest.STATUS_CLOSED]
196 statuses = [PullRequest.STATUS_CLOSED]
197
197
198 data = self._get_pull_requests_list(
198 data = self._get_pull_requests_list(
199 repo_name=self.db_repo_name, source=source,
199 repo_name=self.db_repo_name, source=source,
200 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
200 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
201
201
202 return data
202 return data
203
203
204 def _get_diffset(self, source_repo_name, source_repo,
204 def _get_diffset(self, source_repo_name, source_repo,
205 source_ref_id, target_ref_id,
205 source_ref_id, target_ref_id,
206 target_commit, source_commit, diff_limit, fulldiff,
206 target_commit, source_commit, diff_limit, fulldiff,
207 file_limit, display_inline_comments):
207 file_limit, display_inline_comments):
208
208
209 vcs_diff = PullRequestModel().get_diff(
209 vcs_diff = PullRequestModel().get_diff(
210 source_repo, source_ref_id, target_ref_id)
210 source_repo, source_ref_id, target_ref_id)
211
211
212 diff_processor = diffs.DiffProcessor(
212 diff_processor = diffs.DiffProcessor(
213 vcs_diff, format='newdiff', diff_limit=diff_limit,
213 vcs_diff, format='newdiff', diff_limit=diff_limit,
214 file_limit=file_limit, show_full_diff=fulldiff)
214 file_limit=file_limit, show_full_diff=fulldiff)
215
215
216 _parsed = diff_processor.prepare()
216 _parsed = diff_processor.prepare()
217
217
218 def _node_getter(commit):
218 def _node_getter(commit):
219 def get_node(fname):
219 def get_node(fname):
220 try:
220 try:
221 return commit.get_node(fname)
221 return commit.get_node(fname)
222 except NodeDoesNotExistError:
222 except NodeDoesNotExistError:
223 return None
223 return None
224
224
225 return get_node
225 return get_node
226
226
227 diffset = codeblocks.DiffSet(
227 diffset = codeblocks.DiffSet(
228 repo_name=self.db_repo_name,
228 repo_name=self.db_repo_name,
229 source_repo_name=source_repo_name,
229 source_repo_name=source_repo_name,
230 source_node_getter=_node_getter(target_commit),
230 source_node_getter=_node_getter(target_commit),
231 target_node_getter=_node_getter(source_commit),
231 target_node_getter=_node_getter(source_commit),
232 comments=display_inline_comments
232 comments=display_inline_comments
233 )
233 )
234 diffset = diffset.render_patchset(
234 diffset = diffset.render_patchset(
235 _parsed, target_commit.raw_id, source_commit.raw_id)
235 _parsed, target_commit.raw_id, source_commit.raw_id)
236
236
237 return diffset
237 return diffset
238
238
239 @LoginRequired()
239 @LoginRequired()
240 @HasRepoPermissionAnyDecorator(
240 @HasRepoPermissionAnyDecorator(
241 'repository.read', 'repository.write', 'repository.admin')
241 'repository.read', 'repository.write', 'repository.admin')
242 @view_config(
242 @view_config(
243 route_name='pullrequest_show', request_method='GET',
243 route_name='pullrequest_show', request_method='GET',
244 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
244 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
245 def pull_request_show(self):
245 def pull_request_show(self):
246 pull_request_id = self.request.matchdict['pull_request_id']
246 pull_request_id = self.request.matchdict['pull_request_id']
247
247
248 c = self.load_default_context()
248 c = self.load_default_context()
249
249
250 version = self.request.GET.get('version')
250 version = self.request.GET.get('version')
251 from_version = self.request.GET.get('from_version') or version
251 from_version = self.request.GET.get('from_version') or version
252 merge_checks = self.request.GET.get('merge_checks')
252 merge_checks = self.request.GET.get('merge_checks')
253 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
253 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
254
254
255 (pull_request_latest,
255 (pull_request_latest,
256 pull_request_at_ver,
256 pull_request_at_ver,
257 pull_request_display_obj,
257 pull_request_display_obj,
258 at_version) = PullRequestModel().get_pr_version(
258 at_version) = PullRequestModel().get_pr_version(
259 pull_request_id, version=version)
259 pull_request_id, version=version)
260 pr_closed = pull_request_latest.is_closed()
260 pr_closed = pull_request_latest.is_closed()
261
261
262 if pr_closed and (version or from_version):
262 if pr_closed and (version or from_version):
263 # not allow to browse versions
263 # not allow to browse versions
264 raise HTTPFound(h.route_path(
264 raise HTTPFound(h.route_path(
265 'pullrequest_show', repo_name=self.db_repo_name,
265 'pullrequest_show', repo_name=self.db_repo_name,
266 pull_request_id=pull_request_id))
266 pull_request_id=pull_request_id))
267
267
268 versions = pull_request_display_obj.versions()
268 versions = pull_request_display_obj.versions()
269
269
270 c.at_version = at_version
270 c.at_version = at_version
271 c.at_version_num = (at_version
271 c.at_version_num = (at_version
272 if at_version and at_version != 'latest'
272 if at_version and at_version != 'latest'
273 else None)
273 else None)
274 c.at_version_pos = ChangesetComment.get_index_from_version(
274 c.at_version_pos = ChangesetComment.get_index_from_version(
275 c.at_version_num, versions)
275 c.at_version_num, versions)
276
276
277 (prev_pull_request_latest,
277 (prev_pull_request_latest,
278 prev_pull_request_at_ver,
278 prev_pull_request_at_ver,
279 prev_pull_request_display_obj,
279 prev_pull_request_display_obj,
280 prev_at_version) = PullRequestModel().get_pr_version(
280 prev_at_version) = PullRequestModel().get_pr_version(
281 pull_request_id, version=from_version)
281 pull_request_id, version=from_version)
282
282
283 c.from_version = prev_at_version
283 c.from_version = prev_at_version
284 c.from_version_num = (prev_at_version
284 c.from_version_num = (prev_at_version
285 if prev_at_version and prev_at_version != 'latest'
285 if prev_at_version and prev_at_version != 'latest'
286 else None)
286 else None)
287 c.from_version_pos = ChangesetComment.get_index_from_version(
287 c.from_version_pos = ChangesetComment.get_index_from_version(
288 c.from_version_num, versions)
288 c.from_version_num, versions)
289
289
290 # define if we're in COMPARE mode or VIEW at version mode
290 # define if we're in COMPARE mode or VIEW at version mode
291 compare = at_version != prev_at_version
291 compare = at_version != prev_at_version
292
292
293 # pull_requests repo_name we opened it against
293 # pull_requests repo_name we opened it against
294 # ie. target_repo must match
294 # ie. target_repo must match
295 if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
295 if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
296 raise HTTPNotFound()
296 raise HTTPNotFound()
297
297
298 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
298 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
299 pull_request_at_ver)
299 pull_request_at_ver)
300
300
301 c.pull_request = pull_request_display_obj
301 c.pull_request = pull_request_display_obj
302 c.pull_request_latest = pull_request_latest
302 c.pull_request_latest = pull_request_latest
303
303
304 if compare or (at_version and not at_version == 'latest'):
304 if compare or (at_version and not at_version == 'latest'):
305 c.allowed_to_change_status = False
305 c.allowed_to_change_status = False
306 c.allowed_to_update = False
306 c.allowed_to_update = False
307 c.allowed_to_merge = False
307 c.allowed_to_merge = False
308 c.allowed_to_delete = False
308 c.allowed_to_delete = False
309 c.allowed_to_comment = False
309 c.allowed_to_comment = False
310 c.allowed_to_close = False
310 c.allowed_to_close = False
311 else:
311 else:
312 can_change_status = PullRequestModel().check_user_change_status(
312 can_change_status = PullRequestModel().check_user_change_status(
313 pull_request_at_ver, self._rhodecode_user)
313 pull_request_at_ver, self._rhodecode_user)
314 c.allowed_to_change_status = can_change_status and not pr_closed
314 c.allowed_to_change_status = can_change_status and not pr_closed
315
315
316 c.allowed_to_update = PullRequestModel().check_user_update(
316 c.allowed_to_update = PullRequestModel().check_user_update(
317 pull_request_latest, self._rhodecode_user) and not pr_closed
317 pull_request_latest, self._rhodecode_user) and not pr_closed
318 c.allowed_to_merge = PullRequestModel().check_user_merge(
318 c.allowed_to_merge = PullRequestModel().check_user_merge(
319 pull_request_latest, self._rhodecode_user) and not pr_closed
319 pull_request_latest, self._rhodecode_user) and not pr_closed
320 c.allowed_to_delete = PullRequestModel().check_user_delete(
320 c.allowed_to_delete = PullRequestModel().check_user_delete(
321 pull_request_latest, self._rhodecode_user) and not pr_closed
321 pull_request_latest, self._rhodecode_user) and not pr_closed
322 c.allowed_to_comment = not pr_closed
322 c.allowed_to_comment = not pr_closed
323 c.allowed_to_close = c.allowed_to_merge and not pr_closed
323 c.allowed_to_close = c.allowed_to_merge and not pr_closed
324
324
325 c.forbid_adding_reviewers = False
325 c.forbid_adding_reviewers = False
326 c.forbid_author_to_review = False
326 c.forbid_author_to_review = False
327 c.forbid_commit_author_to_review = False
327 c.forbid_commit_author_to_review = False
328
328
329 if pull_request_latest.reviewer_data and \
329 if pull_request_latest.reviewer_data and \
330 'rules' in pull_request_latest.reviewer_data:
330 'rules' in pull_request_latest.reviewer_data:
331 rules = pull_request_latest.reviewer_data['rules'] or {}
331 rules = pull_request_latest.reviewer_data['rules'] or {}
332 try:
332 try:
333 c.forbid_adding_reviewers = rules.get(
333 c.forbid_adding_reviewers = rules.get(
334 'forbid_adding_reviewers')
334 'forbid_adding_reviewers')
335 c.forbid_author_to_review = rules.get(
335 c.forbid_author_to_review = rules.get(
336 'forbid_author_to_review')
336 'forbid_author_to_review')
337 c.forbid_commit_author_to_review = rules.get(
337 c.forbid_commit_author_to_review = rules.get(
338 'forbid_commit_author_to_review')
338 'forbid_commit_author_to_review')
339 except Exception:
339 except Exception:
340 pass
340 pass
341
341
342 # check merge capabilities
342 # check merge capabilities
343 _merge_check = MergeCheck.validate(
343 _merge_check = MergeCheck.validate(
344 pull_request_latest, user=self._rhodecode_user,
344 pull_request_latest, user=self._rhodecode_user,
345 translator=self.request.translate)
345 translator=self.request.translate)
346 c.pr_merge_errors = _merge_check.error_details
346 c.pr_merge_errors = _merge_check.error_details
347 c.pr_merge_possible = not _merge_check.failed
347 c.pr_merge_possible = not _merge_check.failed
348 c.pr_merge_message = _merge_check.merge_msg
348 c.pr_merge_message = _merge_check.merge_msg
349
349
350 c.pr_merge_info = MergeCheck.get_merge_conditions(
350 c.pr_merge_info = MergeCheck.get_merge_conditions(
351 pull_request_latest, translator=self.request.translate)
351 pull_request_latest, translator=self.request.translate)
352
352
353 c.pull_request_review_status = _merge_check.review_status
353 c.pull_request_review_status = _merge_check.review_status
354 if merge_checks:
354 if merge_checks:
355 self.request.override_renderer = \
355 self.request.override_renderer = \
356 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
356 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
357 return self._get_template_context(c)
357 return self._get_template_context(c)
358
358
359 comments_model = CommentsModel()
359 comments_model = CommentsModel()
360
360
361 # reviewers and statuses
361 # reviewers and statuses
362 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
362 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
363 allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
363 allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
364
364
365 # GENERAL COMMENTS with versions #
365 # GENERAL COMMENTS with versions #
366 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
366 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
367 q = q.order_by(ChangesetComment.comment_id.asc())
367 q = q.order_by(ChangesetComment.comment_id.asc())
368 general_comments = q
368 general_comments = q
369
369
370 # pick comments we want to render at current version
370 # pick comments we want to render at current version
371 c.comment_versions = comments_model.aggregate_comments(
371 c.comment_versions = comments_model.aggregate_comments(
372 general_comments, versions, c.at_version_num)
372 general_comments, versions, c.at_version_num)
373 c.comments = c.comment_versions[c.at_version_num]['until']
373 c.comments = c.comment_versions[c.at_version_num]['until']
374
374
375 # INLINE COMMENTS with versions #
375 # INLINE COMMENTS with versions #
376 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
376 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
377 q = q.order_by(ChangesetComment.comment_id.asc())
377 q = q.order_by(ChangesetComment.comment_id.asc())
378 inline_comments = q
378 inline_comments = q
379
379
380 c.inline_versions = comments_model.aggregate_comments(
380 c.inline_versions = comments_model.aggregate_comments(
381 inline_comments, versions, c.at_version_num, inline=True)
381 inline_comments, versions, c.at_version_num, inline=True)
382
382
383 # inject latest version
383 # inject latest version
384 latest_ver = PullRequest.get_pr_display_object(
384 latest_ver = PullRequest.get_pr_display_object(
385 pull_request_latest, pull_request_latest)
385 pull_request_latest, pull_request_latest)
386
386
387 c.versions = versions + [latest_ver]
387 c.versions = versions + [latest_ver]
388
388
389 # if we use version, then do not show later comments
389 # if we use version, then do not show later comments
390 # than current version
390 # than current version
391 display_inline_comments = collections.defaultdict(
391 display_inline_comments = collections.defaultdict(
392 lambda: collections.defaultdict(list))
392 lambda: collections.defaultdict(list))
393 for co in inline_comments:
393 for co in inline_comments:
394 if c.at_version_num:
394 if c.at_version_num:
395 # pick comments that are at least UPTO given version, so we
395 # pick comments that are at least UPTO given version, so we
396 # don't render comments for higher version
396 # don't render comments for higher version
397 should_render = co.pull_request_version_id and \
397 should_render = co.pull_request_version_id and \
398 co.pull_request_version_id <= c.at_version_num
398 co.pull_request_version_id <= c.at_version_num
399 else:
399 else:
400 # showing all, for 'latest'
400 # showing all, for 'latest'
401 should_render = True
401 should_render = True
402
402
403 if should_render:
403 if should_render:
404 display_inline_comments[co.f_path][co.line_no].append(co)
404 display_inline_comments[co.f_path][co.line_no].append(co)
405
405
406 # load diff data into template context, if we use compare mode then
406 # load diff data into template context, if we use compare mode then
407 # diff is calculated based on changes between versions of PR
407 # diff is calculated based on changes between versions of PR
408
408
409 source_repo = pull_request_at_ver.source_repo
409 source_repo = pull_request_at_ver.source_repo
410 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
410 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
411
411
412 target_repo = pull_request_at_ver.target_repo
412 target_repo = pull_request_at_ver.target_repo
413 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
413 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
414
414
415 if compare:
415 if compare:
416 # in compare switch the diff base to latest commit from prev version
416 # in compare switch the diff base to latest commit from prev version
417 target_ref_id = prev_pull_request_display_obj.revisions[0]
417 target_ref_id = prev_pull_request_display_obj.revisions[0]
418
418
419 # despite opening commits for bookmarks/branches/tags, we always
419 # despite opening commits for bookmarks/branches/tags, we always
420 # convert this to rev to prevent changes after bookmark or branch change
420 # convert this to rev to prevent changes after bookmark or branch change
421 c.source_ref_type = 'rev'
421 c.source_ref_type = 'rev'
422 c.source_ref = source_ref_id
422 c.source_ref = source_ref_id
423
423
424 c.target_ref_type = 'rev'
424 c.target_ref_type = 'rev'
425 c.target_ref = target_ref_id
425 c.target_ref = target_ref_id
426
426
427 c.source_repo = source_repo
427 c.source_repo = source_repo
428 c.target_repo = target_repo
428 c.target_repo = target_repo
429
429
430 c.commit_ranges = []
430 c.commit_ranges = []
431 source_commit = EmptyCommit()
431 source_commit = EmptyCommit()
432 target_commit = EmptyCommit()
432 target_commit = EmptyCommit()
433 c.missing_requirements = False
433 c.missing_requirements = False
434
434
435 source_scm = source_repo.scm_instance()
435 source_scm = source_repo.scm_instance()
436 target_scm = target_repo.scm_instance()
436 target_scm = target_repo.scm_instance()
437
437
438 # try first shadow repo, fallback to regular repo
438 # try first shadow repo, fallback to regular repo
439 try:
439 try:
440 commits_source_repo = pull_request_latest.get_shadow_repo()
440 commits_source_repo = pull_request_latest.get_shadow_repo()
441 except Exception:
441 except Exception:
442 log.debug('Failed to get shadow repo', exc_info=True)
442 log.debug('Failed to get shadow repo', exc_info=True)
443 commits_source_repo = source_scm
443 commits_source_repo = source_scm
444
444
445 c.commits_source_repo = commits_source_repo
445 c.commits_source_repo = commits_source_repo
446 commit_cache = {}
446 commit_cache = {}
447 try:
447 try:
448 pre_load = ["author", "branch", "date", "message"]
448 pre_load = ["author", "branch", "date", "message"]
449 show_revs = pull_request_at_ver.revisions
449 show_revs = pull_request_at_ver.revisions
450 for rev in show_revs:
450 for rev in show_revs:
451 comm = commits_source_repo.get_commit(
451 comm = commits_source_repo.get_commit(
452 commit_id=rev, pre_load=pre_load)
452 commit_id=rev, pre_load=pre_load)
453 c.commit_ranges.append(comm)
453 c.commit_ranges.append(comm)
454 commit_cache[comm.raw_id] = comm
454 commit_cache[comm.raw_id] = comm
455
455
456 # Order here matters, we first need to get target, and then
456 # Order here matters, we first need to get target, and then
457 # the source
457 # the source
458 target_commit = commits_source_repo.get_commit(
458 target_commit = commits_source_repo.get_commit(
459 commit_id=safe_str(target_ref_id))
459 commit_id=safe_str(target_ref_id))
460
460
461 source_commit = commits_source_repo.get_commit(
461 source_commit = commits_source_repo.get_commit(
462 commit_id=safe_str(source_ref_id))
462 commit_id=safe_str(source_ref_id))
463
463
464 except CommitDoesNotExistError:
464 except CommitDoesNotExistError:
465 log.warning(
465 log.warning(
466 'Failed to get commit from `{}` repo'.format(
466 'Failed to get commit from `{}` repo'.format(
467 commits_source_repo), exc_info=True)
467 commits_source_repo), exc_info=True)
468 except RepositoryRequirementError:
468 except RepositoryRequirementError:
469 log.warning(
469 log.warning(
470 'Failed to get all required data from repo', exc_info=True)
470 'Failed to get all required data from repo', exc_info=True)
471 c.missing_requirements = True
471 c.missing_requirements = True
472
472
473 c.ancestor = None # set it to None, to hide it from PR view
473 c.ancestor = None # set it to None, to hide it from PR view
474
474
475 try:
475 try:
476 ancestor_id = source_scm.get_common_ancestor(
476 ancestor_id = source_scm.get_common_ancestor(
477 source_commit.raw_id, target_commit.raw_id, target_scm)
477 source_commit.raw_id, target_commit.raw_id, target_scm)
478 c.ancestor_commit = source_scm.get_commit(ancestor_id)
478 c.ancestor_commit = source_scm.get_commit(ancestor_id)
479 except Exception:
479 except Exception:
480 c.ancestor_commit = None
480 c.ancestor_commit = None
481
481
482 c.statuses = source_repo.statuses(
482 c.statuses = source_repo.statuses(
483 [x.raw_id for x in c.commit_ranges])
483 [x.raw_id for x in c.commit_ranges])
484
484
485 # auto collapse if we have more than limit
485 # auto collapse if we have more than limit
486 collapse_limit = diffs.DiffProcessor._collapse_commits_over
486 collapse_limit = diffs.DiffProcessor._collapse_commits_over
487 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
487 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
488 c.compare_mode = compare
488 c.compare_mode = compare
489
489
490 # diff_limit is the old behavior, will cut off the whole diff
490 # diff_limit is the old behavior, will cut off the whole diff
491 # if the limit is applied otherwise will just hide the
491 # if the limit is applied otherwise will just hide the
492 # big files from the front-end
492 # big files from the front-end
493 diff_limit = c.visual.cut_off_limit_diff
493 diff_limit = c.visual.cut_off_limit_diff
494 file_limit = c.visual.cut_off_limit_file
494 file_limit = c.visual.cut_off_limit_file
495
495
496 c.missing_commits = False
496 c.missing_commits = False
497 if (c.missing_requirements
497 if (c.missing_requirements
498 or isinstance(source_commit, EmptyCommit)
498 or isinstance(source_commit, EmptyCommit)
499 or source_commit == target_commit):
499 or source_commit == target_commit):
500
500
501 c.missing_commits = True
501 c.missing_commits = True
502 else:
502 else:
503
503
504 c.diffset = self._get_diffset(
504 c.diffset = self._get_diffset(
505 c.source_repo.repo_name, commits_source_repo,
505 c.source_repo.repo_name, commits_source_repo,
506 source_ref_id, target_ref_id,
506 source_ref_id, target_ref_id,
507 target_commit, source_commit,
507 target_commit, source_commit,
508 diff_limit, c.fulldiff, file_limit, display_inline_comments)
508 diff_limit, c.fulldiff, file_limit, display_inline_comments)
509
509
510 c.limited_diff = c.diffset.limited_diff
510 c.limited_diff = c.diffset.limited_diff
511
511
512 # calculate removed files that are bound to comments
512 # calculate removed files that are bound to comments
513 comment_deleted_files = [
513 comment_deleted_files = [
514 fname for fname in display_inline_comments
514 fname for fname in display_inline_comments
515 if fname not in c.diffset.file_stats]
515 if fname not in c.diffset.file_stats]
516
516
517 c.deleted_files_comments = collections.defaultdict(dict)
517 c.deleted_files_comments = collections.defaultdict(dict)
518 for fname, per_line_comments in display_inline_comments.items():
518 for fname, per_line_comments in display_inline_comments.items():
519 if fname in comment_deleted_files:
519 if fname in comment_deleted_files:
520 c.deleted_files_comments[fname]['stats'] = 0
520 c.deleted_files_comments[fname]['stats'] = 0
521 c.deleted_files_comments[fname]['comments'] = list()
521 c.deleted_files_comments[fname]['comments'] = list()
522 for lno, comments in per_line_comments.items():
522 for lno, comments in per_line_comments.items():
523 c.deleted_files_comments[fname]['comments'].extend(
523 c.deleted_files_comments[fname]['comments'].extend(
524 comments)
524 comments)
525
525
526 # this is a hack to properly display links, when creating PR, the
526 # this is a hack to properly display links, when creating PR, the
527 # compare view and others uses different notation, and
527 # compare view and others uses different notation, and
528 # compare_commits.mako renders links based on the target_repo.
528 # compare_commits.mako renders links based on the target_repo.
529 # We need to swap that here to generate it properly on the html side
529 # We need to swap that here to generate it properly on the html side
530 c.target_repo = c.source_repo
530 c.target_repo = c.source_repo
531
531
532 c.commit_statuses = ChangesetStatus.STATUSES
532 c.commit_statuses = ChangesetStatus.STATUSES
533
533
534 c.show_version_changes = not pr_closed
534 c.show_version_changes = not pr_closed
535 if c.show_version_changes:
535 if c.show_version_changes:
536 cur_obj = pull_request_at_ver
536 cur_obj = pull_request_at_ver
537 prev_obj = prev_pull_request_at_ver
537 prev_obj = prev_pull_request_at_ver
538
538
539 old_commit_ids = prev_obj.revisions
539 old_commit_ids = prev_obj.revisions
540 new_commit_ids = cur_obj.revisions
540 new_commit_ids = cur_obj.revisions
541 commit_changes = PullRequestModel()._calculate_commit_id_changes(
541 commit_changes = PullRequestModel()._calculate_commit_id_changes(
542 old_commit_ids, new_commit_ids)
542 old_commit_ids, new_commit_ids)
543 c.commit_changes_summary = commit_changes
543 c.commit_changes_summary = commit_changes
544
544
545 # calculate the diff for commits between versions
545 # calculate the diff for commits between versions
546 c.commit_changes = []
546 c.commit_changes = []
547 mark = lambda cs, fw: list(
547 mark = lambda cs, fw: list(
548 h.itertools.izip_longest([], cs, fillvalue=fw))
548 h.itertools.izip_longest([], cs, fillvalue=fw))
549 for c_type, raw_id in mark(commit_changes.added, 'a') \
549 for c_type, raw_id in mark(commit_changes.added, 'a') \
550 + mark(commit_changes.removed, 'r') \
550 + mark(commit_changes.removed, 'r') \
551 + mark(commit_changes.common, 'c'):
551 + mark(commit_changes.common, 'c'):
552
552
553 if raw_id in commit_cache:
553 if raw_id in commit_cache:
554 commit = commit_cache[raw_id]
554 commit = commit_cache[raw_id]
555 else:
555 else:
556 try:
556 try:
557 commit = commits_source_repo.get_commit(raw_id)
557 commit = commits_source_repo.get_commit(raw_id)
558 except CommitDoesNotExistError:
558 except CommitDoesNotExistError:
559 # in case we fail extracting still use "dummy" commit
559 # in case we fail extracting still use "dummy" commit
560 # for display in commit diff
560 # for display in commit diff
561 commit = h.AttributeDict(
561 commit = h.AttributeDict(
562 {'raw_id': raw_id,
562 {'raw_id': raw_id,
563 'message': 'EMPTY or MISSING COMMIT'})
563 'message': 'EMPTY or MISSING COMMIT'})
564 c.commit_changes.append([c_type, commit])
564 c.commit_changes.append([c_type, commit])
565
565
566 # current user review statuses for each version
566 # current user review statuses for each version
567 c.review_versions = {}
567 c.review_versions = {}
568 if self._rhodecode_user.user_id in allowed_reviewers:
568 if self._rhodecode_user.user_id in allowed_reviewers:
569 for co in general_comments:
569 for co in general_comments:
570 if co.author.user_id == self._rhodecode_user.user_id:
570 if co.author.user_id == self._rhodecode_user.user_id:
571 # each comment has a status change
571 # each comment has a status change
572 status = co.status_change
572 status = co.status_change
573 if status:
573 if status:
574 _ver_pr = status[0].comment.pull_request_version_id
574 _ver_pr = status[0].comment.pull_request_version_id
575 c.review_versions[_ver_pr] = status[0]
575 c.review_versions[_ver_pr] = status[0]
576
576
577 return self._get_template_context(c)
577 return self._get_template_context(c)
578
578
579 def assure_not_empty_repo(self):
579 def assure_not_empty_repo(self):
580 _ = self.request.translate
580 _ = self.request.translate
581
581
582 try:
582 try:
583 self.db_repo.scm_instance().get_commit()
583 self.db_repo.scm_instance().get_commit()
584 except EmptyRepositoryError:
584 except EmptyRepositoryError:
585 h.flash(h.literal(_('There are no commits yet')),
585 h.flash(h.literal(_('There are no commits yet')),
586 category='warning')
586 category='warning')
587 raise HTTPFound(
587 raise HTTPFound(
588 h.route_path('repo_summary', repo_name=self.db_repo.repo_name))
588 h.route_path('repo_summary', repo_name=self.db_repo.repo_name))
589
589
590 @LoginRequired()
590 @LoginRequired()
591 @NotAnonymous()
591 @NotAnonymous()
592 @HasRepoPermissionAnyDecorator(
592 @HasRepoPermissionAnyDecorator(
593 'repository.read', 'repository.write', 'repository.admin')
593 'repository.read', 'repository.write', 'repository.admin')
594 @view_config(
594 @view_config(
595 route_name='pullrequest_new', request_method='GET',
595 route_name='pullrequest_new', request_method='GET',
596 renderer='rhodecode:templates/pullrequests/pullrequest.mako')
596 renderer='rhodecode:templates/pullrequests/pullrequest.mako')
597 def pull_request_new(self):
597 def pull_request_new(self):
598 _ = self.request.translate
598 _ = self.request.translate
599 c = self.load_default_context()
599 c = self.load_default_context()
600
600
601 self.assure_not_empty_repo()
601 self.assure_not_empty_repo()
602 source_repo = self.db_repo
602 source_repo = self.db_repo
603
603
604 commit_id = self.request.GET.get('commit')
604 commit_id = self.request.GET.get('commit')
605 branch_ref = self.request.GET.get('branch')
605 branch_ref = self.request.GET.get('branch')
606 bookmark_ref = self.request.GET.get('bookmark')
606 bookmark_ref = self.request.GET.get('bookmark')
607
607
608 try:
608 try:
609 source_repo_data = PullRequestModel().generate_repo_data(
609 source_repo_data = PullRequestModel().generate_repo_data(
610 source_repo, commit_id=commit_id,
610 source_repo, commit_id=commit_id,
611 branch=branch_ref, bookmark=bookmark_ref, translator=self.request.translate)
611 branch=branch_ref, bookmark=bookmark_ref, translator=self.request.translate)
612 except CommitDoesNotExistError as e:
612 except CommitDoesNotExistError as e:
613 log.exception(e)
613 log.exception(e)
614 h.flash(_('Commit does not exist'), 'error')
614 h.flash(_('Commit does not exist'), 'error')
615 raise HTTPFound(
615 raise HTTPFound(
616 h.route_path('pullrequest_new', repo_name=source_repo.repo_name))
616 h.route_path('pullrequest_new', repo_name=source_repo.repo_name))
617
617
618 default_target_repo = source_repo
618 default_target_repo = source_repo
619
619
620 if source_repo.parent:
620 if source_repo.parent:
621 parent_vcs_obj = source_repo.parent.scm_instance()
621 parent_vcs_obj = source_repo.parent.scm_instance()
622 if parent_vcs_obj and not parent_vcs_obj.is_empty():
622 if parent_vcs_obj and not parent_vcs_obj.is_empty():
623 # change default if we have a parent repo
623 # change default if we have a parent repo
624 default_target_repo = source_repo.parent
624 default_target_repo = source_repo.parent
625
625
626 target_repo_data = PullRequestModel().generate_repo_data(
626 target_repo_data = PullRequestModel().generate_repo_data(
627 default_target_repo, translator=self.request.translate)
627 default_target_repo, translator=self.request.translate)
628
628
629 selected_source_ref = source_repo_data['refs']['selected_ref']
629 selected_source_ref = source_repo_data['refs']['selected_ref']
630
630
631 title_source_ref = selected_source_ref.split(':', 2)[1]
631 title_source_ref = selected_source_ref.split(':', 2)[1]
632 c.default_title = PullRequestModel().generate_pullrequest_title(
632 c.default_title = PullRequestModel().generate_pullrequest_title(
633 source=source_repo.repo_name,
633 source=source_repo.repo_name,
634 source_ref=title_source_ref,
634 source_ref=title_source_ref,
635 target=default_target_repo.repo_name
635 target=default_target_repo.repo_name
636 )
636 )
637
637
638 c.default_repo_data = {
638 c.default_repo_data = {
639 'source_repo_name': source_repo.repo_name,
639 'source_repo_name': source_repo.repo_name,
640 'source_refs_json': json.dumps(source_repo_data),
640 'source_refs_json': json.dumps(source_repo_data),
641 'target_repo_name': default_target_repo.repo_name,
641 'target_repo_name': default_target_repo.repo_name,
642 'target_refs_json': json.dumps(target_repo_data),
642 'target_refs_json': json.dumps(target_repo_data),
643 }
643 }
644 c.default_source_ref = selected_source_ref
644 c.default_source_ref = selected_source_ref
645
645
646 return self._get_template_context(c)
646 return self._get_template_context(c)
647
647
648 @LoginRequired()
648 @LoginRequired()
649 @NotAnonymous()
649 @NotAnonymous()
650 @HasRepoPermissionAnyDecorator(
650 @HasRepoPermissionAnyDecorator(
651 'repository.read', 'repository.write', 'repository.admin')
651 'repository.read', 'repository.write', 'repository.admin')
652 @view_config(
652 @view_config(
653 route_name='pullrequest_repo_refs', request_method='GET',
653 route_name='pullrequest_repo_refs', request_method='GET',
654 renderer='json_ext', xhr=True)
654 renderer='json_ext', xhr=True)
655 def pull_request_repo_refs(self):
655 def pull_request_repo_refs(self):
656 self.load_default_context()
656 self.load_default_context()
657 target_repo_name = self.request.matchdict['target_repo_name']
657 target_repo_name = self.request.matchdict['target_repo_name']
658 repo = Repository.get_by_repo_name(target_repo_name)
658 repo = Repository.get_by_repo_name(target_repo_name)
659 if not repo:
659 if not repo:
660 raise HTTPNotFound()
660 raise HTTPNotFound()
661
661
662 target_perm = HasRepoPermissionAny(
662 target_perm = HasRepoPermissionAny(
663 'repository.read', 'repository.write', 'repository.admin')(
663 'repository.read', 'repository.write', 'repository.admin')(
664 target_repo_name)
664 target_repo_name)
665 if not target_perm:
665 if not target_perm:
666 raise HTTPNotFound()
666 raise HTTPNotFound()
667
667
668 return PullRequestModel().generate_repo_data(
668 return PullRequestModel().generate_repo_data(
669 repo, translator=self.request.translate)
669 repo, translator=self.request.translate)
670
670
671 @LoginRequired()
671 @LoginRequired()
672 @NotAnonymous()
672 @NotAnonymous()
673 @HasRepoPermissionAnyDecorator(
673 @HasRepoPermissionAnyDecorator(
674 'repository.read', 'repository.write', 'repository.admin')
674 'repository.read', 'repository.write', 'repository.admin')
675 @view_config(
675 @view_config(
676 route_name='pullrequest_repo_destinations', request_method='GET',
676 route_name='pullrequest_repo_destinations', request_method='GET',
677 renderer='json_ext', xhr=True)
677 renderer='json_ext', xhr=True)
678 def pull_request_repo_destinations(self):
678 def pull_request_repo_destinations(self):
679 _ = self.request.translate
679 _ = self.request.translate
680 filter_query = self.request.GET.get('query')
680 filter_query = self.request.GET.get('query')
681
681
682 query = Repository.query() \
682 query = Repository.query() \
683 .order_by(func.length(Repository.repo_name)) \
683 .order_by(func.length(Repository.repo_name)) \
684 .filter(
684 .filter(
685 or_(Repository.repo_name == self.db_repo.repo_name,
685 or_(Repository.repo_name == self.db_repo.repo_name,
686 Repository.fork_id == self.db_repo.repo_id))
686 Repository.fork_id == self.db_repo.repo_id))
687
687
688 if filter_query:
688 if filter_query:
689 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
689 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
690 query = query.filter(
690 query = query.filter(
691 Repository.repo_name.ilike(ilike_expression))
691 Repository.repo_name.ilike(ilike_expression))
692
692
693 add_parent = False
693 add_parent = False
694 if self.db_repo.parent:
694 if self.db_repo.parent:
695 if filter_query in self.db_repo.parent.repo_name:
695 if filter_query in self.db_repo.parent.repo_name:
696 parent_vcs_obj = self.db_repo.parent.scm_instance()
696 parent_vcs_obj = self.db_repo.parent.scm_instance()
697 if parent_vcs_obj and not parent_vcs_obj.is_empty():
697 if parent_vcs_obj and not parent_vcs_obj.is_empty():
698 add_parent = True
698 add_parent = True
699
699
700 limit = 20 - 1 if add_parent else 20
700 limit = 20 - 1 if add_parent else 20
701 all_repos = query.limit(limit).all()
701 all_repos = query.limit(limit).all()
702 if add_parent:
702 if add_parent:
703 all_repos += [self.db_repo.parent]
703 all_repos += [self.db_repo.parent]
704
704
705 repos = []
705 repos = []
706 for obj in ScmModel().get_repos(all_repos):
706 for obj in ScmModel().get_repos(all_repos):
707 repos.append({
707 repos.append({
708 'id': obj['name'],
708 'id': obj['name'],
709 'text': obj['name'],
709 'text': obj['name'],
710 'type': 'repo',
710 'type': 'repo',
711 'obj': obj['dbrepo']
711 'obj': obj['dbrepo']
712 })
712 })
713
713
714 data = {
714 data = {
715 'more': False,
715 'more': False,
716 'results': [{
716 'results': [{
717 'text': _('Repositories'),
717 'text': _('Repositories'),
718 'children': repos
718 'children': repos
719 }] if repos else []
719 }] if repos else []
720 }
720 }
721 return data
721 return data
722
722
723 @LoginRequired()
723 @LoginRequired()
724 @NotAnonymous()
724 @NotAnonymous()
725 @HasRepoPermissionAnyDecorator(
725 @HasRepoPermissionAnyDecorator(
726 'repository.read', 'repository.write', 'repository.admin')
726 'repository.read', 'repository.write', 'repository.admin')
727 @CSRFRequired()
727 @CSRFRequired()
728 @view_config(
728 @view_config(
729 route_name='pullrequest_create', request_method='POST',
729 route_name='pullrequest_create', request_method='POST',
730 renderer=None)
730 renderer=None)
731 def pull_request_create(self):
731 def pull_request_create(self):
732 _ = self.request.translate
732 _ = self.request.translate
733 self.assure_not_empty_repo()
733 self.assure_not_empty_repo()
734 self.load_default_context()
734 self.load_default_context()
735
735
736 controls = peppercorn.parse(self.request.POST.items())
736 controls = peppercorn.parse(self.request.POST.items())
737
737
738 try:
738 try:
739 form = PullRequestForm(
739 form = PullRequestForm(
740 self.request.translate, self.db_repo.repo_id)()
740 self.request.translate, self.db_repo.repo_id)()
741 _form = form.to_python(controls)
741 _form = form.to_python(controls)
742 except formencode.Invalid as errors:
742 except formencode.Invalid as errors:
743 if errors.error_dict.get('revisions'):
743 if errors.error_dict.get('revisions'):
744 msg = 'Revisions: %s' % errors.error_dict['revisions']
744 msg = 'Revisions: %s' % errors.error_dict['revisions']
745 elif errors.error_dict.get('pullrequest_title'):
745 elif errors.error_dict.get('pullrequest_title'):
746 msg = _('Pull request requires a title with min. 3 chars')
746 msg = errors.error_dict.get('pullrequest_title')
747 else:
747 else:
748 msg = _('Error creating pull request: {}').format(errors)
748 msg = _('Error creating pull request: {}').format(errors)
749 log.exception(msg)
749 log.exception(msg)
750 h.flash(msg, 'error')
750 h.flash(msg, 'error')
751
751
752 # would rather just go back to form ...
752 # would rather just go back to form ...
753 raise HTTPFound(
753 raise HTTPFound(
754 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
754 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
755
755
756 source_repo = _form['source_repo']
756 source_repo = _form['source_repo']
757 source_ref = _form['source_ref']
757 source_ref = _form['source_ref']
758 target_repo = _form['target_repo']
758 target_repo = _form['target_repo']
759 target_ref = _form['target_ref']
759 target_ref = _form['target_ref']
760 commit_ids = _form['revisions'][::-1]
760 commit_ids = _form['revisions'][::-1]
761
761
762 # find the ancestor for this pr
762 # find the ancestor for this pr
763 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
763 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
764 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
764 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
765
765
766 # re-check permissions again here
766 # re-check permissions again here
767 # source_repo we must have read permissions
767 # source_repo we must have read permissions
768
768
769 source_perm = HasRepoPermissionAny(
769 source_perm = HasRepoPermissionAny(
770 'repository.read',
770 'repository.read',
771 'repository.write', 'repository.admin')(source_db_repo.repo_name)
771 'repository.write', 'repository.admin')(source_db_repo.repo_name)
772 if not source_perm:
772 if not source_perm:
773 msg = _('Not Enough permissions to source repo `{}`.'.format(
773 msg = _('Not Enough permissions to source repo `{}`.'.format(
774 source_db_repo.repo_name))
774 source_db_repo.repo_name))
775 h.flash(msg, category='error')
775 h.flash(msg, category='error')
776 # copy the args back to redirect
776 # copy the args back to redirect
777 org_query = self.request.GET.mixed()
777 org_query = self.request.GET.mixed()
778 raise HTTPFound(
778 raise HTTPFound(
779 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
779 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
780 _query=org_query))
780 _query=org_query))
781
781
782 # target repo we must have read permissions, and also later on
782 # target repo we must have read permissions, and also later on
783 # we want to check branch permissions here
783 # we want to check branch permissions here
784 target_perm = HasRepoPermissionAny(
784 target_perm = HasRepoPermissionAny(
785 'repository.read',
785 'repository.read',
786 'repository.write', 'repository.admin')(target_db_repo.repo_name)
786 'repository.write', 'repository.admin')(target_db_repo.repo_name)
787 if not target_perm:
787 if not target_perm:
788 msg = _('Not Enough permissions to target repo `{}`.'.format(
788 msg = _('Not Enough permissions to target repo `{}`.'.format(
789 target_db_repo.repo_name))
789 target_db_repo.repo_name))
790 h.flash(msg, category='error')
790 h.flash(msg, category='error')
791 # copy the args back to redirect
791 # copy the args back to redirect
792 org_query = self.request.GET.mixed()
792 org_query = self.request.GET.mixed()
793 raise HTTPFound(
793 raise HTTPFound(
794 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
794 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
795 _query=org_query))
795 _query=org_query))
796
796
797 source_scm = source_db_repo.scm_instance()
797 source_scm = source_db_repo.scm_instance()
798 target_scm = target_db_repo.scm_instance()
798 target_scm = target_db_repo.scm_instance()
799
799
800 source_commit = source_scm.get_commit(source_ref.split(':')[-1])
800 source_commit = source_scm.get_commit(source_ref.split(':')[-1])
801 target_commit = target_scm.get_commit(target_ref.split(':')[-1])
801 target_commit = target_scm.get_commit(target_ref.split(':')[-1])
802
802
803 ancestor = source_scm.get_common_ancestor(
803 ancestor = source_scm.get_common_ancestor(
804 source_commit.raw_id, target_commit.raw_id, target_scm)
804 source_commit.raw_id, target_commit.raw_id, target_scm)
805
805
806 target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
806 target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
807 target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
807 target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
808
808
809 pullrequest_title = _form['pullrequest_title']
809 pullrequest_title = _form['pullrequest_title']
810 title_source_ref = source_ref.split(':', 2)[1]
810 title_source_ref = source_ref.split(':', 2)[1]
811 if not pullrequest_title:
811 if not pullrequest_title:
812 pullrequest_title = PullRequestModel().generate_pullrequest_title(
812 pullrequest_title = PullRequestModel().generate_pullrequest_title(
813 source=source_repo,
813 source=source_repo,
814 source_ref=title_source_ref,
814 source_ref=title_source_ref,
815 target=target_repo
815 target=target_repo
816 )
816 )
817
817
818 description = _form['pullrequest_desc']
818 description = _form['pullrequest_desc']
819
819
820 get_default_reviewers_data, validate_default_reviewers = \
820 get_default_reviewers_data, validate_default_reviewers = \
821 PullRequestModel().get_reviewer_functions()
821 PullRequestModel().get_reviewer_functions()
822
822
823 # recalculate reviewers logic, to make sure we can validate this
823 # recalculate reviewers logic, to make sure we can validate this
824 reviewer_rules = get_default_reviewers_data(
824 reviewer_rules = get_default_reviewers_data(
825 self._rhodecode_db_user, source_db_repo,
825 self._rhodecode_db_user, source_db_repo,
826 source_commit, target_db_repo, target_commit)
826 source_commit, target_db_repo, target_commit)
827
827
828 given_reviewers = _form['review_members']
828 given_reviewers = _form['review_members']
829 reviewers = validate_default_reviewers(given_reviewers, reviewer_rules)
829 reviewers = validate_default_reviewers(given_reviewers, reviewer_rules)
830
830
831 try:
831 try:
832 pull_request = PullRequestModel().create(
832 pull_request = PullRequestModel().create(
833 self._rhodecode_user.user_id, source_repo, source_ref,
833 self._rhodecode_user.user_id, source_repo, source_ref,
834 target_repo, target_ref, commit_ids, reviewers,
834 target_repo, target_ref, commit_ids, reviewers,
835 pullrequest_title, description, reviewer_rules
835 pullrequest_title, description, reviewer_rules
836 )
836 )
837 Session().commit()
837 Session().commit()
838
838
839 h.flash(_('Successfully opened new pull request'),
839 h.flash(_('Successfully opened new pull request'),
840 category='success')
840 category='success')
841 except Exception:
841 except Exception:
842 msg = _('Error occurred during creation of this pull request.')
842 msg = _('Error occurred during creation of this pull request.')
843 log.exception(msg)
843 log.exception(msg)
844 h.flash(msg, category='error')
844 h.flash(msg, category='error')
845
845
846 # copy the args back to redirect
846 # copy the args back to redirect
847 org_query = self.request.GET.mixed()
847 org_query = self.request.GET.mixed()
848 raise HTTPFound(
848 raise HTTPFound(
849 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
849 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
850 _query=org_query))
850 _query=org_query))
851
851
852 raise HTTPFound(
852 raise HTTPFound(
853 h.route_path('pullrequest_show', repo_name=target_repo,
853 h.route_path('pullrequest_show', repo_name=target_repo,
854 pull_request_id=pull_request.pull_request_id))
854 pull_request_id=pull_request.pull_request_id))
855
855
856 @LoginRequired()
856 @LoginRequired()
857 @NotAnonymous()
857 @NotAnonymous()
858 @HasRepoPermissionAnyDecorator(
858 @HasRepoPermissionAnyDecorator(
859 'repository.read', 'repository.write', 'repository.admin')
859 'repository.read', 'repository.write', 'repository.admin')
860 @CSRFRequired()
860 @CSRFRequired()
861 @view_config(
861 @view_config(
862 route_name='pullrequest_update', request_method='POST',
862 route_name='pullrequest_update', request_method='POST',
863 renderer='json_ext')
863 renderer='json_ext')
864 def pull_request_update(self):
864 def pull_request_update(self):
865 pull_request = PullRequest.get_or_404(
865 pull_request = PullRequest.get_or_404(
866 self.request.matchdict['pull_request_id'])
866 self.request.matchdict['pull_request_id'])
867 _ = self.request.translate
867 _ = self.request.translate
868
868
869 self.load_default_context()
869 self.load_default_context()
870
870
871 if pull_request.is_closed():
871 if pull_request.is_closed():
872 log.debug('update: forbidden because pull request is closed')
872 log.debug('update: forbidden because pull request is closed')
873 msg = _(u'Cannot update closed pull requests.')
873 msg = _(u'Cannot update closed pull requests.')
874 h.flash(msg, category='error')
874 h.flash(msg, category='error')
875 return True
875 return True
876
876
877 # only owner or admin can update it
877 # only owner or admin can update it
878 allowed_to_update = PullRequestModel().check_user_update(
878 allowed_to_update = PullRequestModel().check_user_update(
879 pull_request, self._rhodecode_user)
879 pull_request, self._rhodecode_user)
880 if allowed_to_update:
880 if allowed_to_update:
881 controls = peppercorn.parse(self.request.POST.items())
881 controls = peppercorn.parse(self.request.POST.items())
882
882
883 if 'review_members' in controls:
883 if 'review_members' in controls:
884 self._update_reviewers(
884 self._update_reviewers(
885 pull_request, controls['review_members'],
885 pull_request, controls['review_members'],
886 pull_request.reviewer_data)
886 pull_request.reviewer_data)
887 elif str2bool(self.request.POST.get('update_commits', 'false')):
887 elif str2bool(self.request.POST.get('update_commits', 'false')):
888 self._update_commits(pull_request)
888 self._update_commits(pull_request)
889 elif str2bool(self.request.POST.get('edit_pull_request', 'false')):
889 elif str2bool(self.request.POST.get('edit_pull_request', 'false')):
890 self._edit_pull_request(pull_request)
890 self._edit_pull_request(pull_request)
891 else:
891 else:
892 raise HTTPBadRequest()
892 raise HTTPBadRequest()
893 return True
893 return True
894 raise HTTPForbidden()
894 raise HTTPForbidden()
895
895
896 def _edit_pull_request(self, pull_request):
896 def _edit_pull_request(self, pull_request):
897 _ = self.request.translate
897 _ = self.request.translate
898 try:
898 try:
899 PullRequestModel().edit(
899 PullRequestModel().edit(
900 pull_request, self.request.POST.get('title'),
900 pull_request, self.request.POST.get('title'),
901 self.request.POST.get('description'), self._rhodecode_user)
901 self.request.POST.get('description'), self._rhodecode_user)
902 except ValueError:
902 except ValueError:
903 msg = _(u'Cannot update closed pull requests.')
903 msg = _(u'Cannot update closed pull requests.')
904 h.flash(msg, category='error')
904 h.flash(msg, category='error')
905 return
905 return
906 else:
906 else:
907 Session().commit()
907 Session().commit()
908
908
909 msg = _(u'Pull request title & description updated.')
909 msg = _(u'Pull request title & description updated.')
910 h.flash(msg, category='success')
910 h.flash(msg, category='success')
911 return
911 return
912
912
913 def _update_commits(self, pull_request):
913 def _update_commits(self, pull_request):
914 _ = self.request.translate
914 _ = self.request.translate
915 resp = PullRequestModel().update_commits(pull_request)
915 resp = PullRequestModel().update_commits(pull_request)
916
916
917 if resp.executed:
917 if resp.executed:
918
918
919 if resp.target_changed and resp.source_changed:
919 if resp.target_changed and resp.source_changed:
920 changed = 'target and source repositories'
920 changed = 'target and source repositories'
921 elif resp.target_changed and not resp.source_changed:
921 elif resp.target_changed and not resp.source_changed:
922 changed = 'target repository'
922 changed = 'target repository'
923 elif not resp.target_changed and resp.source_changed:
923 elif not resp.target_changed and resp.source_changed:
924 changed = 'source repository'
924 changed = 'source repository'
925 else:
925 else:
926 changed = 'nothing'
926 changed = 'nothing'
927
927
928 msg = _(
928 msg = _(
929 u'Pull request updated to "{source_commit_id}" with '
929 u'Pull request updated to "{source_commit_id}" with '
930 u'{count_added} added, {count_removed} removed commits. '
930 u'{count_added} added, {count_removed} removed commits. '
931 u'Source of changes: {change_source}')
931 u'Source of changes: {change_source}')
932 msg = msg.format(
932 msg = msg.format(
933 source_commit_id=pull_request.source_ref_parts.commit_id,
933 source_commit_id=pull_request.source_ref_parts.commit_id,
934 count_added=len(resp.changes.added),
934 count_added=len(resp.changes.added),
935 count_removed=len(resp.changes.removed),
935 count_removed=len(resp.changes.removed),
936 change_source=changed)
936 change_source=changed)
937 h.flash(msg, category='success')
937 h.flash(msg, category='success')
938
938
939 channel = '/repo${}$/pr/{}'.format(
939 channel = '/repo${}$/pr/{}'.format(
940 pull_request.target_repo.repo_name,
940 pull_request.target_repo.repo_name,
941 pull_request.pull_request_id)
941 pull_request.pull_request_id)
942 message = msg + (
942 message = msg + (
943 ' - <a onclick="window.location.reload()">'
943 ' - <a onclick="window.location.reload()">'
944 '<strong>{}</strong></a>'.format(_('Reload page')))
944 '<strong>{}</strong></a>'.format(_('Reload page')))
945 channelstream.post_message(
945 channelstream.post_message(
946 channel, message, self._rhodecode_user.username,
946 channel, message, self._rhodecode_user.username,
947 registry=self.request.registry)
947 registry=self.request.registry)
948 else:
948 else:
949 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
949 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
950 warning_reasons = [
950 warning_reasons = [
951 UpdateFailureReason.NO_CHANGE,
951 UpdateFailureReason.NO_CHANGE,
952 UpdateFailureReason.WRONG_REF_TYPE,
952 UpdateFailureReason.WRONG_REF_TYPE,
953 ]
953 ]
954 category = 'warning' if resp.reason in warning_reasons else 'error'
954 category = 'warning' if resp.reason in warning_reasons else 'error'
955 h.flash(msg, category=category)
955 h.flash(msg, category=category)
956
956
957 @LoginRequired()
957 @LoginRequired()
958 @NotAnonymous()
958 @NotAnonymous()
959 @HasRepoPermissionAnyDecorator(
959 @HasRepoPermissionAnyDecorator(
960 'repository.read', 'repository.write', 'repository.admin')
960 'repository.read', 'repository.write', 'repository.admin')
961 @CSRFRequired()
961 @CSRFRequired()
962 @view_config(
962 @view_config(
963 route_name='pullrequest_merge', request_method='POST',
963 route_name='pullrequest_merge', request_method='POST',
964 renderer='json_ext')
964 renderer='json_ext')
965 def pull_request_merge(self):
965 def pull_request_merge(self):
966 """
966 """
967 Merge will perform a server-side merge of the specified
967 Merge will perform a server-side merge of the specified
968 pull request, if the pull request is approved and mergeable.
968 pull request, if the pull request is approved and mergeable.
969 After successful merging, the pull request is automatically
969 After successful merging, the pull request is automatically
970 closed, with a relevant comment.
970 closed, with a relevant comment.
971 """
971 """
972 pull_request = PullRequest.get_or_404(
972 pull_request = PullRequest.get_or_404(
973 self.request.matchdict['pull_request_id'])
973 self.request.matchdict['pull_request_id'])
974
974
975 self.load_default_context()
975 self.load_default_context()
976 check = MergeCheck.validate(pull_request, self._rhodecode_db_user,
976 check = MergeCheck.validate(pull_request, self._rhodecode_db_user,
977 translator=self.request.translate)
977 translator=self.request.translate)
978 merge_possible = not check.failed
978 merge_possible = not check.failed
979
979
980 for err_type, error_msg in check.errors:
980 for err_type, error_msg in check.errors:
981 h.flash(error_msg, category=err_type)
981 h.flash(error_msg, category=err_type)
982
982
983 if merge_possible:
983 if merge_possible:
984 log.debug("Pre-conditions checked, trying to merge.")
984 log.debug("Pre-conditions checked, trying to merge.")
985 extras = vcs_operation_context(
985 extras = vcs_operation_context(
986 self.request.environ, repo_name=pull_request.target_repo.repo_name,
986 self.request.environ, repo_name=pull_request.target_repo.repo_name,
987 username=self._rhodecode_db_user.username, action='push',
987 username=self._rhodecode_db_user.username, action='push',
988 scm=pull_request.target_repo.repo_type)
988 scm=pull_request.target_repo.repo_type)
989 self._merge_pull_request(
989 self._merge_pull_request(
990 pull_request, self._rhodecode_db_user, extras)
990 pull_request, self._rhodecode_db_user, extras)
991 else:
991 else:
992 log.debug("Pre-conditions failed, NOT merging.")
992 log.debug("Pre-conditions failed, NOT merging.")
993
993
994 raise HTTPFound(
994 raise HTTPFound(
995 h.route_path('pullrequest_show',
995 h.route_path('pullrequest_show',
996 repo_name=pull_request.target_repo.repo_name,
996 repo_name=pull_request.target_repo.repo_name,
997 pull_request_id=pull_request.pull_request_id))
997 pull_request_id=pull_request.pull_request_id))
998
998
999 def _merge_pull_request(self, pull_request, user, extras):
999 def _merge_pull_request(self, pull_request, user, extras):
1000 _ = self.request.translate
1000 _ = self.request.translate
1001 merge_resp = PullRequestModel().merge(pull_request, user, extras=extras)
1001 merge_resp = PullRequestModel().merge(pull_request, user, extras=extras)
1002
1002
1003 if merge_resp.executed:
1003 if merge_resp.executed:
1004 log.debug("The merge was successful, closing the pull request.")
1004 log.debug("The merge was successful, closing the pull request.")
1005 PullRequestModel().close_pull_request(
1005 PullRequestModel().close_pull_request(
1006 pull_request.pull_request_id, user)
1006 pull_request.pull_request_id, user)
1007 Session().commit()
1007 Session().commit()
1008 msg = _('Pull request was successfully merged and closed.')
1008 msg = _('Pull request was successfully merged and closed.')
1009 h.flash(msg, category='success')
1009 h.flash(msg, category='success')
1010 else:
1010 else:
1011 log.debug(
1011 log.debug(
1012 "The merge was not successful. Merge response: %s",
1012 "The merge was not successful. Merge response: %s",
1013 merge_resp)
1013 merge_resp)
1014 msg = PullRequestModel().merge_status_message(
1014 msg = PullRequestModel().merge_status_message(
1015 merge_resp.failure_reason)
1015 merge_resp.failure_reason)
1016 h.flash(msg, category='error')
1016 h.flash(msg, category='error')
1017
1017
1018 def _update_reviewers(self, pull_request, review_members, reviewer_rules):
1018 def _update_reviewers(self, pull_request, review_members, reviewer_rules):
1019 _ = self.request.translate
1019 _ = self.request.translate
1020 get_default_reviewers_data, validate_default_reviewers = \
1020 get_default_reviewers_data, validate_default_reviewers = \
1021 PullRequestModel().get_reviewer_functions()
1021 PullRequestModel().get_reviewer_functions()
1022
1022
1023 try:
1023 try:
1024 reviewers = validate_default_reviewers(review_members, reviewer_rules)
1024 reviewers = validate_default_reviewers(review_members, reviewer_rules)
1025 except ValueError as e:
1025 except ValueError as e:
1026 log.error('Reviewers Validation: {}'.format(e))
1026 log.error('Reviewers Validation: {}'.format(e))
1027 h.flash(e, category='error')
1027 h.flash(e, category='error')
1028 return
1028 return
1029
1029
1030 PullRequestModel().update_reviewers(
1030 PullRequestModel().update_reviewers(
1031 pull_request, reviewers, self._rhodecode_user)
1031 pull_request, reviewers, self._rhodecode_user)
1032 h.flash(_('Pull request reviewers updated.'), category='success')
1032 h.flash(_('Pull request reviewers updated.'), category='success')
1033 Session().commit()
1033 Session().commit()
1034
1034
1035 @LoginRequired()
1035 @LoginRequired()
1036 @NotAnonymous()
1036 @NotAnonymous()
1037 @HasRepoPermissionAnyDecorator(
1037 @HasRepoPermissionAnyDecorator(
1038 'repository.read', 'repository.write', 'repository.admin')
1038 'repository.read', 'repository.write', 'repository.admin')
1039 @CSRFRequired()
1039 @CSRFRequired()
1040 @view_config(
1040 @view_config(
1041 route_name='pullrequest_delete', request_method='POST',
1041 route_name='pullrequest_delete', request_method='POST',
1042 renderer='json_ext')
1042 renderer='json_ext')
1043 def pull_request_delete(self):
1043 def pull_request_delete(self):
1044 _ = self.request.translate
1044 _ = self.request.translate
1045
1045
1046 pull_request = PullRequest.get_or_404(
1046 pull_request = PullRequest.get_or_404(
1047 self.request.matchdict['pull_request_id'])
1047 self.request.matchdict['pull_request_id'])
1048 self.load_default_context()
1048 self.load_default_context()
1049
1049
1050 pr_closed = pull_request.is_closed()
1050 pr_closed = pull_request.is_closed()
1051 allowed_to_delete = PullRequestModel().check_user_delete(
1051 allowed_to_delete = PullRequestModel().check_user_delete(
1052 pull_request, self._rhodecode_user) and not pr_closed
1052 pull_request, self._rhodecode_user) and not pr_closed
1053
1053
1054 # only owner can delete it !
1054 # only owner can delete it !
1055 if allowed_to_delete:
1055 if allowed_to_delete:
1056 PullRequestModel().delete(pull_request, self._rhodecode_user)
1056 PullRequestModel().delete(pull_request, self._rhodecode_user)
1057 Session().commit()
1057 Session().commit()
1058 h.flash(_('Successfully deleted pull request'),
1058 h.flash(_('Successfully deleted pull request'),
1059 category='success')
1059 category='success')
1060 raise HTTPFound(h.route_path('pullrequest_show_all',
1060 raise HTTPFound(h.route_path('pullrequest_show_all',
1061 repo_name=self.db_repo_name))
1061 repo_name=self.db_repo_name))
1062
1062
1063 log.warning('user %s tried to delete pull request without access',
1063 log.warning('user %s tried to delete pull request without access',
1064 self._rhodecode_user)
1064 self._rhodecode_user)
1065 raise HTTPNotFound()
1065 raise HTTPNotFound()
1066
1066
1067 @LoginRequired()
1067 @LoginRequired()
1068 @NotAnonymous()
1068 @NotAnonymous()
1069 @HasRepoPermissionAnyDecorator(
1069 @HasRepoPermissionAnyDecorator(
1070 'repository.read', 'repository.write', 'repository.admin')
1070 'repository.read', 'repository.write', 'repository.admin')
1071 @CSRFRequired()
1071 @CSRFRequired()
1072 @view_config(
1072 @view_config(
1073 route_name='pullrequest_comment_create', request_method='POST',
1073 route_name='pullrequest_comment_create', request_method='POST',
1074 renderer='json_ext')
1074 renderer='json_ext')
1075 def pull_request_comment_create(self):
1075 def pull_request_comment_create(self):
1076 _ = self.request.translate
1076 _ = self.request.translate
1077
1077
1078 pull_request = PullRequest.get_or_404(
1078 pull_request = PullRequest.get_or_404(
1079 self.request.matchdict['pull_request_id'])
1079 self.request.matchdict['pull_request_id'])
1080 pull_request_id = pull_request.pull_request_id
1080 pull_request_id = pull_request.pull_request_id
1081
1081
1082 if pull_request.is_closed():
1082 if pull_request.is_closed():
1083 log.debug('comment: forbidden because pull request is closed')
1083 log.debug('comment: forbidden because pull request is closed')
1084 raise HTTPForbidden()
1084 raise HTTPForbidden()
1085
1085
1086 allowed_to_comment = PullRequestModel().check_user_comment(
1086 allowed_to_comment = PullRequestModel().check_user_comment(
1087 pull_request, self._rhodecode_user)
1087 pull_request, self._rhodecode_user)
1088 if not allowed_to_comment:
1088 if not allowed_to_comment:
1089 log.debug(
1089 log.debug(
1090 'comment: forbidden because pull request is from forbidden repo')
1090 'comment: forbidden because pull request is from forbidden repo')
1091 raise HTTPForbidden()
1091 raise HTTPForbidden()
1092
1092
1093 c = self.load_default_context()
1093 c = self.load_default_context()
1094
1094
1095 status = self.request.POST.get('changeset_status', None)
1095 status = self.request.POST.get('changeset_status', None)
1096 text = self.request.POST.get('text')
1096 text = self.request.POST.get('text')
1097 comment_type = self.request.POST.get('comment_type')
1097 comment_type = self.request.POST.get('comment_type')
1098 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
1098 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
1099 close_pull_request = self.request.POST.get('close_pull_request')
1099 close_pull_request = self.request.POST.get('close_pull_request')
1100
1100
1101 # the logic here should work like following, if we submit close
1101 # the logic here should work like following, if we submit close
1102 # pr comment, use `close_pull_request_with_comment` function
1102 # pr comment, use `close_pull_request_with_comment` function
1103 # else handle regular comment logic
1103 # else handle regular comment logic
1104
1104
1105 if close_pull_request:
1105 if close_pull_request:
1106 # only owner or admin or person with write permissions
1106 # only owner or admin or person with write permissions
1107 allowed_to_close = PullRequestModel().check_user_update(
1107 allowed_to_close = PullRequestModel().check_user_update(
1108 pull_request, self._rhodecode_user)
1108 pull_request, self._rhodecode_user)
1109 if not allowed_to_close:
1109 if not allowed_to_close:
1110 log.debug('comment: forbidden because not allowed to close '
1110 log.debug('comment: forbidden because not allowed to close '
1111 'pull request %s', pull_request_id)
1111 'pull request %s', pull_request_id)
1112 raise HTTPForbidden()
1112 raise HTTPForbidden()
1113 comment, status = PullRequestModel().close_pull_request_with_comment(
1113 comment, status = PullRequestModel().close_pull_request_with_comment(
1114 pull_request, self._rhodecode_user, self.db_repo, message=text)
1114 pull_request, self._rhodecode_user, self.db_repo, message=text)
1115 Session().flush()
1115 Session().flush()
1116 events.trigger(
1116 events.trigger(
1117 events.PullRequestCommentEvent(pull_request, comment))
1117 events.PullRequestCommentEvent(pull_request, comment))
1118
1118
1119 else:
1119 else:
1120 # regular comment case, could be inline, or one with status.
1120 # regular comment case, could be inline, or one with status.
1121 # for that one we check also permissions
1121 # for that one we check also permissions
1122
1122
1123 allowed_to_change_status = PullRequestModel().check_user_change_status(
1123 allowed_to_change_status = PullRequestModel().check_user_change_status(
1124 pull_request, self._rhodecode_user)
1124 pull_request, self._rhodecode_user)
1125
1125
1126 if status and allowed_to_change_status:
1126 if status and allowed_to_change_status:
1127 message = (_('Status change %(transition_icon)s %(status)s')
1127 message = (_('Status change %(transition_icon)s %(status)s')
1128 % {'transition_icon': '>',
1128 % {'transition_icon': '>',
1129 'status': ChangesetStatus.get_status_lbl(status)})
1129 'status': ChangesetStatus.get_status_lbl(status)})
1130 text = text or message
1130 text = text or message
1131
1131
1132 comment = CommentsModel().create(
1132 comment = CommentsModel().create(
1133 text=text,
1133 text=text,
1134 repo=self.db_repo.repo_id,
1134 repo=self.db_repo.repo_id,
1135 user=self._rhodecode_user.user_id,
1135 user=self._rhodecode_user.user_id,
1136 pull_request=pull_request,
1136 pull_request=pull_request,
1137 f_path=self.request.POST.get('f_path'),
1137 f_path=self.request.POST.get('f_path'),
1138 line_no=self.request.POST.get('line'),
1138 line_no=self.request.POST.get('line'),
1139 status_change=(ChangesetStatus.get_status_lbl(status)
1139 status_change=(ChangesetStatus.get_status_lbl(status)
1140 if status and allowed_to_change_status else None),
1140 if status and allowed_to_change_status else None),
1141 status_change_type=(status
1141 status_change_type=(status
1142 if status and allowed_to_change_status else None),
1142 if status and allowed_to_change_status else None),
1143 comment_type=comment_type,
1143 comment_type=comment_type,
1144 resolves_comment_id=resolves_comment_id
1144 resolves_comment_id=resolves_comment_id
1145 )
1145 )
1146
1146
1147 if allowed_to_change_status:
1147 if allowed_to_change_status:
1148 # calculate old status before we change it
1148 # calculate old status before we change it
1149 old_calculated_status = pull_request.calculated_review_status()
1149 old_calculated_status = pull_request.calculated_review_status()
1150
1150
1151 # get status if set !
1151 # get status if set !
1152 if status:
1152 if status:
1153 ChangesetStatusModel().set_status(
1153 ChangesetStatusModel().set_status(
1154 self.db_repo.repo_id,
1154 self.db_repo.repo_id,
1155 status,
1155 status,
1156 self._rhodecode_user.user_id,
1156 self._rhodecode_user.user_id,
1157 comment,
1157 comment,
1158 pull_request=pull_request
1158 pull_request=pull_request
1159 )
1159 )
1160
1160
1161 Session().flush()
1161 Session().flush()
1162 # this is somehow required to get access to some relationship
1162 # this is somehow required to get access to some relationship
1163 # loaded on comment
1163 # loaded on comment
1164 Session().refresh(comment)
1164 Session().refresh(comment)
1165
1165
1166 events.trigger(
1166 events.trigger(
1167 events.PullRequestCommentEvent(pull_request, comment))
1167 events.PullRequestCommentEvent(pull_request, comment))
1168
1168
1169 # we now calculate the status of pull request, and based on that
1169 # we now calculate the status of pull request, and based on that
1170 # calculation we set the commits status
1170 # calculation we set the commits status
1171 calculated_status = pull_request.calculated_review_status()
1171 calculated_status = pull_request.calculated_review_status()
1172 if old_calculated_status != calculated_status:
1172 if old_calculated_status != calculated_status:
1173 PullRequestModel()._trigger_pull_request_hook(
1173 PullRequestModel()._trigger_pull_request_hook(
1174 pull_request, self._rhodecode_user, 'review_status_change')
1174 pull_request, self._rhodecode_user, 'review_status_change')
1175
1175
1176 Session().commit()
1176 Session().commit()
1177
1177
1178 data = {
1178 data = {
1179 'target_id': h.safeid(h.safe_unicode(
1179 'target_id': h.safeid(h.safe_unicode(
1180 self.request.POST.get('f_path'))),
1180 self.request.POST.get('f_path'))),
1181 }
1181 }
1182 if comment:
1182 if comment:
1183 c.co = comment
1183 c.co = comment
1184 rendered_comment = render(
1184 rendered_comment = render(
1185 'rhodecode:templates/changeset/changeset_comment_block.mako',
1185 'rhodecode:templates/changeset/changeset_comment_block.mako',
1186 self._get_template_context(c), self.request)
1186 self._get_template_context(c), self.request)
1187
1187
1188 data.update(comment.get_dict())
1188 data.update(comment.get_dict())
1189 data.update({'rendered_text': rendered_comment})
1189 data.update({'rendered_text': rendered_comment})
1190
1190
1191 return data
1191 return data
1192
1192
1193 @LoginRequired()
1193 @LoginRequired()
1194 @NotAnonymous()
1194 @NotAnonymous()
1195 @HasRepoPermissionAnyDecorator(
1195 @HasRepoPermissionAnyDecorator(
1196 'repository.read', 'repository.write', 'repository.admin')
1196 'repository.read', 'repository.write', 'repository.admin')
1197 @CSRFRequired()
1197 @CSRFRequired()
1198 @view_config(
1198 @view_config(
1199 route_name='pullrequest_comment_delete', request_method='POST',
1199 route_name='pullrequest_comment_delete', request_method='POST',
1200 renderer='json_ext')
1200 renderer='json_ext')
1201 def pull_request_comment_delete(self):
1201 def pull_request_comment_delete(self):
1202 pull_request = PullRequest.get_or_404(
1202 pull_request = PullRequest.get_or_404(
1203 self.request.matchdict['pull_request_id'])
1203 self.request.matchdict['pull_request_id'])
1204
1204
1205 comment = ChangesetComment.get_or_404(
1205 comment = ChangesetComment.get_or_404(
1206 self.request.matchdict['comment_id'])
1206 self.request.matchdict['comment_id'])
1207 comment_id = comment.comment_id
1207 comment_id = comment.comment_id
1208
1208
1209 if pull_request.is_closed():
1209 if pull_request.is_closed():
1210 log.debug('comment: forbidden because pull request is closed')
1210 log.debug('comment: forbidden because pull request is closed')
1211 raise HTTPForbidden()
1211 raise HTTPForbidden()
1212
1212
1213 if not comment:
1213 if not comment:
1214 log.debug('Comment with id:%s not found, skipping', comment_id)
1214 log.debug('Comment with id:%s not found, skipping', comment_id)
1215 # comment already deleted in another call probably
1215 # comment already deleted in another call probably
1216 return True
1216 return True
1217
1217
1218 if comment.pull_request.is_closed():
1218 if comment.pull_request.is_closed():
1219 # don't allow deleting comments on closed pull request
1219 # don't allow deleting comments on closed pull request
1220 raise HTTPForbidden()
1220 raise HTTPForbidden()
1221
1221
1222 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1222 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1223 super_admin = h.HasPermissionAny('hg.admin')()
1223 super_admin = h.HasPermissionAny('hg.admin')()
1224 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1224 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1225 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1225 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1226 comment_repo_admin = is_repo_admin and is_repo_comment
1226 comment_repo_admin = is_repo_admin and is_repo_comment
1227
1227
1228 if super_admin or comment_owner or comment_repo_admin:
1228 if super_admin or comment_owner or comment_repo_admin:
1229 old_calculated_status = comment.pull_request.calculated_review_status()
1229 old_calculated_status = comment.pull_request.calculated_review_status()
1230 CommentsModel().delete(comment=comment, user=self._rhodecode_user)
1230 CommentsModel().delete(comment=comment, user=self._rhodecode_user)
1231 Session().commit()
1231 Session().commit()
1232 calculated_status = comment.pull_request.calculated_review_status()
1232 calculated_status = comment.pull_request.calculated_review_status()
1233 if old_calculated_status != calculated_status:
1233 if old_calculated_status != calculated_status:
1234 PullRequestModel()._trigger_pull_request_hook(
1234 PullRequestModel()._trigger_pull_request_hook(
1235 comment.pull_request, self._rhodecode_user, 'review_status_change')
1235 comment.pull_request, self._rhodecode_user, 'review_status_change')
1236 return True
1236 return True
1237 else:
1237 else:
1238 log.warning('No permissions for user %s to delete comment_id: %s',
1238 log.warning('No permissions for user %s to delete comment_id: %s',
1239 self._rhodecode_db_user, comment_id)
1239 self._rhodecode_db_user, comment_id)
1240 raise HTTPNotFound()
1240 raise HTTPNotFound()
@@ -1,614 +1,614 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-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 this is forms validation classes
22 this is forms validation classes
23 http://formencode.org/module-formencode.validators.html
23 http://formencode.org/module-formencode.validators.html
24 for list off all availible validators
24 for list off all availible validators
25
25
26 we can create our own validators
26 we can create our own validators
27
27
28 The table below outlines the options which can be used in a schema in addition to the validators themselves
28 The table below outlines the options which can be used in a schema in addition to the validators themselves
29 pre_validators [] These validators will be applied before the schema
29 pre_validators [] These validators will be applied before the schema
30 chained_validators [] These validators will be applied after the schema
30 chained_validators [] These validators will be applied after the schema
31 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
31 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
32 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
32 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
33 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
33 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
34 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
34 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
35
35
36
36
37 <name> = formencode.validators.<name of validator>
37 <name> = formencode.validators.<name of validator>
38 <name> must equal form name
38 <name> must equal form name
39 list=[1,2,3,4,5]
39 list=[1,2,3,4,5]
40 for SELECT use formencode.All(OneOf(list), Int())
40 for SELECT use formencode.All(OneOf(list), Int())
41
41
42 """
42 """
43
43
44 import deform
44 import deform
45 import logging
45 import logging
46 import formencode
46 import formencode
47
47
48 from pkg_resources import resource_filename
48 from pkg_resources import resource_filename
49 from formencode import All, Pipe
49 from formencode import All, Pipe
50
50
51 from pyramid.threadlocal import get_current_request
51 from pyramid.threadlocal import get_current_request
52
52
53 from rhodecode import BACKENDS
53 from rhodecode import BACKENDS
54 from rhodecode.lib import helpers
54 from rhodecode.lib import helpers
55 from rhodecode.model import validators as v
55 from rhodecode.model import validators as v
56
56
57 log = logging.getLogger(__name__)
57 log = logging.getLogger(__name__)
58
58
59
59
60 deform_templates = resource_filename('deform', 'templates')
60 deform_templates = resource_filename('deform', 'templates')
61 rhodecode_templates = resource_filename('rhodecode', 'templates/forms')
61 rhodecode_templates = resource_filename('rhodecode', 'templates/forms')
62 search_path = (rhodecode_templates, deform_templates)
62 search_path = (rhodecode_templates, deform_templates)
63
63
64
64
65 class RhodecodeFormZPTRendererFactory(deform.ZPTRendererFactory):
65 class RhodecodeFormZPTRendererFactory(deform.ZPTRendererFactory):
66 """ Subclass of ZPTRendererFactory to add rhodecode context variables """
66 """ Subclass of ZPTRendererFactory to add rhodecode context variables """
67 def __call__(self, template_name, **kw):
67 def __call__(self, template_name, **kw):
68 kw['h'] = helpers
68 kw['h'] = helpers
69 kw['request'] = get_current_request()
69 kw['request'] = get_current_request()
70 return self.load(template_name)(**kw)
70 return self.load(template_name)(**kw)
71
71
72
72
73 form_renderer = RhodecodeFormZPTRendererFactory(search_path)
73 form_renderer = RhodecodeFormZPTRendererFactory(search_path)
74 deform.Form.set_default_renderer(form_renderer)
74 deform.Form.set_default_renderer(form_renderer)
75
75
76
76
77 def LoginForm(localizer):
77 def LoginForm(localizer):
78 _ = localizer
78 _ = localizer
79
79
80 class _LoginForm(formencode.Schema):
80 class _LoginForm(formencode.Schema):
81 allow_extra_fields = True
81 allow_extra_fields = True
82 filter_extra_fields = True
82 filter_extra_fields = True
83 username = v.UnicodeString(
83 username = v.UnicodeString(
84 strip=True,
84 strip=True,
85 min=1,
85 min=1,
86 not_empty=True,
86 not_empty=True,
87 messages={
87 messages={
88 'empty': _(u'Please enter a login'),
88 'empty': _(u'Please enter a login'),
89 'tooShort': _(u'Enter a value %(min)i characters long or more')
89 'tooShort': _(u'Enter a value %(min)i characters long or more')
90 }
90 }
91 )
91 )
92
92
93 password = v.UnicodeString(
93 password = v.UnicodeString(
94 strip=False,
94 strip=False,
95 min=3,
95 min=3,
96 max=72,
96 max=72,
97 not_empty=True,
97 not_empty=True,
98 messages={
98 messages={
99 'empty': _(u'Please enter a password'),
99 'empty': _(u'Please enter a password'),
100 'tooShort': _(u'Enter %(min)i characters or more')}
100 'tooShort': _(u'Enter %(min)i characters or more')}
101 )
101 )
102
102
103 remember = v.StringBoolean(if_missing=False)
103 remember = v.StringBoolean(if_missing=False)
104
104
105 chained_validators = [v.ValidAuth(localizer)]
105 chained_validators = [v.ValidAuth(localizer)]
106 return _LoginForm
106 return _LoginForm
107
107
108
108
109 def UserForm(localizer, edit=False, available_languages=None, old_data=None):
109 def UserForm(localizer, edit=False, available_languages=None, old_data=None):
110 old_data = old_data or {}
110 old_data = old_data or {}
111 available_languages = available_languages or []
111 available_languages = available_languages or []
112 _ = localizer
112 _ = localizer
113
113
114 class _UserForm(formencode.Schema):
114 class _UserForm(formencode.Schema):
115 allow_extra_fields = True
115 allow_extra_fields = True
116 filter_extra_fields = True
116 filter_extra_fields = True
117 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
117 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
118 v.ValidUsername(localizer, edit, old_data))
118 v.ValidUsername(localizer, edit, old_data))
119 if edit:
119 if edit:
120 new_password = All(
120 new_password = All(
121 v.ValidPassword(localizer),
121 v.ValidPassword(localizer),
122 v.UnicodeString(strip=False, min=6, max=72, not_empty=False)
122 v.UnicodeString(strip=False, min=6, max=72, not_empty=False)
123 )
123 )
124 password_confirmation = All(
124 password_confirmation = All(
125 v.ValidPassword(localizer),
125 v.ValidPassword(localizer),
126 v.UnicodeString(strip=False, min=6, max=72, not_empty=False),
126 v.UnicodeString(strip=False, min=6, max=72, not_empty=False),
127 )
127 )
128 admin = v.StringBoolean(if_missing=False)
128 admin = v.StringBoolean(if_missing=False)
129 else:
129 else:
130 password = All(
130 password = All(
131 v.ValidPassword(localizer),
131 v.ValidPassword(localizer),
132 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
132 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
133 )
133 )
134 password_confirmation = All(
134 password_confirmation = All(
135 v.ValidPassword(localizer),
135 v.ValidPassword(localizer),
136 v.UnicodeString(strip=False, min=6, max=72, not_empty=False)
136 v.UnicodeString(strip=False, min=6, max=72, not_empty=False)
137 )
137 )
138
138
139 password_change = v.StringBoolean(if_missing=False)
139 password_change = v.StringBoolean(if_missing=False)
140 create_repo_group = v.StringBoolean(if_missing=False)
140 create_repo_group = v.StringBoolean(if_missing=False)
141
141
142 active = v.StringBoolean(if_missing=False)
142 active = v.StringBoolean(if_missing=False)
143 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
143 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
144 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
144 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
145 email = All(v.UniqSystemEmail(localizer, old_data), v.Email(not_empty=True))
145 email = All(v.UniqSystemEmail(localizer, old_data), v.Email(not_empty=True))
146 extern_name = v.UnicodeString(strip=True)
146 extern_name = v.UnicodeString(strip=True)
147 extern_type = v.UnicodeString(strip=True)
147 extern_type = v.UnicodeString(strip=True)
148 language = v.OneOf(available_languages, hideList=False,
148 language = v.OneOf(available_languages, hideList=False,
149 testValueList=True, if_missing=None)
149 testValueList=True, if_missing=None)
150 chained_validators = [v.ValidPasswordsMatch(localizer)]
150 chained_validators = [v.ValidPasswordsMatch(localizer)]
151 return _UserForm
151 return _UserForm
152
152
153
153
154 def UserGroupForm(localizer, edit=False, old_data=None, allow_disabled=False):
154 def UserGroupForm(localizer, edit=False, old_data=None, allow_disabled=False):
155 old_data = old_data or {}
155 old_data = old_data or {}
156 _ = localizer
156 _ = localizer
157
157
158 class _UserGroupForm(formencode.Schema):
158 class _UserGroupForm(formencode.Schema):
159 allow_extra_fields = True
159 allow_extra_fields = True
160 filter_extra_fields = True
160 filter_extra_fields = True
161
161
162 users_group_name = All(
162 users_group_name = All(
163 v.UnicodeString(strip=True, min=1, not_empty=True),
163 v.UnicodeString(strip=True, min=1, not_empty=True),
164 v.ValidUserGroup(localizer, edit, old_data)
164 v.ValidUserGroup(localizer, edit, old_data)
165 )
165 )
166 user_group_description = v.UnicodeString(strip=True, min=1,
166 user_group_description = v.UnicodeString(strip=True, min=1,
167 not_empty=False)
167 not_empty=False)
168
168
169 users_group_active = v.StringBoolean(if_missing=False)
169 users_group_active = v.StringBoolean(if_missing=False)
170
170
171 if edit:
171 if edit:
172 # this is user group owner
172 # this is user group owner
173 user = All(
173 user = All(
174 v.UnicodeString(not_empty=True),
174 v.UnicodeString(not_empty=True),
175 v.ValidRepoUser(localizer, allow_disabled))
175 v.ValidRepoUser(localizer, allow_disabled))
176 return _UserGroupForm
176 return _UserGroupForm
177
177
178
178
179 def RepoGroupForm(localizer, edit=False, old_data=None, available_groups=None,
179 def RepoGroupForm(localizer, edit=False, old_data=None, available_groups=None,
180 can_create_in_root=False, allow_disabled=False):
180 can_create_in_root=False, allow_disabled=False):
181 _ = localizer
181 _ = localizer
182 old_data = old_data or {}
182 old_data = old_data or {}
183 available_groups = available_groups or []
183 available_groups = available_groups or []
184
184
185 class _RepoGroupForm(formencode.Schema):
185 class _RepoGroupForm(formencode.Schema):
186 allow_extra_fields = True
186 allow_extra_fields = True
187 filter_extra_fields = False
187 filter_extra_fields = False
188
188
189 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
189 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
190 v.SlugifyName(localizer),)
190 v.SlugifyName(localizer),)
191 group_description = v.UnicodeString(strip=True, min=1,
191 group_description = v.UnicodeString(strip=True, min=1,
192 not_empty=False)
192 not_empty=False)
193 group_copy_permissions = v.StringBoolean(if_missing=False)
193 group_copy_permissions = v.StringBoolean(if_missing=False)
194
194
195 group_parent_id = v.OneOf(available_groups, hideList=False,
195 group_parent_id = v.OneOf(available_groups, hideList=False,
196 testValueList=True, not_empty=True)
196 testValueList=True, not_empty=True)
197 enable_locking = v.StringBoolean(if_missing=False)
197 enable_locking = v.StringBoolean(if_missing=False)
198 chained_validators = [
198 chained_validators = [
199 v.ValidRepoGroup(localizer, edit, old_data, can_create_in_root)]
199 v.ValidRepoGroup(localizer, edit, old_data, can_create_in_root)]
200
200
201 if edit:
201 if edit:
202 # this is repo group owner
202 # this is repo group owner
203 user = All(
203 user = All(
204 v.UnicodeString(not_empty=True),
204 v.UnicodeString(not_empty=True),
205 v.ValidRepoUser(localizer, allow_disabled))
205 v.ValidRepoUser(localizer, allow_disabled))
206 return _RepoGroupForm
206 return _RepoGroupForm
207
207
208
208
209 def RegisterForm(localizer, edit=False, old_data=None):
209 def RegisterForm(localizer, edit=False, old_data=None):
210 _ = localizer
210 _ = localizer
211 old_data = old_data or {}
211 old_data = old_data or {}
212
212
213 class _RegisterForm(formencode.Schema):
213 class _RegisterForm(formencode.Schema):
214 allow_extra_fields = True
214 allow_extra_fields = True
215 filter_extra_fields = True
215 filter_extra_fields = True
216 username = All(
216 username = All(
217 v.ValidUsername(localizer, edit, old_data),
217 v.ValidUsername(localizer, edit, old_data),
218 v.UnicodeString(strip=True, min=1, not_empty=True)
218 v.UnicodeString(strip=True, min=1, not_empty=True)
219 )
219 )
220 password = All(
220 password = All(
221 v.ValidPassword(localizer),
221 v.ValidPassword(localizer),
222 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
222 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
223 )
223 )
224 password_confirmation = All(
224 password_confirmation = All(
225 v.ValidPassword(localizer),
225 v.ValidPassword(localizer),
226 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
226 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
227 )
227 )
228 active = v.StringBoolean(if_missing=False)
228 active = v.StringBoolean(if_missing=False)
229 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
229 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
230 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
230 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
231 email = All(v.UniqSystemEmail(localizer, old_data), v.Email(not_empty=True))
231 email = All(v.UniqSystemEmail(localizer, old_data), v.Email(not_empty=True))
232
232
233 chained_validators = [v.ValidPasswordsMatch(localizer)]
233 chained_validators = [v.ValidPasswordsMatch(localizer)]
234 return _RegisterForm
234 return _RegisterForm
235
235
236
236
237 def PasswordResetForm(localizer):
237 def PasswordResetForm(localizer):
238 _ = localizer
238 _ = localizer
239
239
240 class _PasswordResetForm(formencode.Schema):
240 class _PasswordResetForm(formencode.Schema):
241 allow_extra_fields = True
241 allow_extra_fields = True
242 filter_extra_fields = True
242 filter_extra_fields = True
243 email = All(v.ValidSystemEmail(localizer), v.Email(not_empty=True))
243 email = All(v.ValidSystemEmail(localizer), v.Email(not_empty=True))
244 return _PasswordResetForm
244 return _PasswordResetForm
245
245
246
246
247 def RepoForm(localizer, edit=False, old_data=None, repo_groups=None,
247 def RepoForm(localizer, edit=False, old_data=None, repo_groups=None,
248 landing_revs=None, allow_disabled=False):
248 landing_revs=None, allow_disabled=False):
249 _ = localizer
249 _ = localizer
250 old_data = old_data or {}
250 old_data = old_data or {}
251 repo_groups = repo_groups or []
251 repo_groups = repo_groups or []
252 landing_revs = landing_revs or []
252 landing_revs = landing_revs or []
253 supported_backends = BACKENDS.keys()
253 supported_backends = BACKENDS.keys()
254
254
255 class _RepoForm(formencode.Schema):
255 class _RepoForm(formencode.Schema):
256 allow_extra_fields = True
256 allow_extra_fields = True
257 filter_extra_fields = False
257 filter_extra_fields = False
258 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
258 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
259 v.SlugifyName(localizer), v.CannotHaveGitSuffix(localizer))
259 v.SlugifyName(localizer), v.CannotHaveGitSuffix(localizer))
260 repo_group = All(v.CanWriteGroup(localizer, old_data),
260 repo_group = All(v.CanWriteGroup(localizer, old_data),
261 v.OneOf(repo_groups, hideList=True))
261 v.OneOf(repo_groups, hideList=True))
262 repo_type = v.OneOf(supported_backends, required=False,
262 repo_type = v.OneOf(supported_backends, required=False,
263 if_missing=old_data.get('repo_type'))
263 if_missing=old_data.get('repo_type'))
264 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
264 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
265 repo_private = v.StringBoolean(if_missing=False)
265 repo_private = v.StringBoolean(if_missing=False)
266 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
266 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
267 repo_copy_permissions = v.StringBoolean(if_missing=False)
267 repo_copy_permissions = v.StringBoolean(if_missing=False)
268 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
268 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
269
269
270 repo_enable_statistics = v.StringBoolean(if_missing=False)
270 repo_enable_statistics = v.StringBoolean(if_missing=False)
271 repo_enable_downloads = v.StringBoolean(if_missing=False)
271 repo_enable_downloads = v.StringBoolean(if_missing=False)
272 repo_enable_locking = v.StringBoolean(if_missing=False)
272 repo_enable_locking = v.StringBoolean(if_missing=False)
273
273
274 if edit:
274 if edit:
275 # this is repo owner
275 # this is repo owner
276 user = All(
276 user = All(
277 v.UnicodeString(not_empty=True),
277 v.UnicodeString(not_empty=True),
278 v.ValidRepoUser(localizer, allow_disabled))
278 v.ValidRepoUser(localizer, allow_disabled))
279 clone_uri_change = v.UnicodeString(
279 clone_uri_change = v.UnicodeString(
280 not_empty=False, if_missing=v.Missing)
280 not_empty=False, if_missing=v.Missing)
281
281
282 chained_validators = [v.ValidCloneUri(localizer),
282 chained_validators = [v.ValidCloneUri(localizer),
283 v.ValidRepoName(localizer, edit, old_data)]
283 v.ValidRepoName(localizer, edit, old_data)]
284 return _RepoForm
284 return _RepoForm
285
285
286
286
287 def RepoPermsForm(localizer):
287 def RepoPermsForm(localizer):
288 _ = localizer
288 _ = localizer
289
289
290 class _RepoPermsForm(formencode.Schema):
290 class _RepoPermsForm(formencode.Schema):
291 allow_extra_fields = True
291 allow_extra_fields = True
292 filter_extra_fields = False
292 filter_extra_fields = False
293 chained_validators = [v.ValidPerms(localizer, type_='repo')]
293 chained_validators = [v.ValidPerms(localizer, type_='repo')]
294 return _RepoPermsForm
294 return _RepoPermsForm
295
295
296
296
297 def RepoGroupPermsForm(localizer, valid_recursive_choices):
297 def RepoGroupPermsForm(localizer, valid_recursive_choices):
298 _ = localizer
298 _ = localizer
299
299
300 class _RepoGroupPermsForm(formencode.Schema):
300 class _RepoGroupPermsForm(formencode.Schema):
301 allow_extra_fields = True
301 allow_extra_fields = True
302 filter_extra_fields = False
302 filter_extra_fields = False
303 recursive = v.OneOf(valid_recursive_choices)
303 recursive = v.OneOf(valid_recursive_choices)
304 chained_validators = [v.ValidPerms(localizer, type_='repo_group')]
304 chained_validators = [v.ValidPerms(localizer, type_='repo_group')]
305 return _RepoGroupPermsForm
305 return _RepoGroupPermsForm
306
306
307
307
308 def UserGroupPermsForm(localizer):
308 def UserGroupPermsForm(localizer):
309 _ = localizer
309 _ = localizer
310
310
311 class _UserPermsForm(formencode.Schema):
311 class _UserPermsForm(formencode.Schema):
312 allow_extra_fields = True
312 allow_extra_fields = True
313 filter_extra_fields = False
313 filter_extra_fields = False
314 chained_validators = [v.ValidPerms(localizer, type_='user_group')]
314 chained_validators = [v.ValidPerms(localizer, type_='user_group')]
315 return _UserPermsForm
315 return _UserPermsForm
316
316
317
317
318 def RepoFieldForm(localizer):
318 def RepoFieldForm(localizer):
319 _ = localizer
319 _ = localizer
320
320
321 class _RepoFieldForm(formencode.Schema):
321 class _RepoFieldForm(formencode.Schema):
322 filter_extra_fields = True
322 filter_extra_fields = True
323 allow_extra_fields = True
323 allow_extra_fields = True
324
324
325 new_field_key = All(v.FieldKey(localizer),
325 new_field_key = All(v.FieldKey(localizer),
326 v.UnicodeString(strip=True, min=3, not_empty=True))
326 v.UnicodeString(strip=True, min=3, not_empty=True))
327 new_field_value = v.UnicodeString(not_empty=False, if_missing=u'')
327 new_field_value = v.UnicodeString(not_empty=False, if_missing=u'')
328 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
328 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
329 if_missing='str')
329 if_missing='str')
330 new_field_label = v.UnicodeString(not_empty=False)
330 new_field_label = v.UnicodeString(not_empty=False)
331 new_field_desc = v.UnicodeString(not_empty=False)
331 new_field_desc = v.UnicodeString(not_empty=False)
332 return _RepoFieldForm
332 return _RepoFieldForm
333
333
334
334
335 def RepoForkForm(localizer, edit=False, old_data=None,
335 def RepoForkForm(localizer, edit=False, old_data=None,
336 supported_backends=BACKENDS.keys(), repo_groups=None,
336 supported_backends=BACKENDS.keys(), repo_groups=None,
337 landing_revs=None):
337 landing_revs=None):
338 _ = localizer
338 _ = localizer
339 old_data = old_data or {}
339 old_data = old_data or {}
340 repo_groups = repo_groups or []
340 repo_groups = repo_groups or []
341 landing_revs = landing_revs or []
341 landing_revs = landing_revs or []
342
342
343 class _RepoForkForm(formencode.Schema):
343 class _RepoForkForm(formencode.Schema):
344 allow_extra_fields = True
344 allow_extra_fields = True
345 filter_extra_fields = False
345 filter_extra_fields = False
346 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
346 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
347 v.SlugifyName(localizer))
347 v.SlugifyName(localizer))
348 repo_group = All(v.CanWriteGroup(localizer, ),
348 repo_group = All(v.CanWriteGroup(localizer, ),
349 v.OneOf(repo_groups, hideList=True))
349 v.OneOf(repo_groups, hideList=True))
350 repo_type = All(v.ValidForkType(localizer, old_data), v.OneOf(supported_backends))
350 repo_type = All(v.ValidForkType(localizer, old_data), v.OneOf(supported_backends))
351 description = v.UnicodeString(strip=True, min=1, not_empty=True)
351 description = v.UnicodeString(strip=True, min=1, not_empty=True)
352 private = v.StringBoolean(if_missing=False)
352 private = v.StringBoolean(if_missing=False)
353 copy_permissions = v.StringBoolean(if_missing=False)
353 copy_permissions = v.StringBoolean(if_missing=False)
354 fork_parent_id = v.UnicodeString()
354 fork_parent_id = v.UnicodeString()
355 chained_validators = [v.ValidForkName(localizer, edit, old_data)]
355 chained_validators = [v.ValidForkName(localizer, edit, old_data)]
356 landing_rev = v.OneOf(landing_revs, hideList=True)
356 landing_rev = v.OneOf(landing_revs, hideList=True)
357 return _RepoForkForm
357 return _RepoForkForm
358
358
359
359
360 def ApplicationSettingsForm(localizer):
360 def ApplicationSettingsForm(localizer):
361 _ = localizer
361 _ = localizer
362
362
363 class _ApplicationSettingsForm(formencode.Schema):
363 class _ApplicationSettingsForm(formencode.Schema):
364 allow_extra_fields = True
364 allow_extra_fields = True
365 filter_extra_fields = False
365 filter_extra_fields = False
366 rhodecode_title = v.UnicodeString(strip=True, max=40, not_empty=False)
366 rhodecode_title = v.UnicodeString(strip=True, max=40, not_empty=False)
367 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
367 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
368 rhodecode_pre_code = v.UnicodeString(strip=True, min=1, not_empty=False)
368 rhodecode_pre_code = v.UnicodeString(strip=True, min=1, not_empty=False)
369 rhodecode_post_code = v.UnicodeString(strip=True, min=1, not_empty=False)
369 rhodecode_post_code = v.UnicodeString(strip=True, min=1, not_empty=False)
370 rhodecode_captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
370 rhodecode_captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
371 rhodecode_captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
371 rhodecode_captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
372 rhodecode_create_personal_repo_group = v.StringBoolean(if_missing=False)
372 rhodecode_create_personal_repo_group = v.StringBoolean(if_missing=False)
373 rhodecode_personal_repo_group_pattern = v.UnicodeString(strip=True, min=1, not_empty=False)
373 rhodecode_personal_repo_group_pattern = v.UnicodeString(strip=True, min=1, not_empty=False)
374 return _ApplicationSettingsForm
374 return _ApplicationSettingsForm
375
375
376
376
377 def ApplicationVisualisationForm(localizer):
377 def ApplicationVisualisationForm(localizer):
378 _ = localizer
378 _ = localizer
379
379
380 class _ApplicationVisualisationForm(formencode.Schema):
380 class _ApplicationVisualisationForm(formencode.Schema):
381 allow_extra_fields = True
381 allow_extra_fields = True
382 filter_extra_fields = False
382 filter_extra_fields = False
383 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
383 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
384 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
384 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
385 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
385 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
386
386
387 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
387 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
388 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
388 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
389 rhodecode_dashboard_items = v.Int(min=5, not_empty=True)
389 rhodecode_dashboard_items = v.Int(min=5, not_empty=True)
390 rhodecode_admin_grid_items = v.Int(min=5, not_empty=True)
390 rhodecode_admin_grid_items = v.Int(min=5, not_empty=True)
391 rhodecode_show_version = v.StringBoolean(if_missing=False)
391 rhodecode_show_version = v.StringBoolean(if_missing=False)
392 rhodecode_use_gravatar = v.StringBoolean(if_missing=False)
392 rhodecode_use_gravatar = v.StringBoolean(if_missing=False)
393 rhodecode_markup_renderer = v.OneOf(['markdown', 'rst'])
393 rhodecode_markup_renderer = v.OneOf(['markdown', 'rst'])
394 rhodecode_gravatar_url = v.UnicodeString(min=3)
394 rhodecode_gravatar_url = v.UnicodeString(min=3)
395 rhodecode_clone_uri_tmpl = v.UnicodeString(min=3)
395 rhodecode_clone_uri_tmpl = v.UnicodeString(min=3)
396 rhodecode_support_url = v.UnicodeString()
396 rhodecode_support_url = v.UnicodeString()
397 rhodecode_show_revision_number = v.StringBoolean(if_missing=False)
397 rhodecode_show_revision_number = v.StringBoolean(if_missing=False)
398 rhodecode_show_sha_length = v.Int(min=4, not_empty=True)
398 rhodecode_show_sha_length = v.Int(min=4, not_empty=True)
399 return _ApplicationVisualisationForm
399 return _ApplicationVisualisationForm
400
400
401
401
402 class _BaseVcsSettingsForm(formencode.Schema):
402 class _BaseVcsSettingsForm(formencode.Schema):
403
403
404 allow_extra_fields = True
404 allow_extra_fields = True
405 filter_extra_fields = False
405 filter_extra_fields = False
406 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
406 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
407 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
407 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
408 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
408 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
409
409
410 # PR/Code-review
410 # PR/Code-review
411 rhodecode_pr_merge_enabled = v.StringBoolean(if_missing=False)
411 rhodecode_pr_merge_enabled = v.StringBoolean(if_missing=False)
412 rhodecode_use_outdated_comments = v.StringBoolean(if_missing=False)
412 rhodecode_use_outdated_comments = v.StringBoolean(if_missing=False)
413
413
414 # hg
414 # hg
415 extensions_largefiles = v.StringBoolean(if_missing=False)
415 extensions_largefiles = v.StringBoolean(if_missing=False)
416 extensions_evolve = v.StringBoolean(if_missing=False)
416 extensions_evolve = v.StringBoolean(if_missing=False)
417 phases_publish = v.StringBoolean(if_missing=False)
417 phases_publish = v.StringBoolean(if_missing=False)
418
418
419 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
419 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
420 rhodecode_hg_close_branch_before_merging = v.StringBoolean(if_missing=False)
420 rhodecode_hg_close_branch_before_merging = v.StringBoolean(if_missing=False)
421
421
422 # git
422 # git
423 vcs_git_lfs_enabled = v.StringBoolean(if_missing=False)
423 vcs_git_lfs_enabled = v.StringBoolean(if_missing=False)
424 rhodecode_git_use_rebase_for_merging = v.StringBoolean(if_missing=False)
424 rhodecode_git_use_rebase_for_merging = v.StringBoolean(if_missing=False)
425 rhodecode_git_close_branch_before_merging = v.StringBoolean(if_missing=False)
425 rhodecode_git_close_branch_before_merging = v.StringBoolean(if_missing=False)
426
426
427 # svn
427 # svn
428 vcs_svn_proxy_http_requests_enabled = v.StringBoolean(if_missing=False)
428 vcs_svn_proxy_http_requests_enabled = v.StringBoolean(if_missing=False)
429 vcs_svn_proxy_http_server_url = v.UnicodeString(strip=True, if_missing=None)
429 vcs_svn_proxy_http_server_url = v.UnicodeString(strip=True, if_missing=None)
430
430
431
431
432 def ApplicationUiSettingsForm(localizer):
432 def ApplicationUiSettingsForm(localizer):
433 _ = localizer
433 _ = localizer
434
434
435 class _ApplicationUiSettingsForm(_BaseVcsSettingsForm):
435 class _ApplicationUiSettingsForm(_BaseVcsSettingsForm):
436 web_push_ssl = v.StringBoolean(if_missing=False)
436 web_push_ssl = v.StringBoolean(if_missing=False)
437 paths_root_path = All(
437 paths_root_path = All(
438 v.ValidPath(localizer),
438 v.ValidPath(localizer),
439 v.UnicodeString(strip=True, min=1, not_empty=True)
439 v.UnicodeString(strip=True, min=1, not_empty=True)
440 )
440 )
441 largefiles_usercache = All(
441 largefiles_usercache = All(
442 v.ValidPath(localizer),
442 v.ValidPath(localizer),
443 v.UnicodeString(strip=True, min=2, not_empty=True))
443 v.UnicodeString(strip=True, min=2, not_empty=True))
444 vcs_git_lfs_store_location = All(
444 vcs_git_lfs_store_location = All(
445 v.ValidPath(localizer),
445 v.ValidPath(localizer),
446 v.UnicodeString(strip=True, min=2, not_empty=True))
446 v.UnicodeString(strip=True, min=2, not_empty=True))
447 extensions_hgsubversion = v.StringBoolean(if_missing=False)
447 extensions_hgsubversion = v.StringBoolean(if_missing=False)
448 extensions_hggit = v.StringBoolean(if_missing=False)
448 extensions_hggit = v.StringBoolean(if_missing=False)
449 new_svn_branch = v.ValidSvnPattern(localizer, section='vcs_svn_branch')
449 new_svn_branch = v.ValidSvnPattern(localizer, section='vcs_svn_branch')
450 new_svn_tag = v.ValidSvnPattern(localizer, section='vcs_svn_tag')
450 new_svn_tag = v.ValidSvnPattern(localizer, section='vcs_svn_tag')
451 return _ApplicationUiSettingsForm
451 return _ApplicationUiSettingsForm
452
452
453
453
454 def RepoVcsSettingsForm(localizer, repo_name):
454 def RepoVcsSettingsForm(localizer, repo_name):
455 _ = localizer
455 _ = localizer
456
456
457 class _RepoVcsSettingsForm(_BaseVcsSettingsForm):
457 class _RepoVcsSettingsForm(_BaseVcsSettingsForm):
458 inherit_global_settings = v.StringBoolean(if_missing=False)
458 inherit_global_settings = v.StringBoolean(if_missing=False)
459 new_svn_branch = v.ValidSvnPattern(localizer,
459 new_svn_branch = v.ValidSvnPattern(localizer,
460 section='vcs_svn_branch', repo_name=repo_name)
460 section='vcs_svn_branch', repo_name=repo_name)
461 new_svn_tag = v.ValidSvnPattern(localizer,
461 new_svn_tag = v.ValidSvnPattern(localizer,
462 section='vcs_svn_tag', repo_name=repo_name)
462 section='vcs_svn_tag', repo_name=repo_name)
463 return _RepoVcsSettingsForm
463 return _RepoVcsSettingsForm
464
464
465
465
466 def LabsSettingsForm(localizer):
466 def LabsSettingsForm(localizer):
467 _ = localizer
467 _ = localizer
468
468
469 class _LabSettingsForm(formencode.Schema):
469 class _LabSettingsForm(formencode.Schema):
470 allow_extra_fields = True
470 allow_extra_fields = True
471 filter_extra_fields = False
471 filter_extra_fields = False
472 return _LabSettingsForm
472 return _LabSettingsForm
473
473
474
474
475 def ApplicationPermissionsForm(
475 def ApplicationPermissionsForm(
476 localizer, register_choices, password_reset_choices,
476 localizer, register_choices, password_reset_choices,
477 extern_activate_choices):
477 extern_activate_choices):
478 _ = localizer
478 _ = localizer
479
479
480 class _DefaultPermissionsForm(formencode.Schema):
480 class _DefaultPermissionsForm(formencode.Schema):
481 allow_extra_fields = True
481 allow_extra_fields = True
482 filter_extra_fields = True
482 filter_extra_fields = True
483
483
484 anonymous = v.StringBoolean(if_missing=False)
484 anonymous = v.StringBoolean(if_missing=False)
485 default_register = v.OneOf(register_choices)
485 default_register = v.OneOf(register_choices)
486 default_register_message = v.UnicodeString()
486 default_register_message = v.UnicodeString()
487 default_password_reset = v.OneOf(password_reset_choices)
487 default_password_reset = v.OneOf(password_reset_choices)
488 default_extern_activate = v.OneOf(extern_activate_choices)
488 default_extern_activate = v.OneOf(extern_activate_choices)
489 return _DefaultPermissionsForm
489 return _DefaultPermissionsForm
490
490
491
491
492 def ObjectPermissionsForm(localizer, repo_perms_choices, group_perms_choices,
492 def ObjectPermissionsForm(localizer, repo_perms_choices, group_perms_choices,
493 user_group_perms_choices):
493 user_group_perms_choices):
494 _ = localizer
494 _ = localizer
495
495
496 class _ObjectPermissionsForm(formencode.Schema):
496 class _ObjectPermissionsForm(formencode.Schema):
497 allow_extra_fields = True
497 allow_extra_fields = True
498 filter_extra_fields = True
498 filter_extra_fields = True
499 overwrite_default_repo = v.StringBoolean(if_missing=False)
499 overwrite_default_repo = v.StringBoolean(if_missing=False)
500 overwrite_default_group = v.StringBoolean(if_missing=False)
500 overwrite_default_group = v.StringBoolean(if_missing=False)
501 overwrite_default_user_group = v.StringBoolean(if_missing=False)
501 overwrite_default_user_group = v.StringBoolean(if_missing=False)
502 default_repo_perm = v.OneOf(repo_perms_choices)
502 default_repo_perm = v.OneOf(repo_perms_choices)
503 default_group_perm = v.OneOf(group_perms_choices)
503 default_group_perm = v.OneOf(group_perms_choices)
504 default_user_group_perm = v.OneOf(user_group_perms_choices)
504 default_user_group_perm = v.OneOf(user_group_perms_choices)
505 return _ObjectPermissionsForm
505 return _ObjectPermissionsForm
506
506
507
507
508 def UserPermissionsForm(localizer, create_choices, create_on_write_choices,
508 def UserPermissionsForm(localizer, create_choices, create_on_write_choices,
509 repo_group_create_choices, user_group_create_choices,
509 repo_group_create_choices, user_group_create_choices,
510 fork_choices, inherit_default_permissions_choices):
510 fork_choices, inherit_default_permissions_choices):
511 _ = localizer
511 _ = localizer
512
512
513 class _DefaultPermissionsForm(formencode.Schema):
513 class _DefaultPermissionsForm(formencode.Schema):
514 allow_extra_fields = True
514 allow_extra_fields = True
515 filter_extra_fields = True
515 filter_extra_fields = True
516
516
517 anonymous = v.StringBoolean(if_missing=False)
517 anonymous = v.StringBoolean(if_missing=False)
518
518
519 default_repo_create = v.OneOf(create_choices)
519 default_repo_create = v.OneOf(create_choices)
520 default_repo_create_on_write = v.OneOf(create_on_write_choices)
520 default_repo_create_on_write = v.OneOf(create_on_write_choices)
521 default_user_group_create = v.OneOf(user_group_create_choices)
521 default_user_group_create = v.OneOf(user_group_create_choices)
522 default_repo_group_create = v.OneOf(repo_group_create_choices)
522 default_repo_group_create = v.OneOf(repo_group_create_choices)
523 default_fork_create = v.OneOf(fork_choices)
523 default_fork_create = v.OneOf(fork_choices)
524 default_inherit_default_permissions = v.OneOf(inherit_default_permissions_choices)
524 default_inherit_default_permissions = v.OneOf(inherit_default_permissions_choices)
525 return _DefaultPermissionsForm
525 return _DefaultPermissionsForm
526
526
527
527
528 def UserIndividualPermissionsForm(localizer):
528 def UserIndividualPermissionsForm(localizer):
529 _ = localizer
529 _ = localizer
530
530
531 class _DefaultPermissionsForm(formencode.Schema):
531 class _DefaultPermissionsForm(formencode.Schema):
532 allow_extra_fields = True
532 allow_extra_fields = True
533 filter_extra_fields = True
533 filter_extra_fields = True
534
534
535 inherit_default_permissions = v.StringBoolean(if_missing=False)
535 inherit_default_permissions = v.StringBoolean(if_missing=False)
536 return _DefaultPermissionsForm
536 return _DefaultPermissionsForm
537
537
538
538
539 def DefaultsForm(localizer, edit=False, old_data=None, supported_backends=BACKENDS.keys()):
539 def DefaultsForm(localizer, edit=False, old_data=None, supported_backends=BACKENDS.keys()):
540 _ = localizer
540 _ = localizer
541 old_data = old_data or {}
541 old_data = old_data or {}
542
542
543 class _DefaultsForm(formencode.Schema):
543 class _DefaultsForm(formencode.Schema):
544 allow_extra_fields = True
544 allow_extra_fields = True
545 filter_extra_fields = True
545 filter_extra_fields = True
546 default_repo_type = v.OneOf(supported_backends)
546 default_repo_type = v.OneOf(supported_backends)
547 default_repo_private = v.StringBoolean(if_missing=False)
547 default_repo_private = v.StringBoolean(if_missing=False)
548 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
548 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
549 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
549 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
550 default_repo_enable_locking = v.StringBoolean(if_missing=False)
550 default_repo_enable_locking = v.StringBoolean(if_missing=False)
551 return _DefaultsForm
551 return _DefaultsForm
552
552
553
553
554 def AuthSettingsForm(localizer):
554 def AuthSettingsForm(localizer):
555 _ = localizer
555 _ = localizer
556
556
557 class _AuthSettingsForm(formencode.Schema):
557 class _AuthSettingsForm(formencode.Schema):
558 allow_extra_fields = True
558 allow_extra_fields = True
559 filter_extra_fields = True
559 filter_extra_fields = True
560 auth_plugins = All(v.ValidAuthPlugins(localizer),
560 auth_plugins = All(v.ValidAuthPlugins(localizer),
561 v.UniqueListFromString(localizer)(not_empty=True))
561 v.UniqueListFromString(localizer)(not_empty=True))
562 return _AuthSettingsForm
562 return _AuthSettingsForm
563
563
564
564
565 def UserExtraEmailForm(localizer):
565 def UserExtraEmailForm(localizer):
566 _ = localizer
566 _ = localizer
567
567
568 class _UserExtraEmailForm(formencode.Schema):
568 class _UserExtraEmailForm(formencode.Schema):
569 email = All(v.UniqSystemEmail(localizer), v.Email(not_empty=True))
569 email = All(v.UniqSystemEmail(localizer), v.Email(not_empty=True))
570 return _UserExtraEmailForm
570 return _UserExtraEmailForm
571
571
572
572
573 def UserExtraIpForm(localizer):
573 def UserExtraIpForm(localizer):
574 _ = localizer
574 _ = localizer
575
575
576 class _UserExtraIpForm(formencode.Schema):
576 class _UserExtraIpForm(formencode.Schema):
577 ip = v.ValidIp(localizer)(not_empty=True)
577 ip = v.ValidIp(localizer)(not_empty=True)
578 return _UserExtraIpForm
578 return _UserExtraIpForm
579
579
580
580
581 def PullRequestForm(localizer, repo_id):
581 def PullRequestForm(localizer, repo_id):
582 _ = localizer
582 _ = localizer
583
583
584 class ReviewerForm(formencode.Schema):
584 class ReviewerForm(formencode.Schema):
585 user_id = v.Int(not_empty=True)
585 user_id = v.Int(not_empty=True)
586 reasons = All()
586 reasons = All()
587 mandatory = v.StringBoolean()
587 mandatory = v.StringBoolean()
588
588
589 class _PullRequestForm(formencode.Schema):
589 class _PullRequestForm(formencode.Schema):
590 allow_extra_fields = True
590 allow_extra_fields = True
591 filter_extra_fields = True
591 filter_extra_fields = True
592
592
593 common_ancestor = v.UnicodeString(strip=True, required=True)
593 common_ancestor = v.UnicodeString(strip=True, required=True)
594 source_repo = v.UnicodeString(strip=True, required=True)
594 source_repo = v.UnicodeString(strip=True, required=True)
595 source_ref = v.UnicodeString(strip=True, required=True)
595 source_ref = v.UnicodeString(strip=True, required=True)
596 target_repo = v.UnicodeString(strip=True, required=True)
596 target_repo = v.UnicodeString(strip=True, required=True)
597 target_ref = v.UnicodeString(strip=True, required=True)
597 target_ref = v.UnicodeString(strip=True, required=True)
598 revisions = All(#v.NotReviewedRevisions(localizer, repo_id)(),
598 revisions = All(#v.NotReviewedRevisions(localizer, repo_id)(),
599 v.UniqueList(localizer)(not_empty=True))
599 v.UniqueList(localizer)(not_empty=True))
600 review_members = formencode.ForEach(ReviewerForm())
600 review_members = formencode.ForEach(ReviewerForm())
601 pullrequest_title = v.UnicodeString(strip=True, required=True)
601 pullrequest_title = v.UnicodeString(strip=True, required=True, min=3, max=255)
602 pullrequest_desc = v.UnicodeString(strip=True, required=False)
602 pullrequest_desc = v.UnicodeString(strip=True, required=False)
603
603
604 return _PullRequestForm
604 return _PullRequestForm
605
605
606
606
607 def IssueTrackerPatternsForm(localizer):
607 def IssueTrackerPatternsForm(localizer):
608 _ = localizer
608 _ = localizer
609
609
610 class _IssueTrackerPatternsForm(formencode.Schema):
610 class _IssueTrackerPatternsForm(formencode.Schema):
611 allow_extra_fields = True
611 allow_extra_fields = True
612 filter_extra_fields = False
612 filter_extra_fields = False
613 chained_validators = [v.ValidPattern(localizer)]
613 chained_validators = [v.ValidPattern(localizer)]
614 return _IssueTrackerPatternsForm
614 return _IssueTrackerPatternsForm
General Comments 0
You need to be logged in to leave comments. Login now