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