##// END OF EJS Templates
mercurial-evolve: enable evolve setting on repositories.
marcink -
r1738:4d2afefb default
parent child Browse files
Show More
@@ -1,1987 +1,1988 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 time
23 23
24 24 import rhodecode
25 25 from rhodecode.api import (
26 26 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
27 27 from rhodecode.api.utils import (
28 28 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
29 29 get_user_group_or_error, get_user_or_error, validate_repo_permissions,
30 30 get_perm_or_error, parse_args, get_origin, build_commit_data,
31 31 validate_set_owner_permissions)
32 32 from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi
33 33 from rhodecode.lib.utils2 import str2bool, time_to_datetime
34 34 from rhodecode.lib.ext_json import json
35 35 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
36 36 from rhodecode.model.changeset_status import ChangesetStatusModel
37 37 from rhodecode.model.comment import CommentsModel
38 38 from rhodecode.model.db import (
39 39 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup,
40 40 ChangesetComment)
41 41 from rhodecode.model.repo import RepoModel
42 42 from rhodecode.model.scm import ScmModel, RepoList
43 43 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
44 44 from rhodecode.model import validation_schema
45 45 from rhodecode.model.validation_schema.schemas import repo_schema
46 46
47 47 log = logging.getLogger(__name__)
48 48
49 49
50 50 @jsonrpc_method()
51 51 def get_repo(request, apiuser, repoid, cache=Optional(True)):
52 52 """
53 53 Gets an existing repository by its name or repository_id.
54 54
55 55 The members section so the output returns users groups or users
56 56 associated with that repository.
57 57
58 58 This command can only be run using an |authtoken| with admin rights,
59 59 or users with at least read rights to the |repo|.
60 60
61 61 :param apiuser: This is filled automatically from the |authtoken|.
62 62 :type apiuser: AuthUser
63 63 :param repoid: The repository name or repository id.
64 64 :type repoid: str or int
65 65 :param cache: use the cached value for last changeset
66 66 :type: cache: Optional(bool)
67 67
68 68 Example output:
69 69
70 70 .. code-block:: bash
71 71
72 72 {
73 73 "error": null,
74 74 "id": <repo_id>,
75 75 "result": {
76 76 "clone_uri": null,
77 77 "created_on": "timestamp",
78 78 "description": "repo description",
79 79 "enable_downloads": false,
80 80 "enable_locking": false,
81 81 "enable_statistics": false,
82 82 "followers": [
83 83 {
84 84 "active": true,
85 85 "admin": false,
86 86 "api_key": "****************************************",
87 87 "api_keys": [
88 88 "****************************************"
89 89 ],
90 90 "email": "user@example.com",
91 91 "emails": [
92 92 "user@example.com"
93 93 ],
94 94 "extern_name": "rhodecode",
95 95 "extern_type": "rhodecode",
96 96 "firstname": "username",
97 97 "ip_addresses": [],
98 98 "language": null,
99 99 "last_login": "2015-09-16T17:16:35.854",
100 100 "lastname": "surname",
101 101 "user_id": <user_id>,
102 102 "username": "name"
103 103 }
104 104 ],
105 105 "fork_of": "parent-repo",
106 106 "landing_rev": [
107 107 "rev",
108 108 "tip"
109 109 ],
110 110 "last_changeset": {
111 111 "author": "User <user@example.com>",
112 112 "branch": "default",
113 113 "date": "timestamp",
114 114 "message": "last commit message",
115 115 "parents": [
116 116 {
117 117 "raw_id": "commit-id"
118 118 }
119 119 ],
120 120 "raw_id": "commit-id",
121 121 "revision": <revision number>,
122 122 "short_id": "short id"
123 123 },
124 124 "lock_reason": null,
125 125 "locked_by": null,
126 126 "locked_date": null,
127 127 "members": [
128 128 {
129 129 "name": "super-admin-name",
130 130 "origin": "super-admin",
131 131 "permission": "repository.admin",
132 132 "type": "user"
133 133 },
134 134 {
135 135 "name": "owner-name",
136 136 "origin": "owner",
137 137 "permission": "repository.admin",
138 138 "type": "user"
139 139 },
140 140 {
141 141 "name": "user-group-name",
142 142 "origin": "permission",
143 143 "permission": "repository.write",
144 144 "type": "user_group"
145 145 }
146 146 ],
147 147 "owner": "owner-name",
148 148 "permissions": [
149 149 {
150 150 "name": "super-admin-name",
151 151 "origin": "super-admin",
152 152 "permission": "repository.admin",
153 153 "type": "user"
154 154 },
155 155 {
156 156 "name": "owner-name",
157 157 "origin": "owner",
158 158 "permission": "repository.admin",
159 159 "type": "user"
160 160 },
161 161 {
162 162 "name": "user-group-name",
163 163 "origin": "permission",
164 164 "permission": "repository.write",
165 165 "type": "user_group"
166 166 }
167 167 ],
168 168 "private": true,
169 169 "repo_id": 676,
170 170 "repo_name": "user-group/repo-name",
171 171 "repo_type": "hg"
172 172 }
173 173 }
174 174 """
175 175
176 176 repo = get_repo_or_error(repoid)
177 177 cache = Optional.extract(cache)
178 178
179 179 include_secrets = False
180 180 if has_superadmin_permission(apiuser):
181 181 include_secrets = True
182 182 else:
183 183 # check if we have at least read permission for this repo !
184 184 _perms = (
185 185 'repository.admin', 'repository.write', 'repository.read',)
186 186 validate_repo_permissions(apiuser, repoid, repo, _perms)
187 187
188 188 permissions = []
189 189 for _user in repo.permissions():
190 190 user_data = {
191 191 'name': _user.username,
192 192 'permission': _user.permission,
193 193 'origin': get_origin(_user),
194 194 'type': "user",
195 195 }
196 196 permissions.append(user_data)
197 197
198 198 for _user_group in repo.permission_user_groups():
199 199 user_group_data = {
200 200 'name': _user_group.users_group_name,
201 201 'permission': _user_group.permission,
202 202 'origin': get_origin(_user_group),
203 203 'type': "user_group",
204 204 }
205 205 permissions.append(user_group_data)
206 206
207 207 following_users = [
208 208 user.user.get_api_data(include_secrets=include_secrets)
209 209 for user in repo.followers]
210 210
211 211 if not cache:
212 212 repo.update_commit_cache()
213 213 data = repo.get_api_data(include_secrets=include_secrets)
214 214 data['members'] = permissions # TODO: this should be deprecated soon
215 215 data['permissions'] = permissions
216 216 data['followers'] = following_users
217 217 return data
218 218
219 219
220 220 @jsonrpc_method()
221 221 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
222 222 """
223 223 Lists all existing repositories.
224 224
225 225 This command can only be run using an |authtoken| with admin rights,
226 226 or users with at least read rights to |repos|.
227 227
228 228 :param apiuser: This is filled automatically from the |authtoken|.
229 229 :type apiuser: AuthUser
230 230 :param root: specify root repository group to fetch repositories.
231 231 filters the returned repositories to be members of given root group.
232 232 :type root: Optional(None)
233 233 :param traverse: traverse given root into subrepositories. With this flag
234 234 set to False, it will only return top-level repositories from `root`.
235 235 if root is empty it will return just top-level repositories.
236 236 :type traverse: Optional(True)
237 237
238 238
239 239 Example output:
240 240
241 241 .. code-block:: bash
242 242
243 243 id : <id_given_in_input>
244 244 result: [
245 245 {
246 246 "repo_id" : "<repo_id>",
247 247 "repo_name" : "<reponame>"
248 248 "repo_type" : "<repo_type>",
249 249 "clone_uri" : "<clone_uri>",
250 250 "private": : "<bool>",
251 251 "created_on" : "<datetimecreated>",
252 252 "description" : "<description>",
253 253 "landing_rev": "<landing_rev>",
254 254 "owner": "<repo_owner>",
255 255 "fork_of": "<name_of_fork_parent>",
256 256 "enable_downloads": "<bool>",
257 257 "enable_locking": "<bool>",
258 258 "enable_statistics": "<bool>",
259 259 },
260 260 ...
261 261 ]
262 262 error: null
263 263 """
264 264
265 265 include_secrets = has_superadmin_permission(apiuser)
266 266 _perms = ('repository.read', 'repository.write', 'repository.admin',)
267 267 extras = {'user': apiuser}
268 268
269 269 root = Optional.extract(root)
270 270 traverse = Optional.extract(traverse, binary=True)
271 271
272 272 if root:
273 273 # verify parent existance, if it's empty return an error
274 274 parent = RepoGroup.get_by_group_name(root)
275 275 if not parent:
276 276 raise JSONRPCError(
277 277 'Root repository group `{}` does not exist'.format(root))
278 278
279 279 if traverse:
280 280 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
281 281 else:
282 282 repos = RepoModel().get_repos_for_root(root=parent)
283 283 else:
284 284 if traverse:
285 285 repos = RepoModel().get_all()
286 286 else:
287 287 # return just top-level
288 288 repos = RepoModel().get_repos_for_root(root=None)
289 289
290 290 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
291 291 return [repo.get_api_data(include_secrets=include_secrets)
292 292 for repo in repo_list]
293 293
294 294
295 295 @jsonrpc_method()
296 296 def get_repo_changeset(request, apiuser, repoid, revision,
297 297 details=Optional('basic')):
298 298 """
299 299 Returns information about a changeset.
300 300
301 301 Additionally parameters define the amount of details returned by
302 302 this function.
303 303
304 304 This command can only be run using an |authtoken| with admin rights,
305 305 or users with at least read rights to the |repo|.
306 306
307 307 :param apiuser: This is filled automatically from the |authtoken|.
308 308 :type apiuser: AuthUser
309 309 :param repoid: The repository name or repository id
310 310 :type repoid: str or int
311 311 :param revision: revision for which listing should be done
312 312 :type revision: str
313 313 :param details: details can be 'basic|extended|full' full gives diff
314 314 info details like the diff itself, and number of changed files etc.
315 315 :type details: Optional(str)
316 316
317 317 """
318 318 repo = get_repo_or_error(repoid)
319 319 if not has_superadmin_permission(apiuser):
320 320 _perms = (
321 321 'repository.admin', 'repository.write', 'repository.read',)
322 322 validate_repo_permissions(apiuser, repoid, repo, _perms)
323 323
324 324 changes_details = Optional.extract(details)
325 325 _changes_details_types = ['basic', 'extended', 'full']
326 326 if changes_details not in _changes_details_types:
327 327 raise JSONRPCError(
328 328 'ret_type must be one of %s' % (
329 329 ','.join(_changes_details_types)))
330 330
331 331 pre_load = ['author', 'branch', 'date', 'message', 'parents',
332 332 'status', '_commit', '_file_paths']
333 333
334 334 try:
335 335 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
336 336 except TypeError as e:
337 337 raise JSONRPCError(e.message)
338 338 _cs_json = cs.__json__()
339 339 _cs_json['diff'] = build_commit_data(cs, changes_details)
340 340 if changes_details == 'full':
341 341 _cs_json['refs'] = {
342 342 'branches': [cs.branch],
343 343 'bookmarks': getattr(cs, 'bookmarks', []),
344 344 'tags': cs.tags
345 345 }
346 346 return _cs_json
347 347
348 348
349 349 @jsonrpc_method()
350 350 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
351 351 details=Optional('basic')):
352 352 """
353 353 Returns a set of commits limited by the number starting
354 354 from the `start_rev` option.
355 355
356 356 Additional parameters define the amount of details returned by this
357 357 function.
358 358
359 359 This command can only be run using an |authtoken| with admin rights,
360 360 or users with at least read rights to |repos|.
361 361
362 362 :param apiuser: This is filled automatically from the |authtoken|.
363 363 :type apiuser: AuthUser
364 364 :param repoid: The repository name or repository ID.
365 365 :type repoid: str or int
366 366 :param start_rev: The starting revision from where to get changesets.
367 367 :type start_rev: str
368 368 :param limit: Limit the number of commits to this amount
369 369 :type limit: str or int
370 370 :param details: Set the level of detail returned. Valid option are:
371 371 ``basic``, ``extended`` and ``full``.
372 372 :type details: Optional(str)
373 373
374 374 .. note::
375 375
376 376 Setting the parameter `details` to the value ``full`` is extensive
377 377 and returns details like the diff itself, and the number
378 378 of changed files.
379 379
380 380 """
381 381 repo = get_repo_or_error(repoid)
382 382 if not has_superadmin_permission(apiuser):
383 383 _perms = (
384 384 'repository.admin', 'repository.write', 'repository.read',)
385 385 validate_repo_permissions(apiuser, repoid, repo, _perms)
386 386
387 387 changes_details = Optional.extract(details)
388 388 _changes_details_types = ['basic', 'extended', 'full']
389 389 if changes_details not in _changes_details_types:
390 390 raise JSONRPCError(
391 391 'ret_type must be one of %s' % (
392 392 ','.join(_changes_details_types)))
393 393
394 394 limit = int(limit)
395 395 pre_load = ['author', 'branch', 'date', 'message', 'parents',
396 396 'status', '_commit', '_file_paths']
397 397
398 398 vcs_repo = repo.scm_instance()
399 399 # SVN needs a special case to distinguish its index and commit id
400 400 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
401 401 start_rev = vcs_repo.commit_ids[0]
402 402
403 403 try:
404 404 commits = vcs_repo.get_commits(
405 405 start_id=start_rev, pre_load=pre_load)
406 406 except TypeError as e:
407 407 raise JSONRPCError(e.message)
408 408 except Exception:
409 409 log.exception('Fetching of commits failed')
410 410 raise JSONRPCError('Error occurred during commit fetching')
411 411
412 412 ret = []
413 413 for cnt, commit in enumerate(commits):
414 414 if cnt >= limit != -1:
415 415 break
416 416 _cs_json = commit.__json__()
417 417 _cs_json['diff'] = build_commit_data(commit, changes_details)
418 418 if changes_details == 'full':
419 419 _cs_json['refs'] = {
420 420 'branches': [commit.branch],
421 421 'bookmarks': getattr(commit, 'bookmarks', []),
422 422 'tags': commit.tags
423 423 }
424 424 ret.append(_cs_json)
425 425 return ret
426 426
427 427
428 428 @jsonrpc_method()
429 429 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
430 430 ret_type=Optional('all'), details=Optional('basic'),
431 431 max_file_bytes=Optional(None)):
432 432 """
433 433 Returns a list of nodes and children in a flat list for a given
434 434 path at given revision.
435 435
436 436 It's possible to specify ret_type to show only `files` or `dirs`.
437 437
438 438 This command can only be run using an |authtoken| with admin rights,
439 439 or users with at least read rights to |repos|.
440 440
441 441 :param apiuser: This is filled automatically from the |authtoken|.
442 442 :type apiuser: AuthUser
443 443 :param repoid: The repository name or repository ID.
444 444 :type repoid: str or int
445 445 :param revision: The revision for which listing should be done.
446 446 :type revision: str
447 447 :param root_path: The path from which to start displaying.
448 448 :type root_path: str
449 449 :param ret_type: Set the return type. Valid options are
450 450 ``all`` (default), ``files`` and ``dirs``.
451 451 :type ret_type: Optional(str)
452 452 :param details: Returns extended information about nodes, such as
453 453 md5, binary, and or content. The valid options are ``basic`` and
454 454 ``full``.
455 455 :type details: Optional(str)
456 456 :param max_file_bytes: Only return file content under this file size bytes
457 457 :type details: Optional(int)
458 458
459 459 Example output:
460 460
461 461 .. code-block:: bash
462 462
463 463 id : <id_given_in_input>
464 464 result: [
465 465 {
466 466 "name" : "<name>"
467 467 "type" : "<type>",
468 468 "binary": "<true|false>" (only in extended mode)
469 469 "md5" : "<md5 of file content>" (only in extended mode)
470 470 },
471 471 ...
472 472 ]
473 473 error: null
474 474 """
475 475
476 476 repo = get_repo_or_error(repoid)
477 477 if not has_superadmin_permission(apiuser):
478 478 _perms = (
479 479 'repository.admin', 'repository.write', 'repository.read',)
480 480 validate_repo_permissions(apiuser, repoid, repo, _perms)
481 481
482 482 ret_type = Optional.extract(ret_type)
483 483 details = Optional.extract(details)
484 484 _extended_types = ['basic', 'full']
485 485 if details not in _extended_types:
486 486 raise JSONRPCError(
487 487 'ret_type must be one of %s' % (','.join(_extended_types)))
488 488 extended_info = False
489 489 content = False
490 490 if details == 'basic':
491 491 extended_info = True
492 492
493 493 if details == 'full':
494 494 extended_info = content = True
495 495
496 496 _map = {}
497 497 try:
498 498 # check if repo is not empty by any chance, skip quicker if it is.
499 499 _scm = repo.scm_instance()
500 500 if _scm.is_empty():
501 501 return []
502 502
503 503 _d, _f = ScmModel().get_nodes(
504 504 repo, revision, root_path, flat=False,
505 505 extended_info=extended_info, content=content,
506 506 max_file_bytes=max_file_bytes)
507 507 _map = {
508 508 'all': _d + _f,
509 509 'files': _f,
510 510 'dirs': _d,
511 511 }
512 512 return _map[ret_type]
513 513 except KeyError:
514 514 raise JSONRPCError(
515 515 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
516 516 except Exception:
517 517 log.exception("Exception occurred while trying to get repo nodes")
518 518 raise JSONRPCError(
519 519 'failed to get repo: `%s` nodes' % repo.repo_name
520 520 )
521 521
522 522
523 523 @jsonrpc_method()
524 524 def get_repo_refs(request, apiuser, repoid):
525 525 """
526 526 Returns a dictionary of current references. It returns
527 527 bookmarks, branches, closed_branches, and tags for given repository
528 528
529 529 It's possible to specify ret_type to show only `files` or `dirs`.
530 530
531 531 This command can only be run using an |authtoken| with admin rights,
532 532 or users with at least read rights to |repos|.
533 533
534 534 :param apiuser: This is filled automatically from the |authtoken|.
535 535 :type apiuser: AuthUser
536 536 :param repoid: The repository name or repository ID.
537 537 :type repoid: str or int
538 538
539 539 Example output:
540 540
541 541 .. code-block:: bash
542 542
543 543 id : <id_given_in_input>
544 544 "result": {
545 545 "bookmarks": {
546 546 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
547 547 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
548 548 },
549 549 "branches": {
550 550 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
551 551 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
552 552 },
553 553 "branches_closed": {},
554 554 "tags": {
555 555 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
556 556 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
557 557 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
558 558 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
559 559 }
560 560 }
561 561 error: null
562 562 """
563 563
564 564 repo = get_repo_or_error(repoid)
565 565 if not has_superadmin_permission(apiuser):
566 566 _perms = ('repository.admin', 'repository.write', 'repository.read',)
567 567 validate_repo_permissions(apiuser, repoid, repo, _perms)
568 568
569 569 try:
570 570 # check if repo is not empty by any chance, skip quicker if it is.
571 571 vcs_instance = repo.scm_instance()
572 572 refs = vcs_instance.refs()
573 573 return refs
574 574 except Exception:
575 575 log.exception("Exception occurred while trying to get repo refs")
576 576 raise JSONRPCError(
577 577 'failed to get repo: `%s` references' % repo.repo_name
578 578 )
579 579
580 580
581 581 @jsonrpc_method()
582 582 def create_repo(
583 583 request, apiuser, repo_name, repo_type,
584 584 owner=Optional(OAttr('apiuser')),
585 585 description=Optional(''),
586 586 private=Optional(False),
587 587 clone_uri=Optional(None),
588 588 landing_rev=Optional('rev:tip'),
589 589 enable_statistics=Optional(False),
590 590 enable_locking=Optional(False),
591 591 enable_downloads=Optional(False),
592 592 copy_permissions=Optional(False)):
593 593 """
594 594 Creates a repository.
595 595
596 596 * If the repository name contains "/", repository will be created inside
597 597 a repository group or nested repository groups
598 598
599 599 For example "foo/bar/repo1" will create |repo| called "repo1" inside
600 600 group "foo/bar". You have to have permissions to access and write to
601 601 the last repository group ("bar" in this example)
602 602
603 603 This command can only be run using an |authtoken| with at least
604 604 permissions to create repositories, or write permissions to
605 605 parent repository groups.
606 606
607 607 :param apiuser: This is filled automatically from the |authtoken|.
608 608 :type apiuser: AuthUser
609 609 :param repo_name: Set the repository name.
610 610 :type repo_name: str
611 611 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
612 612 :type repo_type: str
613 613 :param owner: user_id or username
614 614 :type owner: Optional(str)
615 615 :param description: Set the repository description.
616 616 :type description: Optional(str)
617 617 :param private: set repository as private
618 618 :type private: bool
619 619 :param clone_uri: set clone_uri
620 620 :type clone_uri: str
621 621 :param landing_rev: <rev_type>:<rev>
622 622 :type landing_rev: str
623 623 :param enable_locking:
624 624 :type enable_locking: bool
625 625 :param enable_downloads:
626 626 :type enable_downloads: bool
627 627 :param enable_statistics:
628 628 :type enable_statistics: bool
629 629 :param copy_permissions: Copy permission from group in which the
630 630 repository is being created.
631 631 :type copy_permissions: bool
632 632
633 633
634 634 Example output:
635 635
636 636 .. code-block:: bash
637 637
638 638 id : <id_given_in_input>
639 639 result: {
640 640 "msg": "Created new repository `<reponame>`",
641 641 "success": true,
642 642 "task": "<celery task id or None if done sync>"
643 643 }
644 644 error: null
645 645
646 646
647 647 Example error output:
648 648
649 649 .. code-block:: bash
650 650
651 651 id : <id_given_in_input>
652 652 result : null
653 653 error : {
654 654 'failed to create repository `<repo_name>`'
655 655 }
656 656
657 657 """
658 658
659 659 owner = validate_set_owner_permissions(apiuser, owner)
660 660
661 661 description = Optional.extract(description)
662 662 copy_permissions = Optional.extract(copy_permissions)
663 663 clone_uri = Optional.extract(clone_uri)
664 664 landing_commit_ref = Optional.extract(landing_rev)
665 665
666 666 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
667 667 if isinstance(private, Optional):
668 668 private = defs.get('repo_private') or Optional.extract(private)
669 669 if isinstance(repo_type, Optional):
670 670 repo_type = defs.get('repo_type')
671 671 if isinstance(enable_statistics, Optional):
672 672 enable_statistics = defs.get('repo_enable_statistics')
673 673 if isinstance(enable_locking, Optional):
674 674 enable_locking = defs.get('repo_enable_locking')
675 675 if isinstance(enable_downloads, Optional):
676 676 enable_downloads = defs.get('repo_enable_downloads')
677 677
678 678 schema = repo_schema.RepoSchema().bind(
679 679 repo_type_options=rhodecode.BACKENDS.keys(),
680 680 # user caller
681 681 user=apiuser)
682 682
683 683 try:
684 684 schema_data = schema.deserialize(dict(
685 685 repo_name=repo_name,
686 686 repo_type=repo_type,
687 687 repo_owner=owner.username,
688 688 repo_description=description,
689 689 repo_landing_commit_ref=landing_commit_ref,
690 690 repo_clone_uri=clone_uri,
691 691 repo_private=private,
692 692 repo_copy_permissions=copy_permissions,
693 693 repo_enable_statistics=enable_statistics,
694 694 repo_enable_downloads=enable_downloads,
695 695 repo_enable_locking=enable_locking))
696 696 except validation_schema.Invalid as err:
697 697 raise JSONRPCValidationError(colander_exc=err)
698 698
699 699 try:
700 700 data = {
701 701 'owner': owner,
702 702 'repo_name': schema_data['repo_group']['repo_name_without_group'],
703 703 'repo_name_full': schema_data['repo_name'],
704 704 'repo_group': schema_data['repo_group']['repo_group_id'],
705 705 'repo_type': schema_data['repo_type'],
706 706 'repo_description': schema_data['repo_description'],
707 707 'repo_private': schema_data['repo_private'],
708 708 'clone_uri': schema_data['repo_clone_uri'],
709 709 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
710 710 'enable_statistics': schema_data['repo_enable_statistics'],
711 711 'enable_locking': schema_data['repo_enable_locking'],
712 712 'enable_downloads': schema_data['repo_enable_downloads'],
713 713 'repo_copy_permissions': schema_data['repo_copy_permissions'],
714 714 }
715 715
716 716 task = RepoModel().create(form_data=data, cur_user=owner)
717 717 from celery.result import BaseAsyncResult
718 718 task_id = None
719 719 if isinstance(task, BaseAsyncResult):
720 720 task_id = task.task_id
721 721 # no commit, it's done in RepoModel, or async via celery
722 722 return {
723 723 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
724 724 'success': True, # cannot return the repo data here since fork
725 725 # can be done async
726 726 'task': task_id
727 727 }
728 728 except Exception:
729 729 log.exception(
730 730 u"Exception while trying to create the repository %s",
731 731 schema_data['repo_name'])
732 732 raise JSONRPCError(
733 733 'failed to create repository `%s`' % (schema_data['repo_name'],))
734 734
735 735
736 736 @jsonrpc_method()
737 737 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
738 738 description=Optional('')):
739 739 """
740 740 Adds an extra field to a repository.
741 741
742 742 This command can only be run using an |authtoken| with at least
743 743 write permissions to the |repo|.
744 744
745 745 :param apiuser: This is filled automatically from the |authtoken|.
746 746 :type apiuser: AuthUser
747 747 :param repoid: Set the repository name or repository id.
748 748 :type repoid: str or int
749 749 :param key: Create a unique field key for this repository.
750 750 :type key: str
751 751 :param label:
752 752 :type label: Optional(str)
753 753 :param description:
754 754 :type description: Optional(str)
755 755 """
756 756 repo = get_repo_or_error(repoid)
757 757 if not has_superadmin_permission(apiuser):
758 758 _perms = ('repository.admin',)
759 759 validate_repo_permissions(apiuser, repoid, repo, _perms)
760 760
761 761 label = Optional.extract(label) or key
762 762 description = Optional.extract(description)
763 763
764 764 field = RepositoryField.get_by_key_name(key, repo)
765 765 if field:
766 766 raise JSONRPCError('Field with key '
767 767 '`%s` exists for repo `%s`' % (key, repoid))
768 768
769 769 try:
770 770 RepoModel().add_repo_field(repo, key, field_label=label,
771 771 field_desc=description)
772 772 Session().commit()
773 773 return {
774 774 'msg': "Added new repository field `%s`" % (key,),
775 775 'success': True,
776 776 }
777 777 except Exception:
778 778 log.exception("Exception occurred while trying to add field to repo")
779 779 raise JSONRPCError(
780 780 'failed to create new field for repository `%s`' % (repoid,))
781 781
782 782
783 783 @jsonrpc_method()
784 784 def remove_field_from_repo(request, apiuser, repoid, key):
785 785 """
786 786 Removes an extra field from a repository.
787 787
788 788 This command can only be run using an |authtoken| with at least
789 789 write permissions to the |repo|.
790 790
791 791 :param apiuser: This is filled automatically from the |authtoken|.
792 792 :type apiuser: AuthUser
793 793 :param repoid: Set the repository name or repository ID.
794 794 :type repoid: str or int
795 795 :param key: Set the unique field key for this repository.
796 796 :type key: str
797 797 """
798 798
799 799 repo = get_repo_or_error(repoid)
800 800 if not has_superadmin_permission(apiuser):
801 801 _perms = ('repository.admin',)
802 802 validate_repo_permissions(apiuser, repoid, repo, _perms)
803 803
804 804 field = RepositoryField.get_by_key_name(key, repo)
805 805 if not field:
806 806 raise JSONRPCError('Field with key `%s` does not '
807 807 'exists for repo `%s`' % (key, repoid))
808 808
809 809 try:
810 810 RepoModel().delete_repo_field(repo, field_key=key)
811 811 Session().commit()
812 812 return {
813 813 'msg': "Deleted repository field `%s`" % (key,),
814 814 'success': True,
815 815 }
816 816 except Exception:
817 817 log.exception(
818 818 "Exception occurred while trying to delete field from repo")
819 819 raise JSONRPCError(
820 820 'failed to delete field for repository `%s`' % (repoid,))
821 821
822 822
823 823 @jsonrpc_method()
824 824 def update_repo(
825 825 request, apiuser, repoid, repo_name=Optional(None),
826 826 owner=Optional(OAttr('apiuser')), description=Optional(''),
827 827 private=Optional(False), clone_uri=Optional(None),
828 828 landing_rev=Optional('rev:tip'), fork_of=Optional(None),
829 829 enable_statistics=Optional(False),
830 830 enable_locking=Optional(False),
831 831 enable_downloads=Optional(False), fields=Optional('')):
832 832 """
833 833 Updates a repository with the given information.
834 834
835 835 This command can only be run using an |authtoken| with at least
836 836 admin permissions to the |repo|.
837 837
838 838 * If the repository name contains "/", repository will be updated
839 839 accordingly with a repository group or nested repository groups
840 840
841 841 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
842 842 called "repo-test" and place it inside group "foo/bar".
843 843 You have to have permissions to access and write to the last repository
844 844 group ("bar" in this example)
845 845
846 846 :param apiuser: This is filled automatically from the |authtoken|.
847 847 :type apiuser: AuthUser
848 848 :param repoid: repository name or repository ID.
849 849 :type repoid: str or int
850 850 :param repo_name: Update the |repo| name, including the
851 851 repository group it's in.
852 852 :type repo_name: str
853 853 :param owner: Set the |repo| owner.
854 854 :type owner: str
855 855 :param fork_of: Set the |repo| as fork of another |repo|.
856 856 :type fork_of: str
857 857 :param description: Update the |repo| description.
858 858 :type description: str
859 859 :param private: Set the |repo| as private. (True | False)
860 860 :type private: bool
861 861 :param clone_uri: Update the |repo| clone URI.
862 862 :type clone_uri: str
863 863 :param landing_rev: Set the |repo| landing revision. Default is ``rev:tip``.
864 864 :type landing_rev: str
865 865 :param enable_statistics: Enable statistics on the |repo|, (True | False).
866 866 :type enable_statistics: bool
867 867 :param enable_locking: Enable |repo| locking.
868 868 :type enable_locking: bool
869 869 :param enable_downloads: Enable downloads from the |repo|, (True | False).
870 870 :type enable_downloads: bool
871 871 :param fields: Add extra fields to the |repo|. Use the following
872 872 example format: ``field_key=field_val,field_key2=fieldval2``.
873 873 Escape ', ' with \,
874 874 :type fields: str
875 875 """
876 876
877 877 repo = get_repo_or_error(repoid)
878 878
879 879 include_secrets = False
880 880 if not has_superadmin_permission(apiuser):
881 881 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
882 882 else:
883 883 include_secrets = True
884 884
885 885 updates = dict(
886 886 repo_name=repo_name
887 887 if not isinstance(repo_name, Optional) else repo.repo_name,
888 888
889 889 fork_id=fork_of
890 890 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
891 891
892 892 user=owner
893 893 if not isinstance(owner, Optional) else repo.user.username,
894 894
895 895 repo_description=description
896 896 if not isinstance(description, Optional) else repo.description,
897 897
898 898 repo_private=private
899 899 if not isinstance(private, Optional) else repo.private,
900 900
901 901 clone_uri=clone_uri
902 902 if not isinstance(clone_uri, Optional) else repo.clone_uri,
903 903
904 904 repo_landing_rev=landing_rev
905 905 if not isinstance(landing_rev, Optional) else repo._landing_revision,
906 906
907 907 repo_enable_statistics=enable_statistics
908 908 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
909 909
910 910 repo_enable_locking=enable_locking
911 911 if not isinstance(enable_locking, Optional) else repo.enable_locking,
912 912
913 913 repo_enable_downloads=enable_downloads
914 914 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
915 915
916 916 ref_choices, _labels = ScmModel().get_repo_landing_revs(repo=repo)
917 917
918 918 schema = repo_schema.RepoSchema().bind(
919 919 repo_type_options=rhodecode.BACKENDS.keys(),
920 920 repo_ref_options=ref_choices,
921 921 # user caller
922 922 user=apiuser,
923 923 old_values=repo.get_api_data())
924 924 try:
925 925 schema_data = schema.deserialize(dict(
926 926 # we save old value, users cannot change type
927 927 repo_type=repo.repo_type,
928 928
929 929 repo_name=updates['repo_name'],
930 930 repo_owner=updates['user'],
931 931 repo_description=updates['repo_description'],
932 932 repo_clone_uri=updates['clone_uri'],
933 933 repo_fork_of=updates['fork_id'],
934 934 repo_private=updates['repo_private'],
935 935 repo_landing_commit_ref=updates['repo_landing_rev'],
936 936 repo_enable_statistics=updates['repo_enable_statistics'],
937 937 repo_enable_downloads=updates['repo_enable_downloads'],
938 938 repo_enable_locking=updates['repo_enable_locking']))
939 939 except validation_schema.Invalid as err:
940 940 raise JSONRPCValidationError(colander_exc=err)
941 941
942 942 # save validated data back into the updates dict
943 943 validated_updates = dict(
944 944 repo_name=schema_data['repo_group']['repo_name_without_group'],
945 945 repo_group=schema_data['repo_group']['repo_group_id'],
946 946
947 947 user=schema_data['repo_owner'],
948 948 repo_description=schema_data['repo_description'],
949 949 repo_private=schema_data['repo_private'],
950 950 clone_uri=schema_data['repo_clone_uri'],
951 951 repo_landing_rev=schema_data['repo_landing_commit_ref'],
952 952 repo_enable_statistics=schema_data['repo_enable_statistics'],
953 953 repo_enable_locking=schema_data['repo_enable_locking'],
954 954 repo_enable_downloads=schema_data['repo_enable_downloads'],
955 955 )
956 956
957 957 if schema_data['repo_fork_of']:
958 958 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
959 959 validated_updates['fork_id'] = fork_repo.repo_id
960 960
961 961 # extra fields
962 962 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
963 963 if fields:
964 964 validated_updates.update(fields)
965 965
966 966 try:
967 967 RepoModel().update(repo, **validated_updates)
968 968 Session().commit()
969 969 return {
970 970 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
971 971 'repository': repo.get_api_data(include_secrets=include_secrets)
972 972 }
973 973 except Exception:
974 974 log.exception(
975 975 u"Exception while trying to update the repository %s",
976 976 repoid)
977 977 raise JSONRPCError('failed to update repo `%s`' % repoid)
978 978
979 979
980 980 @jsonrpc_method()
981 981 def fork_repo(request, apiuser, repoid, fork_name,
982 982 owner=Optional(OAttr('apiuser')),
983 983 description=Optional(''),
984 984 private=Optional(False),
985 985 clone_uri=Optional(None),
986 986 landing_rev=Optional('rev:tip'),
987 987 copy_permissions=Optional(False)):
988 988 """
989 989 Creates a fork of the specified |repo|.
990 990
991 991 * If the fork_name contains "/", fork will be created inside
992 992 a repository group or nested repository groups
993 993
994 994 For example "foo/bar/fork-repo" will create fork called "fork-repo"
995 995 inside group "foo/bar". You have to have permissions to access and
996 996 write to the last repository group ("bar" in this example)
997 997
998 998 This command can only be run using an |authtoken| with minimum
999 999 read permissions of the forked repo, create fork permissions for an user.
1000 1000
1001 1001 :param apiuser: This is filled automatically from the |authtoken|.
1002 1002 :type apiuser: AuthUser
1003 1003 :param repoid: Set repository name or repository ID.
1004 1004 :type repoid: str or int
1005 1005 :param fork_name: Set the fork name, including it's repository group membership.
1006 1006 :type fork_name: str
1007 1007 :param owner: Set the fork owner.
1008 1008 :type owner: str
1009 1009 :param description: Set the fork description.
1010 1010 :type description: str
1011 1011 :param copy_permissions: Copy permissions from parent |repo|. The
1012 1012 default is False.
1013 1013 :type copy_permissions: bool
1014 1014 :param private: Make the fork private. The default is False.
1015 1015 :type private: bool
1016 1016 :param landing_rev: Set the landing revision. The default is tip.
1017 1017
1018 1018 Example output:
1019 1019
1020 1020 .. code-block:: bash
1021 1021
1022 1022 id : <id_for_response>
1023 1023 api_key : "<api_key>"
1024 1024 args: {
1025 1025 "repoid" : "<reponame or repo_id>",
1026 1026 "fork_name": "<forkname>",
1027 1027 "owner": "<username or user_id = Optional(=apiuser)>",
1028 1028 "description": "<description>",
1029 1029 "copy_permissions": "<bool>",
1030 1030 "private": "<bool>",
1031 1031 "landing_rev": "<landing_rev>"
1032 1032 }
1033 1033
1034 1034 Example error output:
1035 1035
1036 1036 .. code-block:: bash
1037 1037
1038 1038 id : <id_given_in_input>
1039 1039 result: {
1040 1040 "msg": "Created fork of `<reponame>` as `<forkname>`",
1041 1041 "success": true,
1042 1042 "task": "<celery task id or None if done sync>"
1043 1043 }
1044 1044 error: null
1045 1045
1046 1046 """
1047 1047
1048 1048 repo = get_repo_or_error(repoid)
1049 1049 repo_name = repo.repo_name
1050 1050
1051 1051 if not has_superadmin_permission(apiuser):
1052 1052 # check if we have at least read permission for
1053 1053 # this repo that we fork !
1054 1054 _perms = (
1055 1055 'repository.admin', 'repository.write', 'repository.read')
1056 1056 validate_repo_permissions(apiuser, repoid, repo, _perms)
1057 1057
1058 1058 # check if the regular user has at least fork permissions as well
1059 1059 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1060 1060 raise JSONRPCForbidden()
1061 1061
1062 1062 # check if user can set owner parameter
1063 1063 owner = validate_set_owner_permissions(apiuser, owner)
1064 1064
1065 1065 description = Optional.extract(description)
1066 1066 copy_permissions = Optional.extract(copy_permissions)
1067 1067 clone_uri = Optional.extract(clone_uri)
1068 1068 landing_commit_ref = Optional.extract(landing_rev)
1069 1069 private = Optional.extract(private)
1070 1070
1071 1071 schema = repo_schema.RepoSchema().bind(
1072 1072 repo_type_options=rhodecode.BACKENDS.keys(),
1073 1073 # user caller
1074 1074 user=apiuser)
1075 1075
1076 1076 try:
1077 1077 schema_data = schema.deserialize(dict(
1078 1078 repo_name=fork_name,
1079 1079 repo_type=repo.repo_type,
1080 1080 repo_owner=owner.username,
1081 1081 repo_description=description,
1082 1082 repo_landing_commit_ref=landing_commit_ref,
1083 1083 repo_clone_uri=clone_uri,
1084 1084 repo_private=private,
1085 1085 repo_copy_permissions=copy_permissions))
1086 1086 except validation_schema.Invalid as err:
1087 1087 raise JSONRPCValidationError(colander_exc=err)
1088 1088
1089 1089 try:
1090 1090 data = {
1091 1091 'fork_parent_id': repo.repo_id,
1092 1092
1093 1093 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1094 1094 'repo_name_full': schema_data['repo_name'],
1095 1095 'repo_group': schema_data['repo_group']['repo_group_id'],
1096 1096 'repo_type': schema_data['repo_type'],
1097 1097 'description': schema_data['repo_description'],
1098 1098 'private': schema_data['repo_private'],
1099 1099 'copy_permissions': schema_data['repo_copy_permissions'],
1100 1100 'landing_rev': schema_data['repo_landing_commit_ref'],
1101 1101 }
1102 1102
1103 1103 task = RepoModel().create_fork(data, cur_user=owner)
1104 1104 # no commit, it's done in RepoModel, or async via celery
1105 1105 from celery.result import BaseAsyncResult
1106 1106 task_id = None
1107 1107 if isinstance(task, BaseAsyncResult):
1108 1108 task_id = task.task_id
1109 1109 return {
1110 1110 'msg': 'Created fork of `%s` as `%s`' % (
1111 1111 repo.repo_name, schema_data['repo_name']),
1112 1112 'success': True, # cannot return the repo data here since fork
1113 1113 # can be done async
1114 1114 'task': task_id
1115 1115 }
1116 1116 except Exception:
1117 1117 log.exception(
1118 1118 u"Exception while trying to create fork %s",
1119 1119 schema_data['repo_name'])
1120 1120 raise JSONRPCError(
1121 1121 'failed to fork repository `%s` as `%s`' % (
1122 1122 repo_name, schema_data['repo_name']))
1123 1123
1124 1124
1125 1125 @jsonrpc_method()
1126 1126 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1127 1127 """
1128 1128 Deletes a repository.
1129 1129
1130 1130 * When the `forks` parameter is set it's possible to detach or delete
1131 1131 forks of deleted repository.
1132 1132
1133 1133 This command can only be run using an |authtoken| with admin
1134 1134 permissions on the |repo|.
1135 1135
1136 1136 :param apiuser: This is filled automatically from the |authtoken|.
1137 1137 :type apiuser: AuthUser
1138 1138 :param repoid: Set the repository name or repository ID.
1139 1139 :type repoid: str or int
1140 1140 :param forks: Set to `detach` or `delete` forks from the |repo|.
1141 1141 :type forks: Optional(str)
1142 1142
1143 1143 Example error output:
1144 1144
1145 1145 .. code-block:: bash
1146 1146
1147 1147 id : <id_given_in_input>
1148 1148 result: {
1149 1149 "msg": "Deleted repository `<reponame>`",
1150 1150 "success": true
1151 1151 }
1152 1152 error: null
1153 1153 """
1154 1154
1155 1155 repo = get_repo_or_error(repoid)
1156 1156 if not has_superadmin_permission(apiuser):
1157 1157 _perms = ('repository.admin',)
1158 1158 validate_repo_permissions(apiuser, repoid, repo, _perms)
1159 1159
1160 1160 try:
1161 1161 handle_forks = Optional.extract(forks)
1162 1162 _forks_msg = ''
1163 1163 _forks = [f for f in repo.forks]
1164 1164 if handle_forks == 'detach':
1165 1165 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1166 1166 elif handle_forks == 'delete':
1167 1167 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1168 1168 elif _forks:
1169 1169 raise JSONRPCError(
1170 1170 'Cannot delete `%s` it still contains attached forks' %
1171 1171 (repo.repo_name,)
1172 1172 )
1173 1173
1174 1174 RepoModel().delete(repo, forks=forks)
1175 1175 Session().commit()
1176 1176 return {
1177 1177 'msg': 'Deleted repository `%s`%s' % (
1178 1178 repo.repo_name, _forks_msg),
1179 1179 'success': True
1180 1180 }
1181 1181 except Exception:
1182 1182 log.exception("Exception occurred while trying to delete repo")
1183 1183 raise JSONRPCError(
1184 1184 'failed to delete repository `%s`' % (repo.repo_name,)
1185 1185 )
1186 1186
1187 1187
1188 1188 #TODO: marcink, change name ?
1189 1189 @jsonrpc_method()
1190 1190 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1191 1191 """
1192 1192 Invalidates the cache for the specified repository.
1193 1193
1194 1194 This command can only be run using an |authtoken| with admin rights to
1195 1195 the specified repository.
1196 1196
1197 1197 This command takes the following options:
1198 1198
1199 1199 :param apiuser: This is filled automatically from |authtoken|.
1200 1200 :type apiuser: AuthUser
1201 1201 :param repoid: Sets the repository name or repository ID.
1202 1202 :type repoid: str or int
1203 1203 :param delete_keys: This deletes the invalidated keys instead of
1204 1204 just flagging them.
1205 1205 :type delete_keys: Optional(``True`` | ``False``)
1206 1206
1207 1207 Example output:
1208 1208
1209 1209 .. code-block:: bash
1210 1210
1211 1211 id : <id_given_in_input>
1212 1212 result : {
1213 1213 'msg': Cache for repository `<repository name>` was invalidated,
1214 1214 'repository': <repository name>
1215 1215 }
1216 1216 error : null
1217 1217
1218 1218 Example error output:
1219 1219
1220 1220 .. code-block:: bash
1221 1221
1222 1222 id : <id_given_in_input>
1223 1223 result : null
1224 1224 error : {
1225 1225 'Error occurred during cache invalidation action'
1226 1226 }
1227 1227
1228 1228 """
1229 1229
1230 1230 repo = get_repo_or_error(repoid)
1231 1231 if not has_superadmin_permission(apiuser):
1232 1232 _perms = ('repository.admin', 'repository.write',)
1233 1233 validate_repo_permissions(apiuser, repoid, repo, _perms)
1234 1234
1235 1235 delete = Optional.extract(delete_keys)
1236 1236 try:
1237 1237 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1238 1238 return {
1239 1239 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1240 1240 'repository': repo.repo_name
1241 1241 }
1242 1242 except Exception:
1243 1243 log.exception(
1244 1244 "Exception occurred while trying to invalidate repo cache")
1245 1245 raise JSONRPCError(
1246 1246 'Error occurred during cache invalidation action'
1247 1247 )
1248 1248
1249 1249
1250 1250 #TODO: marcink, change name ?
1251 1251 @jsonrpc_method()
1252 1252 def lock(request, apiuser, repoid, locked=Optional(None),
1253 1253 userid=Optional(OAttr('apiuser'))):
1254 1254 """
1255 1255 Sets the lock state of the specified |repo| by the given user.
1256 1256 From more information, see :ref:`repo-locking`.
1257 1257
1258 1258 * If the ``userid`` option is not set, the repository is locked to the
1259 1259 user who called the method.
1260 1260 * If the ``locked`` parameter is not set, the current lock state of the
1261 1261 repository is displayed.
1262 1262
1263 1263 This command can only be run using an |authtoken| with admin rights to
1264 1264 the specified repository.
1265 1265
1266 1266 This command takes the following options:
1267 1267
1268 1268 :param apiuser: This is filled automatically from the |authtoken|.
1269 1269 :type apiuser: AuthUser
1270 1270 :param repoid: Sets the repository name or repository ID.
1271 1271 :type repoid: str or int
1272 1272 :param locked: Sets the lock state.
1273 1273 :type locked: Optional(``True`` | ``False``)
1274 1274 :param userid: Set the repository lock to this user.
1275 1275 :type userid: Optional(str or int)
1276 1276
1277 1277 Example error output:
1278 1278
1279 1279 .. code-block:: bash
1280 1280
1281 1281 id : <id_given_in_input>
1282 1282 result : {
1283 1283 'repo': '<reponame>',
1284 1284 'locked': <bool: lock state>,
1285 1285 'locked_since': <int: lock timestamp>,
1286 1286 'locked_by': <username of person who made the lock>,
1287 1287 'lock_reason': <str: reason for locking>,
1288 1288 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1289 1289 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1290 1290 or
1291 1291 'msg': 'Repo `<repository name>` not locked.'
1292 1292 or
1293 1293 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1294 1294 }
1295 1295 error : null
1296 1296
1297 1297 Example error output:
1298 1298
1299 1299 .. code-block:: bash
1300 1300
1301 1301 id : <id_given_in_input>
1302 1302 result : null
1303 1303 error : {
1304 1304 'Error occurred locking repository `<reponame>`'
1305 1305 }
1306 1306 """
1307 1307
1308 1308 repo = get_repo_or_error(repoid)
1309 1309 if not has_superadmin_permission(apiuser):
1310 1310 # check if we have at least write permission for this repo !
1311 1311 _perms = ('repository.admin', 'repository.write',)
1312 1312 validate_repo_permissions(apiuser, repoid, repo, _perms)
1313 1313
1314 1314 # make sure normal user does not pass someone else userid,
1315 1315 # he is not allowed to do that
1316 1316 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1317 1317 raise JSONRPCError('userid is not the same as your user')
1318 1318
1319 1319 if isinstance(userid, Optional):
1320 1320 userid = apiuser.user_id
1321 1321
1322 1322 user = get_user_or_error(userid)
1323 1323
1324 1324 if isinstance(locked, Optional):
1325 1325 lockobj = repo.locked
1326 1326
1327 1327 if lockobj[0] is None:
1328 1328 _d = {
1329 1329 'repo': repo.repo_name,
1330 1330 'locked': False,
1331 1331 'locked_since': None,
1332 1332 'locked_by': None,
1333 1333 'lock_reason': None,
1334 1334 'lock_state_changed': False,
1335 1335 'msg': 'Repo `%s` not locked.' % repo.repo_name
1336 1336 }
1337 1337 return _d
1338 1338 else:
1339 1339 _user_id, _time, _reason = lockobj
1340 1340 lock_user = get_user_or_error(userid)
1341 1341 _d = {
1342 1342 'repo': repo.repo_name,
1343 1343 'locked': True,
1344 1344 'locked_since': _time,
1345 1345 'locked_by': lock_user.username,
1346 1346 'lock_reason': _reason,
1347 1347 'lock_state_changed': False,
1348 1348 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1349 1349 % (repo.repo_name, lock_user.username,
1350 1350 json.dumps(time_to_datetime(_time))))
1351 1351 }
1352 1352 return _d
1353 1353
1354 1354 # force locked state through a flag
1355 1355 else:
1356 1356 locked = str2bool(locked)
1357 1357 lock_reason = Repository.LOCK_API
1358 1358 try:
1359 1359 if locked:
1360 1360 lock_time = time.time()
1361 1361 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1362 1362 else:
1363 1363 lock_time = None
1364 1364 Repository.unlock(repo)
1365 1365 _d = {
1366 1366 'repo': repo.repo_name,
1367 1367 'locked': locked,
1368 1368 'locked_since': lock_time,
1369 1369 'locked_by': user.username,
1370 1370 'lock_reason': lock_reason,
1371 1371 'lock_state_changed': True,
1372 1372 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1373 1373 % (user.username, repo.repo_name, locked))
1374 1374 }
1375 1375 return _d
1376 1376 except Exception:
1377 1377 log.exception(
1378 1378 "Exception occurred while trying to lock repository")
1379 1379 raise JSONRPCError(
1380 1380 'Error occurred locking repository `%s`' % repo.repo_name
1381 1381 )
1382 1382
1383 1383
1384 1384 @jsonrpc_method()
1385 1385 def comment_commit(
1386 1386 request, apiuser, repoid, commit_id, message, status=Optional(None),
1387 1387 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1388 1388 resolves_comment_id=Optional(None),
1389 1389 userid=Optional(OAttr('apiuser'))):
1390 1390 """
1391 1391 Set a commit comment, and optionally change the status of the commit.
1392 1392
1393 1393 :param apiuser: This is filled automatically from the |authtoken|.
1394 1394 :type apiuser: AuthUser
1395 1395 :param repoid: Set the repository name or repository ID.
1396 1396 :type repoid: str or int
1397 1397 :param commit_id: Specify the commit_id for which to set a comment.
1398 1398 :type commit_id: str
1399 1399 :param message: The comment text.
1400 1400 :type message: str
1401 1401 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1402 1402 'approved', 'rejected', 'under_review'
1403 1403 :type status: str
1404 1404 :param comment_type: Comment type, one of: 'note', 'todo'
1405 1405 :type comment_type: Optional(str), default: 'note'
1406 1406 :param userid: Set the user name of the comment creator.
1407 1407 :type userid: Optional(str or int)
1408 1408
1409 1409 Example error output:
1410 1410
1411 1411 .. code-block:: bash
1412 1412
1413 1413 {
1414 1414 "id" : <id_given_in_input>,
1415 1415 "result" : {
1416 1416 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1417 1417 "status_change": null or <status>,
1418 1418 "success": true
1419 1419 },
1420 1420 "error" : null
1421 1421 }
1422 1422
1423 1423 """
1424 1424 repo = get_repo_or_error(repoid)
1425 1425 if not has_superadmin_permission(apiuser):
1426 1426 _perms = ('repository.read', 'repository.write', 'repository.admin')
1427 1427 validate_repo_permissions(apiuser, repoid, repo, _perms)
1428 1428
1429 1429 try:
1430 1430 commit_id = repo.scm_instance().get_commit(commit_id=commit_id).raw_id
1431 1431 except Exception as e:
1432 1432 log.exception('Failed to fetch commit')
1433 1433 raise JSONRPCError(e.message)
1434 1434
1435 1435 if isinstance(userid, Optional):
1436 1436 userid = apiuser.user_id
1437 1437
1438 1438 user = get_user_or_error(userid)
1439 1439 status = Optional.extract(status)
1440 1440 comment_type = Optional.extract(comment_type)
1441 1441 resolves_comment_id = Optional.extract(resolves_comment_id)
1442 1442
1443 1443 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1444 1444 if status and status not in allowed_statuses:
1445 1445 raise JSONRPCError('Bad status, must be on '
1446 1446 'of %s got %s' % (allowed_statuses, status,))
1447 1447
1448 1448 if resolves_comment_id:
1449 1449 comment = ChangesetComment.get(resolves_comment_id)
1450 1450 if not comment:
1451 1451 raise JSONRPCError(
1452 1452 'Invalid resolves_comment_id `%s` for this commit.'
1453 1453 % resolves_comment_id)
1454 1454 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1455 1455 raise JSONRPCError(
1456 1456 'Comment `%s` is wrong type for setting status to resolved.'
1457 1457 % resolves_comment_id)
1458 1458
1459 1459 try:
1460 1460 rc_config = SettingsModel().get_all_settings()
1461 1461 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1462 1462 status_change_label = ChangesetStatus.get_status_lbl(status)
1463 1463 comm = CommentsModel().create(
1464 1464 message, repo, user, commit_id=commit_id,
1465 1465 status_change=status_change_label,
1466 1466 status_change_type=status,
1467 1467 renderer=renderer,
1468 1468 comment_type=comment_type,
1469 1469 resolves_comment_id=resolves_comment_id
1470 1470 )
1471 1471 if status:
1472 1472 # also do a status change
1473 1473 try:
1474 1474 ChangesetStatusModel().set_status(
1475 1475 repo, status, user, comm, revision=commit_id,
1476 1476 dont_allow_on_closed_pull_request=True
1477 1477 )
1478 1478 except StatusChangeOnClosedPullRequestError:
1479 1479 log.exception(
1480 1480 "Exception occurred while trying to change repo commit status")
1481 1481 msg = ('Changing status on a changeset associated with '
1482 1482 'a closed pull request is not allowed')
1483 1483 raise JSONRPCError(msg)
1484 1484
1485 1485 Session().commit()
1486 1486 return {
1487 1487 'msg': (
1488 1488 'Commented on commit `%s` for repository `%s`' % (
1489 1489 comm.revision, repo.repo_name)),
1490 1490 'status_change': status,
1491 1491 'success': True,
1492 1492 }
1493 1493 except JSONRPCError:
1494 1494 # catch any inside errors, and re-raise them to prevent from
1495 1495 # below global catch to silence them
1496 1496 raise
1497 1497 except Exception:
1498 1498 log.exception("Exception occurred while trying to comment on commit")
1499 1499 raise JSONRPCError(
1500 1500 'failed to set comment on repository `%s`' % (repo.repo_name,)
1501 1501 )
1502 1502
1503 1503
1504 1504 @jsonrpc_method()
1505 1505 def grant_user_permission(request, apiuser, repoid, userid, perm):
1506 1506 """
1507 1507 Grant permissions for the specified user on the given repository,
1508 1508 or update existing permissions if found.
1509 1509
1510 1510 This command can only be run using an |authtoken| with admin
1511 1511 permissions on the |repo|.
1512 1512
1513 1513 :param apiuser: This is filled automatically from the |authtoken|.
1514 1514 :type apiuser: AuthUser
1515 1515 :param repoid: Set the repository name or repository ID.
1516 1516 :type repoid: str or int
1517 1517 :param userid: Set the user name.
1518 1518 :type userid: str
1519 1519 :param perm: Set the user permissions, using the following format
1520 1520 ``(repository.(none|read|write|admin))``
1521 1521 :type perm: str
1522 1522
1523 1523 Example output:
1524 1524
1525 1525 .. code-block:: bash
1526 1526
1527 1527 id : <id_given_in_input>
1528 1528 result: {
1529 1529 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1530 1530 "success": true
1531 1531 }
1532 1532 error: null
1533 1533 """
1534 1534
1535 1535 repo = get_repo_or_error(repoid)
1536 1536 user = get_user_or_error(userid)
1537 1537 perm = get_perm_or_error(perm)
1538 1538 if not has_superadmin_permission(apiuser):
1539 1539 _perms = ('repository.admin',)
1540 1540 validate_repo_permissions(apiuser, repoid, repo, _perms)
1541 1541
1542 1542 try:
1543 1543
1544 1544 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
1545 1545
1546 1546 Session().commit()
1547 1547 return {
1548 1548 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1549 1549 perm.permission_name, user.username, repo.repo_name
1550 1550 ),
1551 1551 'success': True
1552 1552 }
1553 1553 except Exception:
1554 1554 log.exception(
1555 1555 "Exception occurred while trying edit permissions for repo")
1556 1556 raise JSONRPCError(
1557 1557 'failed to edit permission for user: `%s` in repo: `%s`' % (
1558 1558 userid, repoid
1559 1559 )
1560 1560 )
1561 1561
1562 1562
1563 1563 @jsonrpc_method()
1564 1564 def revoke_user_permission(request, apiuser, repoid, userid):
1565 1565 """
1566 1566 Revoke permission for a user on the specified repository.
1567 1567
1568 1568 This command can only be run using an |authtoken| with admin
1569 1569 permissions on the |repo|.
1570 1570
1571 1571 :param apiuser: This is filled automatically from the |authtoken|.
1572 1572 :type apiuser: AuthUser
1573 1573 :param repoid: Set the repository name or repository ID.
1574 1574 :type repoid: str or int
1575 1575 :param userid: Set the user name of revoked user.
1576 1576 :type userid: str or int
1577 1577
1578 1578 Example error output:
1579 1579
1580 1580 .. code-block:: bash
1581 1581
1582 1582 id : <id_given_in_input>
1583 1583 result: {
1584 1584 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1585 1585 "success": true
1586 1586 }
1587 1587 error: null
1588 1588 """
1589 1589
1590 1590 repo = get_repo_or_error(repoid)
1591 1591 user = get_user_or_error(userid)
1592 1592 if not has_superadmin_permission(apiuser):
1593 1593 _perms = ('repository.admin',)
1594 1594 validate_repo_permissions(apiuser, repoid, repo, _perms)
1595 1595
1596 1596 try:
1597 1597 RepoModel().revoke_user_permission(repo=repo, user=user)
1598 1598 Session().commit()
1599 1599 return {
1600 1600 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1601 1601 user.username, repo.repo_name
1602 1602 ),
1603 1603 'success': True
1604 1604 }
1605 1605 except Exception:
1606 1606 log.exception(
1607 1607 "Exception occurred while trying revoke permissions to repo")
1608 1608 raise JSONRPCError(
1609 1609 'failed to edit permission for user: `%s` in repo: `%s`' % (
1610 1610 userid, repoid
1611 1611 )
1612 1612 )
1613 1613
1614 1614
1615 1615 @jsonrpc_method()
1616 1616 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1617 1617 """
1618 1618 Grant permission for a user group on the specified repository,
1619 1619 or update existing permissions.
1620 1620
1621 1621 This command can only be run using an |authtoken| with admin
1622 1622 permissions on the |repo|.
1623 1623
1624 1624 :param apiuser: This is filled automatically from the |authtoken|.
1625 1625 :type apiuser: AuthUser
1626 1626 :param repoid: Set the repository name or repository ID.
1627 1627 :type repoid: str or int
1628 1628 :param usergroupid: Specify the ID of the user group.
1629 1629 :type usergroupid: str or int
1630 1630 :param perm: Set the user group permissions using the following
1631 1631 format: (repository.(none|read|write|admin))
1632 1632 :type perm: str
1633 1633
1634 1634 Example output:
1635 1635
1636 1636 .. code-block:: bash
1637 1637
1638 1638 id : <id_given_in_input>
1639 1639 result : {
1640 1640 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1641 1641 "success": true
1642 1642
1643 1643 }
1644 1644 error : null
1645 1645
1646 1646 Example error output:
1647 1647
1648 1648 .. code-block:: bash
1649 1649
1650 1650 id : <id_given_in_input>
1651 1651 result : null
1652 1652 error : {
1653 1653 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1654 1654 }
1655 1655
1656 1656 """
1657 1657
1658 1658 repo = get_repo_or_error(repoid)
1659 1659 perm = get_perm_or_error(perm)
1660 1660 if not has_superadmin_permission(apiuser):
1661 1661 _perms = ('repository.admin',)
1662 1662 validate_repo_permissions(apiuser, repoid, repo, _perms)
1663 1663
1664 1664 user_group = get_user_group_or_error(usergroupid)
1665 1665 if not has_superadmin_permission(apiuser):
1666 1666 # check if we have at least read permission for this user group !
1667 1667 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1668 1668 if not HasUserGroupPermissionAnyApi(*_perms)(
1669 1669 user=apiuser, user_group_name=user_group.users_group_name):
1670 1670 raise JSONRPCError(
1671 1671 'user group `%s` does not exist' % (usergroupid,))
1672 1672
1673 1673 try:
1674 1674 RepoModel().grant_user_group_permission(
1675 1675 repo=repo, group_name=user_group, perm=perm)
1676 1676
1677 1677 Session().commit()
1678 1678 return {
1679 1679 'msg': 'Granted perm: `%s` for user group: `%s` in '
1680 1680 'repo: `%s`' % (
1681 1681 perm.permission_name, user_group.users_group_name,
1682 1682 repo.repo_name
1683 1683 ),
1684 1684 'success': True
1685 1685 }
1686 1686 except Exception:
1687 1687 log.exception(
1688 1688 "Exception occurred while trying change permission on repo")
1689 1689 raise JSONRPCError(
1690 1690 'failed to edit permission for user group: `%s` in '
1691 1691 'repo: `%s`' % (
1692 1692 usergroupid, repo.repo_name
1693 1693 )
1694 1694 )
1695 1695
1696 1696
1697 1697 @jsonrpc_method()
1698 1698 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1699 1699 """
1700 1700 Revoke the permissions of a user group on a given repository.
1701 1701
1702 1702 This command can only be run using an |authtoken| with admin
1703 1703 permissions on the |repo|.
1704 1704
1705 1705 :param apiuser: This is filled automatically from the |authtoken|.
1706 1706 :type apiuser: AuthUser
1707 1707 :param repoid: Set the repository name or repository ID.
1708 1708 :type repoid: str or int
1709 1709 :param usergroupid: Specify the user group ID.
1710 1710 :type usergroupid: str or int
1711 1711
1712 1712 Example output:
1713 1713
1714 1714 .. code-block:: bash
1715 1715
1716 1716 id : <id_given_in_input>
1717 1717 result: {
1718 1718 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1719 1719 "success": true
1720 1720 }
1721 1721 error: null
1722 1722 """
1723 1723
1724 1724 repo = get_repo_or_error(repoid)
1725 1725 if not has_superadmin_permission(apiuser):
1726 1726 _perms = ('repository.admin',)
1727 1727 validate_repo_permissions(apiuser, repoid, repo, _perms)
1728 1728
1729 1729 user_group = get_user_group_or_error(usergroupid)
1730 1730 if not has_superadmin_permission(apiuser):
1731 1731 # check if we have at least read permission for this user group !
1732 1732 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1733 1733 if not HasUserGroupPermissionAnyApi(*_perms)(
1734 1734 user=apiuser, user_group_name=user_group.users_group_name):
1735 1735 raise JSONRPCError(
1736 1736 'user group `%s` does not exist' % (usergroupid,))
1737 1737
1738 1738 try:
1739 1739 RepoModel().revoke_user_group_permission(
1740 1740 repo=repo, group_name=user_group)
1741 1741
1742 1742 Session().commit()
1743 1743 return {
1744 1744 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1745 1745 user_group.users_group_name, repo.repo_name
1746 1746 ),
1747 1747 'success': True
1748 1748 }
1749 1749 except Exception:
1750 1750 log.exception("Exception occurred while trying revoke "
1751 1751 "user group permission on repo")
1752 1752 raise JSONRPCError(
1753 1753 'failed to edit permission for user group: `%s` in '
1754 1754 'repo: `%s`' % (
1755 1755 user_group.users_group_name, repo.repo_name
1756 1756 )
1757 1757 )
1758 1758
1759 1759
1760 1760 @jsonrpc_method()
1761 1761 def pull(request, apiuser, repoid):
1762 1762 """
1763 1763 Triggers a pull on the given repository from a remote location. You
1764 1764 can use this to keep remote repositories up-to-date.
1765 1765
1766 1766 This command can only be run using an |authtoken| with admin
1767 1767 rights to the specified repository. For more information,
1768 1768 see :ref:`config-token-ref`.
1769 1769
1770 1770 This command takes the following options:
1771 1771
1772 1772 :param apiuser: This is filled automatically from the |authtoken|.
1773 1773 :type apiuser: AuthUser
1774 1774 :param repoid: The repository name or repository ID.
1775 1775 :type repoid: str or int
1776 1776
1777 1777 Example output:
1778 1778
1779 1779 .. code-block:: bash
1780 1780
1781 1781 id : <id_given_in_input>
1782 1782 result : {
1783 1783 "msg": "Pulled from `<repository name>`"
1784 1784 "repository": "<repository name>"
1785 1785 }
1786 1786 error : null
1787 1787
1788 1788 Example error output:
1789 1789
1790 1790 .. code-block:: bash
1791 1791
1792 1792 id : <id_given_in_input>
1793 1793 result : null
1794 1794 error : {
1795 1795 "Unable to pull changes from `<reponame>`"
1796 1796 }
1797 1797
1798 1798 """
1799 1799
1800 1800 repo = get_repo_or_error(repoid)
1801 1801 if not has_superadmin_permission(apiuser):
1802 1802 _perms = ('repository.admin',)
1803 1803 validate_repo_permissions(apiuser, repoid, repo, _perms)
1804 1804
1805 1805 try:
1806 1806 ScmModel().pull_changes(repo.repo_name, apiuser.username)
1807 1807 return {
1808 1808 'msg': 'Pulled from `%s`' % repo.repo_name,
1809 1809 'repository': repo.repo_name
1810 1810 }
1811 1811 except Exception:
1812 1812 log.exception("Exception occurred while trying to "
1813 1813 "pull changes from remote location")
1814 1814 raise JSONRPCError(
1815 1815 'Unable to pull changes from `%s`' % repo.repo_name
1816 1816 )
1817 1817
1818 1818
1819 1819 @jsonrpc_method()
1820 1820 def strip(request, apiuser, repoid, revision, branch):
1821 1821 """
1822 1822 Strips the given revision from the specified repository.
1823 1823
1824 1824 * This will remove the revision and all of its decendants.
1825 1825
1826 1826 This command can only be run using an |authtoken| with admin rights to
1827 1827 the specified repository.
1828 1828
1829 1829 This command takes the following options:
1830 1830
1831 1831 :param apiuser: This is filled automatically from the |authtoken|.
1832 1832 :type apiuser: AuthUser
1833 1833 :param repoid: The repository name or repository ID.
1834 1834 :type repoid: str or int
1835 1835 :param revision: The revision you wish to strip.
1836 1836 :type revision: str
1837 1837 :param branch: The branch from which to strip the revision.
1838 1838 :type branch: str
1839 1839
1840 1840 Example output:
1841 1841
1842 1842 .. code-block:: bash
1843 1843
1844 1844 id : <id_given_in_input>
1845 1845 result : {
1846 1846 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
1847 1847 "repository": "<repository name>"
1848 1848 }
1849 1849 error : null
1850 1850
1851 1851 Example error output:
1852 1852
1853 1853 .. code-block:: bash
1854 1854
1855 1855 id : <id_given_in_input>
1856 1856 result : null
1857 1857 error : {
1858 1858 "Unable to strip commit <commit_hash> from repo `<repository name>`"
1859 1859 }
1860 1860
1861 1861 """
1862 1862
1863 1863 repo = get_repo_or_error(repoid)
1864 1864 if not has_superadmin_permission(apiuser):
1865 1865 _perms = ('repository.admin',)
1866 1866 validate_repo_permissions(apiuser, repoid, repo, _perms)
1867 1867
1868 1868 try:
1869 1869 ScmModel().strip(repo, revision, branch)
1870 1870 return {
1871 1871 'msg': 'Stripped commit %s from repo `%s`' % (
1872 1872 revision, repo.repo_name),
1873 1873 'repository': repo.repo_name
1874 1874 }
1875 1875 except Exception:
1876 1876 log.exception("Exception while trying to strip")
1877 1877 raise JSONRPCError(
1878 1878 'Unable to strip commit %s from repo `%s`' % (
1879 1879 revision, repo.repo_name)
1880 1880 )
1881 1881
1882 1882
1883 1883 @jsonrpc_method()
1884 1884 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
1885 1885 """
1886 1886 Returns all settings for a repository. If key is given it only returns the
1887 1887 setting identified by the key or null.
1888 1888
1889 1889 :param apiuser: This is filled automatically from the |authtoken|.
1890 1890 :type apiuser: AuthUser
1891 1891 :param repoid: The repository name or repository id.
1892 1892 :type repoid: str or int
1893 1893 :param key: Key of the setting to return.
1894 1894 :type: key: Optional(str)
1895 1895
1896 1896 Example output:
1897 1897
1898 1898 .. code-block:: bash
1899 1899
1900 1900 {
1901 1901 "error": null,
1902 1902 "id": 237,
1903 1903 "result": {
1904 1904 "extensions_largefiles": true,
1905 "extensions_evolve": true,
1905 1906 "hooks_changegroup_push_logger": true,
1906 1907 "hooks_changegroup_repo_size": false,
1907 1908 "hooks_outgoing_pull_logger": true,
1908 1909 "phases_publish": "True",
1909 1910 "rhodecode_hg_use_rebase_for_merging": true,
1910 1911 "rhodecode_pr_merge_enabled": true,
1911 1912 "rhodecode_use_outdated_comments": true
1912 1913 }
1913 1914 }
1914 1915 """
1915 1916
1916 1917 # Restrict access to this api method to admins only.
1917 1918 if not has_superadmin_permission(apiuser):
1918 1919 raise JSONRPCForbidden()
1919 1920
1920 1921 try:
1921 1922 repo = get_repo_or_error(repoid)
1922 1923 settings_model = VcsSettingsModel(repo=repo)
1923 1924 settings = settings_model.get_global_settings()
1924 1925 settings.update(settings_model.get_repo_settings())
1925 1926
1926 1927 # If only a single setting is requested fetch it from all settings.
1927 1928 key = Optional.extract(key)
1928 1929 if key is not None:
1929 1930 settings = settings.get(key, None)
1930 1931 except Exception:
1931 1932 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
1932 1933 log.exception(msg)
1933 1934 raise JSONRPCError(msg)
1934 1935
1935 1936 return settings
1936 1937
1937 1938
1938 1939 @jsonrpc_method()
1939 1940 def set_repo_settings(request, apiuser, repoid, settings):
1940 1941 """
1941 1942 Update repository settings. Returns true on success.
1942 1943
1943 1944 :param apiuser: This is filled automatically from the |authtoken|.
1944 1945 :type apiuser: AuthUser
1945 1946 :param repoid: The repository name or repository id.
1946 1947 :type repoid: str or int
1947 1948 :param settings: The new settings for the repository.
1948 1949 :type: settings: dict
1949 1950
1950 1951 Example output:
1951 1952
1952 1953 .. code-block:: bash
1953 1954
1954 1955 {
1955 1956 "error": null,
1956 1957 "id": 237,
1957 1958 "result": true
1958 1959 }
1959 1960 """
1960 1961 # Restrict access to this api method to admins only.
1961 1962 if not has_superadmin_permission(apiuser):
1962 1963 raise JSONRPCForbidden()
1963 1964
1964 1965 if type(settings) is not dict:
1965 1966 raise JSONRPCError('Settings have to be a JSON Object.')
1966 1967
1967 1968 try:
1968 1969 settings_model = VcsSettingsModel(repo=repoid)
1969 1970
1970 1971 # Merge global, repo and incoming settings.
1971 1972 new_settings = settings_model.get_global_settings()
1972 1973 new_settings.update(settings_model.get_repo_settings())
1973 1974 new_settings.update(settings)
1974 1975
1975 1976 # Update the settings.
1976 1977 inherit_global_settings = new_settings.get(
1977 1978 'inherit_global_settings', False)
1978 1979 settings_model.create_or_update_repo_settings(
1979 1980 new_settings, inherit_global_settings=inherit_global_settings)
1980 1981 Session().commit()
1981 1982 except Exception:
1982 1983 msg = 'Failed to update settings for repository `{}`'.format(repoid)
1983 1984 log.exception(msg)
1984 1985 raise JSONRPCError(msg)
1985 1986
1986 1987 # Indicate success.
1987 1988 return True
@@ -1,611 +1,619 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 Database creation, and setup module for RhodeCode Enterprise. Used for creation
23 23 of database as well as for migration operations
24 24 """
25 25
26 26 import os
27 27 import sys
28 28 import time
29 29 import uuid
30 30 import logging
31 31 import getpass
32 32 from os.path import dirname as dn, join as jn
33 33
34 34 from sqlalchemy.engine import create_engine
35 35
36 36 from rhodecode import __dbversion__
37 37 from rhodecode.model import init_model
38 38 from rhodecode.model.user import UserModel
39 39 from rhodecode.model.db import (
40 40 User, Permission, RhodeCodeUi, RhodeCodeSetting, UserToPerm,
41 41 DbMigrateVersion, RepoGroup, UserRepoGroupToPerm, CacheKey, Repository)
42 42 from rhodecode.model.meta import Session, Base
43 43 from rhodecode.model.permission import PermissionModel
44 44 from rhodecode.model.repo import RepoModel
45 45 from rhodecode.model.repo_group import RepoGroupModel
46 46 from rhodecode.model.settings import SettingsModel
47 47
48 48
49 49 log = logging.getLogger(__name__)
50 50
51 51
52 52 def notify(msg):
53 53 """
54 54 Notification for migrations messages
55 55 """
56 56 ml = len(msg) + (4 * 2)
57 57 print('\n%s\n*** %s ***\n%s' % ('*' * ml, msg, '*' * ml)).upper()
58 58
59 59
60 60 class DbManage(object):
61 61
62 62 def __init__(self, log_sql, dbconf, root, tests=False,
63 63 SESSION=None, cli_args={}):
64 64 self.dbname = dbconf.split('/')[-1]
65 65 self.tests = tests
66 66 self.root = root
67 67 self.dburi = dbconf
68 68 self.log_sql = log_sql
69 69 self.db_exists = False
70 70 self.cli_args = cli_args
71 71 self.init_db(SESSION=SESSION)
72 72 self.ask_ok = self.get_ask_ok_func(self.cli_args.get('force_ask'))
73 73
74 74 def get_ask_ok_func(self, param):
75 75 if param not in [None]:
76 76 # return a function lambda that has a default set to param
77 77 return lambda *args, **kwargs: param
78 78 else:
79 79 from rhodecode.lib.utils import ask_ok
80 80 return ask_ok
81 81
82 82 def init_db(self, SESSION=None):
83 83 if SESSION:
84 84 self.sa = SESSION
85 85 else:
86 86 # init new sessions
87 87 engine = create_engine(self.dburi, echo=self.log_sql)
88 88 init_model(engine)
89 89 self.sa = Session()
90 90
91 91 def create_tables(self, override=False):
92 92 """
93 93 Create a auth database
94 94 """
95 95
96 96 log.info("Existing database with the same name is going to be destroyed.")
97 97 log.info("Setup command will run DROP ALL command on that database.")
98 98 if self.tests:
99 99 destroy = True
100 100 else:
101 101 destroy = self.ask_ok('Are you sure that you want to destroy the old database? [y/n]')
102 102 if not destroy:
103 103 log.info('Nothing done.')
104 104 sys.exit(0)
105 105 if destroy:
106 106 Base.metadata.drop_all()
107 107
108 108 checkfirst = not override
109 109 Base.metadata.create_all(checkfirst=checkfirst)
110 110 log.info('Created tables for %s' % self.dbname)
111 111
112 112 def set_db_version(self):
113 113 ver = DbMigrateVersion()
114 114 ver.version = __dbversion__
115 115 ver.repository_id = 'rhodecode_db_migrations'
116 116 ver.repository_path = 'versions'
117 117 self.sa.add(ver)
118 118 log.info('db version set to: %s' % __dbversion__)
119 119
120 120 def run_pre_migration_tasks(self):
121 121 """
122 122 Run various tasks before actually doing migrations
123 123 """
124 124 # delete cache keys on each upgrade
125 125 total = CacheKey.query().count()
126 126 log.info("Deleting (%s) cache keys now...", total)
127 127 CacheKey.delete_all_cache()
128 128
129 129 def upgrade(self):
130 130 """
131 131 Upgrades given database schema to given revision following
132 132 all needed steps, to perform the upgrade
133 133
134 134 """
135 135
136 136 from rhodecode.lib.dbmigrate.migrate.versioning import api
137 137 from rhodecode.lib.dbmigrate.migrate.exceptions import \
138 138 DatabaseNotControlledError
139 139
140 140 if 'sqlite' in self.dburi:
141 141 print (
142 142 '********************** WARNING **********************\n'
143 143 'Make sure your version of sqlite is at least 3.7.X. \n'
144 144 'Earlier versions are known to fail on some migrations\n'
145 145 '*****************************************************\n')
146 146
147 147 upgrade = self.ask_ok(
148 148 'You are about to perform a database upgrade. Make '
149 149 'sure you have backed up your database. '
150 150 'Continue ? [y/n]')
151 151 if not upgrade:
152 152 log.info('No upgrade performed')
153 153 sys.exit(0)
154 154
155 155 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
156 156 'rhodecode/lib/dbmigrate')
157 157 db_uri = self.dburi
158 158
159 159 try:
160 160 curr_version = api.db_version(db_uri, repository_path)
161 161 msg = ('Found current database under version '
162 162 'control with version %s' % curr_version)
163 163
164 164 except (RuntimeError, DatabaseNotControlledError):
165 165 curr_version = 1
166 166 msg = ('Current database is not under version control. Setting '
167 167 'as version %s' % curr_version)
168 168 api.version_control(db_uri, repository_path, curr_version)
169 169
170 170 notify(msg)
171 171
172 172 self.run_pre_migration_tasks()
173 173
174 174 if curr_version == __dbversion__:
175 175 log.info('This database is already at the newest version')
176 176 sys.exit(0)
177 177
178 178 upgrade_steps = range(curr_version + 1, __dbversion__ + 1)
179 179 notify('attempting to upgrade database from '
180 180 'version %s to version %s' % (curr_version, __dbversion__))
181 181
182 182 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
183 183 _step = None
184 184 for step in upgrade_steps:
185 185 notify('performing upgrade step %s' % step)
186 186 time.sleep(0.5)
187 187
188 188 api.upgrade(db_uri, repository_path, step)
189 189 self.sa.rollback()
190 190 notify('schema upgrade for step %s completed' % (step,))
191 191
192 192 _step = step
193 193
194 194 notify('upgrade to version %s successful' % _step)
195 195
196 196 def fix_repo_paths(self):
197 197 """
198 198 Fixes an old RhodeCode version path into new one without a '*'
199 199 """
200 200
201 201 paths = self.sa.query(RhodeCodeUi)\
202 202 .filter(RhodeCodeUi.ui_key == '/')\
203 203 .scalar()
204 204
205 205 paths.ui_value = paths.ui_value.replace('*', '')
206 206
207 207 try:
208 208 self.sa.add(paths)
209 209 self.sa.commit()
210 210 except Exception:
211 211 self.sa.rollback()
212 212 raise
213 213
214 214 def fix_default_user(self):
215 215 """
216 216 Fixes an old default user with some 'nicer' default values,
217 217 used mostly for anonymous access
218 218 """
219 219 def_user = self.sa.query(User)\
220 220 .filter(User.username == User.DEFAULT_USER)\
221 221 .one()
222 222
223 223 def_user.name = 'Anonymous'
224 224 def_user.lastname = 'User'
225 225 def_user.email = User.DEFAULT_USER_EMAIL
226 226
227 227 try:
228 228 self.sa.add(def_user)
229 229 self.sa.commit()
230 230 except Exception:
231 231 self.sa.rollback()
232 232 raise
233 233
234 234 def fix_settings(self):
235 235 """
236 236 Fixes rhodecode settings and adds ga_code key for google analytics
237 237 """
238 238
239 239 hgsettings3 = RhodeCodeSetting('ga_code', '')
240 240
241 241 try:
242 242 self.sa.add(hgsettings3)
243 243 self.sa.commit()
244 244 except Exception:
245 245 self.sa.rollback()
246 246 raise
247 247
248 248 def create_admin_and_prompt(self):
249 249
250 250 # defaults
251 251 defaults = self.cli_args
252 252 username = defaults.get('username')
253 253 password = defaults.get('password')
254 254 email = defaults.get('email')
255 255
256 256 if username is None:
257 257 username = raw_input('Specify admin username:')
258 258 if password is None:
259 259 password = self._get_admin_password()
260 260 if not password:
261 261 # second try
262 262 password = self._get_admin_password()
263 263 if not password:
264 264 sys.exit()
265 265 if email is None:
266 266 email = raw_input('Specify admin email:')
267 267 api_key = self.cli_args.get('api_key')
268 268 self.create_user(username, password, email, True,
269 269 strict_creation_check=False,
270 270 api_key=api_key)
271 271
272 272 def _get_admin_password(self):
273 273 password = getpass.getpass('Specify admin password '
274 274 '(min 6 chars):')
275 275 confirm = getpass.getpass('Confirm password:')
276 276
277 277 if password != confirm:
278 278 log.error('passwords mismatch')
279 279 return False
280 280 if len(password) < 6:
281 281 log.error('password is too short - use at least 6 characters')
282 282 return False
283 283
284 284 return password
285 285
286 286 def create_test_admin_and_users(self):
287 287 log.info('creating admin and regular test users')
288 288 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
289 289 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
290 290 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
291 291 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
292 292 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
293 293
294 294 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
295 295 TEST_USER_ADMIN_EMAIL, True, api_key=True)
296 296
297 297 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
298 298 TEST_USER_REGULAR_EMAIL, False, api_key=True)
299 299
300 300 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
301 301 TEST_USER_REGULAR2_EMAIL, False, api_key=True)
302 302
303 303 def create_ui_settings(self, repo_store_path):
304 304 """
305 305 Creates ui settings, fills out hooks
306 306 and disables dotencode
307 307 """
308 308 settings_model = SettingsModel(sa=self.sa)
309 309 from rhodecode.lib.vcs.backends.hg import largefiles_store
310 310 from rhodecode.lib.vcs.backends.git import lfs_store
311 311
312 312 # Build HOOKS
313 313 hooks = [
314 314 (RhodeCodeUi.HOOK_REPO_SIZE, 'python:vcsserver.hooks.repo_size'),
315 315
316 316 # HG
317 317 (RhodeCodeUi.HOOK_PRE_PULL, 'python:vcsserver.hooks.pre_pull'),
318 318 (RhodeCodeUi.HOOK_PULL, 'python:vcsserver.hooks.log_pull_action'),
319 319 (RhodeCodeUi.HOOK_PRE_PUSH, 'python:vcsserver.hooks.pre_push'),
320 320 (RhodeCodeUi.HOOK_PRETX_PUSH, 'python:vcsserver.hooks.pre_push'),
321 321 (RhodeCodeUi.HOOK_PUSH, 'python:vcsserver.hooks.log_push_action'),
322 322
323 323 ]
324 324
325 325 for key, value in hooks:
326 326 hook_obj = settings_model.get_ui_by_key(key)
327 327 hooks2 = hook_obj if hook_obj else RhodeCodeUi()
328 328 hooks2.ui_section = 'hooks'
329 329 hooks2.ui_key = key
330 330 hooks2.ui_value = value
331 331 self.sa.add(hooks2)
332 332
333 333 # enable largefiles
334 334 largefiles = RhodeCodeUi()
335 335 largefiles.ui_section = 'extensions'
336 336 largefiles.ui_key = 'largefiles'
337 337 largefiles.ui_value = ''
338 338 self.sa.add(largefiles)
339 339
340 340 # set default largefiles cache dir, defaults to
341 341 # /repo_store_location/.cache/largefiles
342 342 largefiles = RhodeCodeUi()
343 343 largefiles.ui_section = 'largefiles'
344 344 largefiles.ui_key = 'usercache'
345 345 largefiles.ui_value = largefiles_store(repo_store_path)
346 346
347 347 self.sa.add(largefiles)
348 348
349 349 # set default lfs cache dir, defaults to
350 350 # /repo_store_location/.cache/lfs_store
351 351 lfsstore = RhodeCodeUi()
352 352 lfsstore.ui_section = 'vcs_git_lfs'
353 353 lfsstore.ui_key = 'store_location'
354 354 lfsstore.ui_value = lfs_store(repo_store_path)
355 355
356 356 self.sa.add(lfsstore)
357 357
358 358 # enable hgsubversion disabled by default
359 359 hgsubversion = RhodeCodeUi()
360 360 hgsubversion.ui_section = 'extensions'
361 361 hgsubversion.ui_key = 'hgsubversion'
362 362 hgsubversion.ui_value = ''
363 363 hgsubversion.ui_active = False
364 364 self.sa.add(hgsubversion)
365 365
366 # enable hgevolve disabled by default
367 hgevolve = RhodeCodeUi()
368 hgevolve.ui_section = 'extensions'
369 hgevolve.ui_key = 'evolve'
370 hgevolve.ui_value = ''
371 hgevolve.ui_active = False
372 self.sa.add(hgevolve)
373
366 374 # enable hggit disabled by default
367 375 hggit = RhodeCodeUi()
368 376 hggit.ui_section = 'extensions'
369 377 hggit.ui_key = 'hggit'
370 378 hggit.ui_value = ''
371 379 hggit.ui_active = False
372 380 self.sa.add(hggit)
373 381
374 382 # set svn branch defaults
375 383 branches = ["/branches/*", "/trunk"]
376 384 tags = ["/tags/*"]
377 385
378 386 for branch in branches:
379 387 settings_model.create_ui_section_value(
380 388 RhodeCodeUi.SVN_BRANCH_ID, branch)
381 389
382 390 for tag in tags:
383 391 settings_model.create_ui_section_value(RhodeCodeUi.SVN_TAG_ID, tag)
384 392
385 393 def create_auth_plugin_options(self, skip_existing=False):
386 394 """
387 395 Create default auth plugin settings, and make it active
388 396
389 397 :param skip_existing:
390 398 """
391 399
392 400 for k, v, t in [('auth_plugins', 'egg:rhodecode-enterprise-ce#rhodecode', 'list'),
393 401 ('auth_rhodecode_enabled', 'True', 'bool')]:
394 402 if (skip_existing and
395 403 SettingsModel().get_setting_by_name(k) is not None):
396 404 log.debug('Skipping option %s' % k)
397 405 continue
398 406 setting = RhodeCodeSetting(k, v, t)
399 407 self.sa.add(setting)
400 408
401 409 def create_default_options(self, skip_existing=False):
402 410 """Creates default settings"""
403 411
404 412 for k, v, t in [
405 413 ('default_repo_enable_locking', False, 'bool'),
406 414 ('default_repo_enable_downloads', False, 'bool'),
407 415 ('default_repo_enable_statistics', False, 'bool'),
408 416 ('default_repo_private', False, 'bool'),
409 417 ('default_repo_type', 'hg', 'unicode')]:
410 418
411 419 if (skip_existing and
412 420 SettingsModel().get_setting_by_name(k) is not None):
413 421 log.debug('Skipping option %s' % k)
414 422 continue
415 423 setting = RhodeCodeSetting(k, v, t)
416 424 self.sa.add(setting)
417 425
418 426 def fixup_groups(self):
419 427 def_usr = User.get_default_user()
420 428 for g in RepoGroup.query().all():
421 429 g.group_name = g.get_new_name(g.name)
422 430 self.sa.add(g)
423 431 # get default perm
424 432 default = UserRepoGroupToPerm.query()\
425 433 .filter(UserRepoGroupToPerm.group == g)\
426 434 .filter(UserRepoGroupToPerm.user == def_usr)\
427 435 .scalar()
428 436
429 437 if default is None:
430 438 log.debug('missing default permission for group %s adding' % g)
431 439 perm_obj = RepoGroupModel()._create_default_perms(g)
432 440 self.sa.add(perm_obj)
433 441
434 442 def reset_permissions(self, username):
435 443 """
436 444 Resets permissions to default state, useful when old systems had
437 445 bad permissions, we must clean them up
438 446
439 447 :param username:
440 448 """
441 449 default_user = User.get_by_username(username)
442 450 if not default_user:
443 451 return
444 452
445 453 u2p = UserToPerm.query()\
446 454 .filter(UserToPerm.user == default_user).all()
447 455 fixed = False
448 456 if len(u2p) != len(Permission.DEFAULT_USER_PERMISSIONS):
449 457 for p in u2p:
450 458 Session().delete(p)
451 459 fixed = True
452 460 self.populate_default_permissions()
453 461 return fixed
454 462
455 463 def update_repo_info(self):
456 464 RepoModel.update_repoinfo()
457 465
458 466 def config_prompt(self, test_repo_path='', retries=3):
459 467 defaults = self.cli_args
460 468 _path = defaults.get('repos_location')
461 469 if retries == 3:
462 470 log.info('Setting up repositories config')
463 471
464 472 if _path is not None:
465 473 path = _path
466 474 elif not self.tests and not test_repo_path:
467 475 path = raw_input(
468 476 'Enter a valid absolute path to store repositories. '
469 477 'All repositories in that path will be added automatically:'
470 478 )
471 479 else:
472 480 path = test_repo_path
473 481 path_ok = True
474 482
475 483 # check proper dir
476 484 if not os.path.isdir(path):
477 485 path_ok = False
478 486 log.error('Given path %s is not a valid directory' % (path,))
479 487
480 488 elif not os.path.isabs(path):
481 489 path_ok = False
482 490 log.error('Given path %s is not an absolute path' % (path,))
483 491
484 492 # check if path is at least readable.
485 493 if not os.access(path, os.R_OK):
486 494 path_ok = False
487 495 log.error('Given path %s is not readable' % (path,))
488 496
489 497 # check write access, warn user about non writeable paths
490 498 elif not os.access(path, os.W_OK) and path_ok:
491 499 log.warning('No write permission to given path %s' % (path,))
492 500
493 501 q = ('Given path %s is not writeable, do you want to '
494 502 'continue with read only mode ? [y/n]' % (path,))
495 503 if not self.ask_ok(q):
496 504 log.error('Canceled by user')
497 505 sys.exit(-1)
498 506
499 507 if retries == 0:
500 508 sys.exit('max retries reached')
501 509 if not path_ok:
502 510 retries -= 1
503 511 return self.config_prompt(test_repo_path, retries)
504 512
505 513 real_path = os.path.normpath(os.path.realpath(path))
506 514
507 515 if real_path != os.path.normpath(path):
508 516 q = ('Path looks like a symlink, RhodeCode Enterprise will store '
509 517 'given path as %s ? [y/n]') % (real_path,)
510 518 if not self.ask_ok(q):
511 519 log.error('Canceled by user')
512 520 sys.exit(-1)
513 521
514 522 return real_path
515 523
516 524 def create_settings(self, path):
517 525
518 526 self.create_ui_settings(path)
519 527
520 528 ui_config = [
521 529 ('web', 'push_ssl', 'False'),
522 530 ('web', 'allow_archive', 'gz zip bz2'),
523 531 ('web', 'allow_push', '*'),
524 532 ('web', 'baseurl', '/'),
525 533 ('paths', '/', path),
526 534 ('phases', 'publish', 'True')
527 535 ]
528 536 for section, key, value in ui_config:
529 537 ui_conf = RhodeCodeUi()
530 538 setattr(ui_conf, 'ui_section', section)
531 539 setattr(ui_conf, 'ui_key', key)
532 540 setattr(ui_conf, 'ui_value', value)
533 541 self.sa.add(ui_conf)
534 542
535 543 # rhodecode app settings
536 544 settings = [
537 545 ('realm', 'RhodeCode', 'unicode'),
538 546 ('title', '', 'unicode'),
539 547 ('pre_code', '', 'unicode'),
540 548 ('post_code', '', 'unicode'),
541 549 ('show_public_icon', True, 'bool'),
542 550 ('show_private_icon', True, 'bool'),
543 551 ('stylify_metatags', False, 'bool'),
544 552 ('dashboard_items', 100, 'int'),
545 553 ('admin_grid_items', 25, 'int'),
546 554 ('show_version', True, 'bool'),
547 555 ('use_gravatar', False, 'bool'),
548 556 ('gravatar_url', User.DEFAULT_GRAVATAR_URL, 'unicode'),
549 557 ('clone_uri_tmpl', Repository.DEFAULT_CLONE_URI, 'unicode'),
550 558 ('support_url', '', 'unicode'),
551 559 ('update_url', RhodeCodeSetting.DEFAULT_UPDATE_URL, 'unicode'),
552 560 ('show_revision_number', True, 'bool'),
553 561 ('show_sha_length', 12, 'int'),
554 562 ]
555 563
556 564 for key, val, type_ in settings:
557 565 sett = RhodeCodeSetting(key, val, type_)
558 566 self.sa.add(sett)
559 567
560 568 self.create_auth_plugin_options()
561 569 self.create_default_options()
562 570
563 571 log.info('created ui config')
564 572
565 573 def create_user(self, username, password, email='', admin=False,
566 574 strict_creation_check=True, api_key=None):
567 575 log.info('creating user %s' % username)
568 576 user = UserModel().create_or_update(
569 577 username, password, email, firstname='RhodeCode', lastname='Admin',
570 578 active=True, admin=admin, extern_type="rhodecode",
571 579 strict_creation_check=strict_creation_check)
572 580
573 581 if api_key:
574 582 log.info('setting a provided api key for the user %s', username)
575 583 from rhodecode.model.auth_token import AuthTokenModel
576 584 AuthTokenModel().create(
577 585 user=user, description='BUILTIN TOKEN')
578 586
579 587 def create_default_user(self):
580 588 log.info('creating default user')
581 589 # create default user for handling default permissions.
582 590 user = UserModel().create_or_update(username=User.DEFAULT_USER,
583 591 password=str(uuid.uuid1())[:20],
584 592 email=User.DEFAULT_USER_EMAIL,
585 593 firstname='Anonymous',
586 594 lastname='User',
587 595 strict_creation_check=False)
588 596 # based on configuration options activate/deactive this user which
589 597 # controlls anonymous access
590 598 if self.cli_args.get('public_access') is False:
591 599 log.info('Public access disabled')
592 600 user.active = False
593 601 Session().add(user)
594 602 Session().commit()
595 603
596 604 def create_permissions(self):
597 605 """
598 606 Creates all permissions defined in the system
599 607 """
600 608 # module.(access|create|change|delete)_[name]
601 609 # module.(none|read|write|admin)
602 610 log.info('creating permissions')
603 611 PermissionModel(self.sa).create_permissions()
604 612
605 613 def populate_default_permissions(self):
606 614 """
607 615 Populate default permissions. It will create only the default
608 616 permissions that are missing, and not alter already defined ones
609 617 """
610 618 log.info('creating default user permissions')
611 619 PermissionModel(self.sa).create_default_user_permissions(user=User.DEFAULT_USER)
@@ -1,373 +1,383 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2014-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 HG commit module
23 23 """
24 24
25 25 import os
26 26
27 27 from zope.cachedescriptors.property import Lazy as LazyProperty
28 28
29 29 from rhodecode.lib.datelib import utcdate_fromtimestamp
30 30 from rhodecode.lib.utils import safe_str, safe_unicode
31 31 from rhodecode.lib.vcs import path as vcspath
32 32 from rhodecode.lib.vcs.backends import base
33 33 from rhodecode.lib.vcs.backends.hg.diff import MercurialDiff
34 34 from rhodecode.lib.vcs.exceptions import CommitError
35 35 from rhodecode.lib.vcs.nodes import (
36 36 AddedFileNodesGenerator, ChangedFileNodesGenerator, DirNode, FileNode,
37 37 NodeKind, RemovedFileNodesGenerator, RootNode, SubModuleNode,
38 38 LargeFileNode, LARGEFILE_PREFIX)
39 39 from rhodecode.lib.vcs.utils.paths import get_dirs_for_path
40 40
41 41
42 42 class MercurialCommit(base.BaseCommit):
43 43 """
44 44 Represents state of the repository at the single commit.
45 45 """
46 46
47 47 _filter_pre_load = [
48 48 # git specific property not supported here
49 49 "_commit",
50 50 ]
51 51
52 52 def __init__(self, repository, raw_id, idx, pre_load=None):
53 53 raw_id = safe_str(raw_id)
54 54
55 55 self.repository = repository
56 56 self._remote = repository._remote
57 57
58 58 self.raw_id = raw_id
59 59 self.idx = repository._sanitize_commit_idx(idx)
60 60
61 61 self._set_bulk_properties(pre_load)
62 62
63 63 # caches
64 64 self.nodes = {}
65 65
66 66 def _set_bulk_properties(self, pre_load):
67 67 if not pre_load:
68 68 return
69 69 pre_load = [entry for entry in pre_load
70 70 if entry not in self._filter_pre_load]
71 71 if not pre_load:
72 72 return
73 73
74 74 result = self._remote.bulk_request(self.idx, pre_load)
75 75 for attr, value in result.items():
76 76 if attr in ["author", "branch", "message"]:
77 77 value = safe_unicode(value)
78 78 elif attr == "affected_files":
79 79 value = map(safe_unicode, value)
80 80 elif attr == "date":
81 81 value = utcdate_fromtimestamp(*value)
82 82 elif attr in ["children", "parents"]:
83 83 value = self._make_commits(value)
84 84 self.__dict__[attr] = value
85 85
86 86 @LazyProperty
87 87 def tags(self):
88 88 tags = [name for name, commit_id in self.repository.tags.iteritems()
89 89 if commit_id == self.raw_id]
90 90 return tags
91 91
92 92 @LazyProperty
93 93 def branch(self):
94 94 return safe_unicode(self._remote.ctx_branch(self.idx))
95 95
96 96 @LazyProperty
97 97 def bookmarks(self):
98 98 bookmarks = [
99 99 name for name, commit_id in self.repository.bookmarks.iteritems()
100 100 if commit_id == self.raw_id]
101 101 return bookmarks
102 102
103 103 @LazyProperty
104 104 def message(self):
105 105 return safe_unicode(self._remote.ctx_description(self.idx))
106 106
107 107 @LazyProperty
108 108 def committer(self):
109 109 return safe_unicode(self.author)
110 110
111 111 @LazyProperty
112 112 def author(self):
113 113 return safe_unicode(self._remote.ctx_user(self.idx))
114 114
115 115 @LazyProperty
116 116 def date(self):
117 117 return utcdate_fromtimestamp(*self._remote.ctx_date(self.idx))
118 118
119 119 @LazyProperty
120 120 def status(self):
121 121 """
122 122 Returns modified, added, removed, deleted files for current commit
123 123 """
124 124 return self._remote.ctx_status(self.idx)
125 125
126 126 @LazyProperty
127 127 def _file_paths(self):
128 128 return self._remote.ctx_list(self.idx)
129 129
130 130 @LazyProperty
131 131 def _dir_paths(self):
132 132 p = list(set(get_dirs_for_path(*self._file_paths)))
133 133 p.insert(0, '')
134 134 return p
135 135
136 136 @LazyProperty
137 137 def _paths(self):
138 138 return self._dir_paths + self._file_paths
139 139
140 140 @LazyProperty
141 141 def id(self):
142 142 if self.last:
143 143 return u'tip'
144 144 return self.short_id
145 145
146 146 @LazyProperty
147 147 def short_id(self):
148 148 return self.raw_id[:12]
149 149
150 150 def _make_commits(self, indexes):
151 151 return [self.repository.get_commit(commit_idx=idx)
152 152 for idx in indexes if idx >= 0]
153 153
154 154 @LazyProperty
155 155 def parents(self):
156 156 """
157 157 Returns list of parent commits.
158 158 """
159 159 parents = self._remote.ctx_parents(self.idx)
160 160 return self._make_commits(parents)
161 161
162 162 @LazyProperty
163 163 def phase(self):
164 164 phase_id = self._remote.ctx_phase(self.idx)
165 165 phase_text = {
166 166 0: 'public',
167 167 1: 'draft',
168 168 2: 'secret',
169 169 }.get(phase_id) or ''
170 170
171 171 return safe_unicode(phase_text)
172 172
173 173 @LazyProperty
174 def obsolete(self):
175 obsolete = self._remote.ctx_obsolete(self.idx)
176 return obsolete
177
178 @LazyProperty
179 def hidden(self):
180 hidden = self._remote.ctx_hidden(self.idx)
181 return hidden
182
183 @LazyProperty
174 184 def children(self):
175 185 """
176 186 Returns list of child commits.
177 187 """
178 188 children = self._remote.ctx_children(self.idx)
179 189 return self._make_commits(children)
180 190
181 191 def diff(self, ignore_whitespace=True, context=3):
182 192 result = self._remote.ctx_diff(
183 193 self.idx,
184 194 git=True, ignore_whitespace=ignore_whitespace, context=context)
185 195 diff = ''.join(result)
186 196 return MercurialDiff(diff)
187 197
188 198 def _fix_path(self, path):
189 199 """
190 200 Mercurial keeps filenodes as str so we need to encode from unicode
191 201 to str.
192 202 """
193 203 return safe_str(super(MercurialCommit, self)._fix_path(path))
194 204
195 205 def _get_kind(self, path):
196 206 path = self._fix_path(path)
197 207 if path in self._file_paths:
198 208 return NodeKind.FILE
199 209 elif path in self._dir_paths:
200 210 return NodeKind.DIR
201 211 else:
202 212 raise CommitError(
203 213 "Node does not exist at the given path '%s'" % (path, ))
204 214
205 215 def _get_filectx(self, path):
206 216 path = self._fix_path(path)
207 217 if self._get_kind(path) != NodeKind.FILE:
208 218 raise CommitError(
209 219 "File does not exist for idx %s at '%s'" % (self.raw_id, path))
210 220 return path
211 221
212 222 def get_file_mode(self, path):
213 223 """
214 224 Returns stat mode of the file at the given ``path``.
215 225 """
216 226 path = self._get_filectx(path)
217 227 if 'x' in self._remote.fctx_flags(self.idx, path):
218 228 return base.FILEMODE_EXECUTABLE
219 229 else:
220 230 return base.FILEMODE_DEFAULT
221 231
222 232 def is_link(self, path):
223 233 path = self._get_filectx(path)
224 234 return 'l' in self._remote.fctx_flags(self.idx, path)
225 235
226 236 def get_file_content(self, path):
227 237 """
228 238 Returns content of the file at given ``path``.
229 239 """
230 240 path = self._get_filectx(path)
231 241 return self._remote.fctx_data(self.idx, path)
232 242
233 243 def get_file_size(self, path):
234 244 """
235 245 Returns size of the file at given ``path``.
236 246 """
237 247 path = self._get_filectx(path)
238 248 return self._remote.fctx_size(self.idx, path)
239 249
240 250 def get_file_history(self, path, limit=None, pre_load=None):
241 251 """
242 252 Returns history of file as reversed list of `MercurialCommit` objects
243 253 for which file at given ``path`` has been modified.
244 254 """
245 255 path = self._get_filectx(path)
246 256 hist = self._remote.file_history(self.idx, path, limit)
247 257 return [
248 258 self.repository.get_commit(commit_id=commit_id, pre_load=pre_load)
249 259 for commit_id in hist]
250 260
251 261 def get_file_annotate(self, path, pre_load=None):
252 262 """
253 263 Returns a generator of four element tuples with
254 264 lineno, commit_id, commit lazy loader and line
255 265 """
256 266 result = self._remote.fctx_annotate(self.idx, path)
257 267
258 268 for ln_no, commit_id, content in result:
259 269 yield (
260 270 ln_no, commit_id,
261 271 lambda: self.repository.get_commit(commit_id=commit_id,
262 272 pre_load=pre_load),
263 273 content)
264 274
265 275 def get_nodes(self, path):
266 276 """
267 277 Returns combined ``DirNode`` and ``FileNode`` objects list representing
268 278 state of commit at the given ``path``. If node at the given ``path``
269 279 is not instance of ``DirNode``, CommitError would be raised.
270 280 """
271 281
272 282 if self._get_kind(path) != NodeKind.DIR:
273 283 raise CommitError(
274 284 "Directory does not exist for idx %s at '%s'" %
275 285 (self.idx, path))
276 286 path = self._fix_path(path)
277 287
278 288 filenodes = [
279 289 FileNode(f, commit=self) for f in self._file_paths
280 290 if os.path.dirname(f) == path]
281 291 # TODO: johbo: Check if this can be done in a more obvious way
282 292 dirs = path == '' and '' or [
283 293 d for d in self._dir_paths
284 294 if d and vcspath.dirname(d) == path]
285 295 dirnodes = [
286 296 DirNode(d, commit=self) for d in dirs
287 297 if os.path.dirname(d) == path]
288 298
289 299 alias = self.repository.alias
290 300 for k, vals in self._submodules.iteritems():
291 301 loc = vals[0]
292 302 commit = vals[1]
293 303 dirnodes.append(
294 304 SubModuleNode(k, url=loc, commit=commit, alias=alias))
295 305 nodes = dirnodes + filenodes
296 306 # cache nodes
297 307 for node in nodes:
298 308 self.nodes[node.path] = node
299 309 nodes.sort()
300 310
301 311 return nodes
302 312
303 313 def get_node(self, path, pre_load=None):
304 314 """
305 315 Returns `Node` object from the given `path`. If there is no node at
306 316 the given `path`, `NodeDoesNotExistError` would be raised.
307 317 """
308 318 path = self._fix_path(path)
309 319
310 320 if path not in self.nodes:
311 321 if path in self._file_paths:
312 322 node = FileNode(path, commit=self, pre_load=pre_load)
313 323 elif path in self._dir_paths:
314 324 if path == '':
315 325 node = RootNode(commit=self)
316 326 else:
317 327 node = DirNode(path, commit=self)
318 328 else:
319 329 raise self.no_node_at_path(path)
320 330
321 331 # cache node
322 332 self.nodes[path] = node
323 333 return self.nodes[path]
324 334
325 335 def get_largefile_node(self, path):
326 336
327 337 if self._remote.is_large_file(path):
328 338 # content of that file regular FileNode is the hash of largefile
329 339 file_id = self.get_file_content(path).strip()
330 340
331 341 if self._remote.in_largefiles_store(file_id):
332 342 lf_path = self._remote.store_path(file_id)
333 343 return LargeFileNode(lf_path, commit=self, org_path=path)
334 344 elif self._remote.in_user_cache(file_id):
335 345 lf_path = self._remote.store_path(file_id)
336 346 self._remote.link(file_id, path)
337 347 return LargeFileNode(lf_path, commit=self, org_path=path)
338 348
339 349 @LazyProperty
340 350 def _submodules(self):
341 351 """
342 352 Returns a dictionary with submodule information from substate file
343 353 of hg repository.
344 354 """
345 355 return self._remote.ctx_substate(self.idx)
346 356
347 357 @LazyProperty
348 358 def affected_files(self):
349 359 """
350 360 Gets a fast accessible file changes for given commit
351 361 """
352 362 return self._remote.ctx_files(self.idx)
353 363
354 364 @property
355 365 def added(self):
356 366 """
357 367 Returns list of added ``FileNode`` objects.
358 368 """
359 369 return AddedFileNodesGenerator([n for n in self.status[1]], self)
360 370
361 371 @property
362 372 def changed(self):
363 373 """
364 374 Returns list of modified ``FileNode`` objects.
365 375 """
366 376 return ChangedFileNodesGenerator([n for n in self.status[0]], self)
367 377
368 378 @property
369 379 def removed(self):
370 380 """
371 381 Returns list of removed ``FileNode`` objects.
372 382 """
373 383 return RemovedFileNodesGenerator([n for n in self.status[2]], self)
@@ -1,561 +1,562 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 pylons.i18n.translation import _
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 return self.load(template_name)(**kw)
70 70
71 71
72 72 form_renderer = RhodecodeFormZPTRendererFactory(search_path)
73 73 deform.Form.set_default_renderer(form_renderer)
74 74
75 75
76 76 def LoginForm():
77 77 class _LoginForm(formencode.Schema):
78 78 allow_extra_fields = True
79 79 filter_extra_fields = True
80 80 username = v.UnicodeString(
81 81 strip=True,
82 82 min=1,
83 83 not_empty=True,
84 84 messages={
85 85 'empty': _(u'Please enter a login'),
86 86 'tooShort': _(u'Enter a value %(min)i characters long or more')
87 87 }
88 88 )
89 89
90 90 password = v.UnicodeString(
91 91 strip=False,
92 92 min=3,
93 93 not_empty=True,
94 94 messages={
95 95 'empty': _(u'Please enter a password'),
96 96 'tooShort': _(u'Enter %(min)i characters or more')}
97 97 )
98 98
99 99 remember = v.StringBoolean(if_missing=False)
100 100
101 101 chained_validators = [v.ValidAuth()]
102 102 return _LoginForm
103 103
104 104
105 105 def UserForm(edit=False, available_languages=[], old_data={}):
106 106 class _UserForm(formencode.Schema):
107 107 allow_extra_fields = True
108 108 filter_extra_fields = True
109 109 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
110 110 v.ValidUsername(edit, old_data))
111 111 if edit:
112 112 new_password = All(
113 113 v.ValidPassword(),
114 114 v.UnicodeString(strip=False, min=6, not_empty=False)
115 115 )
116 116 password_confirmation = All(
117 117 v.ValidPassword(),
118 118 v.UnicodeString(strip=False, min=6, not_empty=False),
119 119 )
120 120 admin = v.StringBoolean(if_missing=False)
121 121 else:
122 122 password = All(
123 123 v.ValidPassword(),
124 124 v.UnicodeString(strip=False, min=6, not_empty=True)
125 125 )
126 126 password_confirmation = All(
127 127 v.ValidPassword(),
128 128 v.UnicodeString(strip=False, min=6, not_empty=False)
129 129 )
130 130
131 131 password_change = v.StringBoolean(if_missing=False)
132 132 create_repo_group = v.StringBoolean(if_missing=False)
133 133
134 134 active = v.StringBoolean(if_missing=False)
135 135 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
136 136 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
137 137 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
138 138 extern_name = v.UnicodeString(strip=True)
139 139 extern_type = v.UnicodeString(strip=True)
140 140 language = v.OneOf(available_languages, hideList=False,
141 141 testValueList=True, if_missing=None)
142 142 chained_validators = [v.ValidPasswordsMatch()]
143 143 return _UserForm
144 144
145 145
146 146 def UserGroupForm(edit=False, old_data=None, allow_disabled=False):
147 147 old_data = old_data or {}
148 148
149 149 class _UserGroupForm(formencode.Schema):
150 150 allow_extra_fields = True
151 151 filter_extra_fields = True
152 152
153 153 users_group_name = All(
154 154 v.UnicodeString(strip=True, min=1, not_empty=True),
155 155 v.ValidUserGroup(edit, old_data)
156 156 )
157 157 user_group_description = v.UnicodeString(strip=True, min=1,
158 158 not_empty=False)
159 159
160 160 users_group_active = v.StringBoolean(if_missing=False)
161 161
162 162 if edit:
163 163 # this is user group owner
164 164 user = All(
165 165 v.UnicodeString(not_empty=True),
166 166 v.ValidRepoUser(allow_disabled))
167 167 return _UserGroupForm
168 168
169 169
170 170 def RepoGroupForm(edit=False, old_data=None, available_groups=None,
171 171 can_create_in_root=False, allow_disabled=False):
172 172 old_data = old_data or {}
173 173 available_groups = available_groups or []
174 174
175 175 class _RepoGroupForm(formencode.Schema):
176 176 allow_extra_fields = True
177 177 filter_extra_fields = False
178 178
179 179 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
180 180 v.SlugifyName(),)
181 181 group_description = v.UnicodeString(strip=True, min=1,
182 182 not_empty=False)
183 183 group_copy_permissions = v.StringBoolean(if_missing=False)
184 184
185 185 group_parent_id = v.OneOf(available_groups, hideList=False,
186 186 testValueList=True, not_empty=True)
187 187 enable_locking = v.StringBoolean(if_missing=False)
188 188 chained_validators = [
189 189 v.ValidRepoGroup(edit, old_data, can_create_in_root)]
190 190
191 191 if edit:
192 192 # this is repo group owner
193 193 user = All(
194 194 v.UnicodeString(not_empty=True),
195 195 v.ValidRepoUser(allow_disabled))
196 196
197 197 return _RepoGroupForm
198 198
199 199
200 200 def RegisterForm(edit=False, old_data={}):
201 201 class _RegisterForm(formencode.Schema):
202 202 allow_extra_fields = True
203 203 filter_extra_fields = True
204 204 username = All(
205 205 v.ValidUsername(edit, old_data),
206 206 v.UnicodeString(strip=True, min=1, not_empty=True)
207 207 )
208 208 password = All(
209 209 v.ValidPassword(),
210 210 v.UnicodeString(strip=False, min=6, not_empty=True)
211 211 )
212 212 password_confirmation = All(
213 213 v.ValidPassword(),
214 214 v.UnicodeString(strip=False, min=6, not_empty=True)
215 215 )
216 216 active = v.StringBoolean(if_missing=False)
217 217 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
218 218 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
219 219 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
220 220
221 221 chained_validators = [v.ValidPasswordsMatch()]
222 222
223 223 return _RegisterForm
224 224
225 225
226 226 def PasswordResetForm():
227 227 class _PasswordResetForm(formencode.Schema):
228 228 allow_extra_fields = True
229 229 filter_extra_fields = True
230 230 email = All(v.ValidSystemEmail(), v.Email(not_empty=True))
231 231 return _PasswordResetForm
232 232
233 233
234 234 def RepoForm(edit=False, old_data=None, repo_groups=None, landing_revs=None,
235 235 allow_disabled=False):
236 236 old_data = old_data or {}
237 237 repo_groups = repo_groups or []
238 238 landing_revs = landing_revs or []
239 239 supported_backends = BACKENDS.keys()
240 240
241 241 class _RepoForm(formencode.Schema):
242 242 allow_extra_fields = True
243 243 filter_extra_fields = False
244 244 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
245 245 v.SlugifyName(), v.CannotHaveGitSuffix())
246 246 repo_group = All(v.CanWriteGroup(old_data),
247 247 v.OneOf(repo_groups, hideList=True))
248 248 repo_type = v.OneOf(supported_backends, required=False,
249 249 if_missing=old_data.get('repo_type'))
250 250 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
251 251 repo_private = v.StringBoolean(if_missing=False)
252 252 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
253 253 repo_copy_permissions = v.StringBoolean(if_missing=False)
254 254 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
255 255
256 256 repo_enable_statistics = v.StringBoolean(if_missing=False)
257 257 repo_enable_downloads = v.StringBoolean(if_missing=False)
258 258 repo_enable_locking = v.StringBoolean(if_missing=False)
259 259
260 260 if edit:
261 261 # this is repo owner
262 262 user = All(
263 263 v.UnicodeString(not_empty=True),
264 264 v.ValidRepoUser(allow_disabled))
265 265 clone_uri_change = v.UnicodeString(
266 266 not_empty=False, if_missing=v.Missing)
267 267
268 268 chained_validators = [v.ValidCloneUri(),
269 269 v.ValidRepoName(edit, old_data)]
270 270 return _RepoForm
271 271
272 272
273 273 def RepoPermsForm():
274 274 class _RepoPermsForm(formencode.Schema):
275 275 allow_extra_fields = True
276 276 filter_extra_fields = False
277 277 chained_validators = [v.ValidPerms(type_='repo')]
278 278 return _RepoPermsForm
279 279
280 280
281 281 def RepoGroupPermsForm(valid_recursive_choices):
282 282 class _RepoGroupPermsForm(formencode.Schema):
283 283 allow_extra_fields = True
284 284 filter_extra_fields = False
285 285 recursive = v.OneOf(valid_recursive_choices)
286 286 chained_validators = [v.ValidPerms(type_='repo_group')]
287 287 return _RepoGroupPermsForm
288 288
289 289
290 290 def UserGroupPermsForm():
291 291 class _UserPermsForm(formencode.Schema):
292 292 allow_extra_fields = True
293 293 filter_extra_fields = False
294 294 chained_validators = [v.ValidPerms(type_='user_group')]
295 295 return _UserPermsForm
296 296
297 297
298 298 def RepoFieldForm():
299 299 class _RepoFieldForm(formencode.Schema):
300 300 filter_extra_fields = True
301 301 allow_extra_fields = True
302 302
303 303 new_field_key = All(v.FieldKey(),
304 304 v.UnicodeString(strip=True, min=3, not_empty=True))
305 305 new_field_value = v.UnicodeString(not_empty=False, if_missing=u'')
306 306 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
307 307 if_missing='str')
308 308 new_field_label = v.UnicodeString(not_empty=False)
309 309 new_field_desc = v.UnicodeString(not_empty=False)
310 310
311 311 return _RepoFieldForm
312 312
313 313
314 314 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
315 315 repo_groups=[], landing_revs=[]):
316 316 class _RepoForkForm(formencode.Schema):
317 317 allow_extra_fields = True
318 318 filter_extra_fields = False
319 319 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
320 320 v.SlugifyName())
321 321 repo_group = All(v.CanWriteGroup(),
322 322 v.OneOf(repo_groups, hideList=True))
323 323 repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends))
324 324 description = v.UnicodeString(strip=True, min=1, not_empty=True)
325 325 private = v.StringBoolean(if_missing=False)
326 326 copy_permissions = v.StringBoolean(if_missing=False)
327 327 fork_parent_id = v.UnicodeString()
328 328 chained_validators = [v.ValidForkName(edit, old_data)]
329 329 landing_rev = v.OneOf(landing_revs, hideList=True)
330 330
331 331 return _RepoForkForm
332 332
333 333
334 334 def ApplicationSettingsForm():
335 335 class _ApplicationSettingsForm(formencode.Schema):
336 336 allow_extra_fields = True
337 337 filter_extra_fields = False
338 338 rhodecode_title = v.UnicodeString(strip=True, max=40, not_empty=False)
339 339 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
340 340 rhodecode_pre_code = v.UnicodeString(strip=True, min=1, not_empty=False)
341 341 rhodecode_post_code = v.UnicodeString(strip=True, min=1, not_empty=False)
342 342 rhodecode_captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
343 343 rhodecode_captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
344 344 rhodecode_create_personal_repo_group = v.StringBoolean(if_missing=False)
345 345 rhodecode_personal_repo_group_pattern = v.UnicodeString(strip=True, min=1, not_empty=False)
346 346
347 347 return _ApplicationSettingsForm
348 348
349 349
350 350 def ApplicationVisualisationForm():
351 351 class _ApplicationVisualisationForm(formencode.Schema):
352 352 allow_extra_fields = True
353 353 filter_extra_fields = False
354 354 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
355 355 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
356 356 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
357 357
358 358 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
359 359 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
360 360 rhodecode_dashboard_items = v.Int(min=5, not_empty=True)
361 361 rhodecode_admin_grid_items = v.Int(min=5, not_empty=True)
362 362 rhodecode_show_version = v.StringBoolean(if_missing=False)
363 363 rhodecode_use_gravatar = v.StringBoolean(if_missing=False)
364 364 rhodecode_markup_renderer = v.OneOf(['markdown', 'rst'])
365 365 rhodecode_gravatar_url = v.UnicodeString(min=3)
366 366 rhodecode_clone_uri_tmpl = v.UnicodeString(min=3)
367 367 rhodecode_support_url = v.UnicodeString()
368 368 rhodecode_show_revision_number = v.StringBoolean(if_missing=False)
369 369 rhodecode_show_sha_length = v.Int(min=4, not_empty=True)
370 370
371 371 return _ApplicationVisualisationForm
372 372
373 373
374 374 class _BaseVcsSettingsForm(formencode.Schema):
375 375 allow_extra_fields = True
376 376 filter_extra_fields = False
377 377 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
378 378 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
379 379 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
380 380
381 381 # PR/Code-review
382 382 rhodecode_pr_merge_enabled = v.StringBoolean(if_missing=False)
383 383 rhodecode_use_outdated_comments = v.StringBoolean(if_missing=False)
384 384
385 385 # hg
386 386 extensions_largefiles = v.StringBoolean(if_missing=False)
387 extensions_evolve = v.StringBoolean(if_missing=False)
387 388 phases_publish = v.StringBoolean(if_missing=False)
388 389 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
389 390
390 391 # git
391 392 vcs_git_lfs_enabled = v.StringBoolean(if_missing=False)
392 393
393 394 # svn
394 395 vcs_svn_proxy_http_requests_enabled = v.StringBoolean(if_missing=False)
395 396 vcs_svn_proxy_http_server_url = v.UnicodeString(strip=True, if_missing=None)
396 397
397 398
398 399 def ApplicationUiSettingsForm():
399 400 class _ApplicationUiSettingsForm(_BaseVcsSettingsForm):
400 401 web_push_ssl = v.StringBoolean(if_missing=False)
401 402 paths_root_path = All(
402 403 v.ValidPath(),
403 404 v.UnicodeString(strip=True, min=1, not_empty=True)
404 405 )
405 406 largefiles_usercache = All(
406 407 v.ValidPath(),
407 408 v.UnicodeString(strip=True, min=2, not_empty=True))
408 409 vcs_git_lfs_store_location = All(
409 410 v.ValidPath(),
410 411 v.UnicodeString(strip=True, min=2, not_empty=True))
411 412 extensions_hgsubversion = v.StringBoolean(if_missing=False)
412 413 extensions_hggit = v.StringBoolean(if_missing=False)
413 414 new_svn_branch = v.ValidSvnPattern(section='vcs_svn_branch')
414 415 new_svn_tag = v.ValidSvnPattern(section='vcs_svn_tag')
415 416
416 417 return _ApplicationUiSettingsForm
417 418
418 419
419 420 def RepoVcsSettingsForm(repo_name):
420 421 class _RepoVcsSettingsForm(_BaseVcsSettingsForm):
421 422 inherit_global_settings = v.StringBoolean(if_missing=False)
422 423 new_svn_branch = v.ValidSvnPattern(
423 424 section='vcs_svn_branch', repo_name=repo_name)
424 425 new_svn_tag = v.ValidSvnPattern(
425 426 section='vcs_svn_tag', repo_name=repo_name)
426 427
427 428 return _RepoVcsSettingsForm
428 429
429 430
430 431 def LabsSettingsForm():
431 432 class _LabSettingsForm(formencode.Schema):
432 433 allow_extra_fields = True
433 434 filter_extra_fields = False
434 435
435 436 return _LabSettingsForm
436 437
437 438
438 439 def ApplicationPermissionsForm(
439 440 register_choices, password_reset_choices, extern_activate_choices):
440 441 class _DefaultPermissionsForm(formencode.Schema):
441 442 allow_extra_fields = True
442 443 filter_extra_fields = True
443 444
444 445 anonymous = v.StringBoolean(if_missing=False)
445 446 default_register = v.OneOf(register_choices)
446 447 default_register_message = v.UnicodeString()
447 448 default_password_reset = v.OneOf(password_reset_choices)
448 449 default_extern_activate = v.OneOf(extern_activate_choices)
449 450
450 451 return _DefaultPermissionsForm
451 452
452 453
453 454 def ObjectPermissionsForm(repo_perms_choices, group_perms_choices,
454 455 user_group_perms_choices):
455 456 class _ObjectPermissionsForm(formencode.Schema):
456 457 allow_extra_fields = True
457 458 filter_extra_fields = True
458 459 overwrite_default_repo = v.StringBoolean(if_missing=False)
459 460 overwrite_default_group = v.StringBoolean(if_missing=False)
460 461 overwrite_default_user_group = v.StringBoolean(if_missing=False)
461 462 default_repo_perm = v.OneOf(repo_perms_choices)
462 463 default_group_perm = v.OneOf(group_perms_choices)
463 464 default_user_group_perm = v.OneOf(user_group_perms_choices)
464 465
465 466 return _ObjectPermissionsForm
466 467
467 468
468 469 def UserPermissionsForm(create_choices, create_on_write_choices,
469 470 repo_group_create_choices, user_group_create_choices,
470 471 fork_choices, inherit_default_permissions_choices):
471 472 class _DefaultPermissionsForm(formencode.Schema):
472 473 allow_extra_fields = True
473 474 filter_extra_fields = True
474 475
475 476 anonymous = v.StringBoolean(if_missing=False)
476 477
477 478 default_repo_create = v.OneOf(create_choices)
478 479 default_repo_create_on_write = v.OneOf(create_on_write_choices)
479 480 default_user_group_create = v.OneOf(user_group_create_choices)
480 481 default_repo_group_create = v.OneOf(repo_group_create_choices)
481 482 default_fork_create = v.OneOf(fork_choices)
482 483 default_inherit_default_permissions = v.OneOf(inherit_default_permissions_choices)
483 484
484 485 return _DefaultPermissionsForm
485 486
486 487
487 488 def UserIndividualPermissionsForm():
488 489 class _DefaultPermissionsForm(formencode.Schema):
489 490 allow_extra_fields = True
490 491 filter_extra_fields = True
491 492
492 493 inherit_default_permissions = v.StringBoolean(if_missing=False)
493 494
494 495 return _DefaultPermissionsForm
495 496
496 497
497 498 def DefaultsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
498 499 class _DefaultsForm(formencode.Schema):
499 500 allow_extra_fields = True
500 501 filter_extra_fields = True
501 502 default_repo_type = v.OneOf(supported_backends)
502 503 default_repo_private = v.StringBoolean(if_missing=False)
503 504 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
504 505 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
505 506 default_repo_enable_locking = v.StringBoolean(if_missing=False)
506 507
507 508 return _DefaultsForm
508 509
509 510
510 511 def AuthSettingsForm():
511 512 class _AuthSettingsForm(formencode.Schema):
512 513 allow_extra_fields = True
513 514 filter_extra_fields = True
514 515 auth_plugins = All(v.ValidAuthPlugins(),
515 516 v.UniqueListFromString()(not_empty=True))
516 517
517 518 return _AuthSettingsForm
518 519
519 520
520 521 def UserExtraEmailForm():
521 522 class _UserExtraEmailForm(formencode.Schema):
522 523 email = All(v.UniqSystemEmail(), v.Email(not_empty=True))
523 524 return _UserExtraEmailForm
524 525
525 526
526 527 def UserExtraIpForm():
527 528 class _UserExtraIpForm(formencode.Schema):
528 529 ip = v.ValidIp()(not_empty=True)
529 530 return _UserExtraIpForm
530 531
531 532
532 533
533 534 def PullRequestForm(repo_id):
534 535 class ReviewerForm(formencode.Schema):
535 536 user_id = v.Int(not_empty=True)
536 537 reasons = All()
537 538
538 539 class _PullRequestForm(formencode.Schema):
539 540 allow_extra_fields = True
540 541 filter_extra_fields = True
541 542
542 543 user = v.UnicodeString(strip=True, required=True)
543 544 source_repo = v.UnicodeString(strip=True, required=True)
544 545 source_ref = v.UnicodeString(strip=True, required=True)
545 546 target_repo = v.UnicodeString(strip=True, required=True)
546 547 target_ref = v.UnicodeString(strip=True, required=True)
547 548 revisions = All(#v.NotReviewedRevisions(repo_id)(),
548 549 v.UniqueList()(not_empty=True))
549 550 review_members = formencode.ForEach(ReviewerForm())
550 551 pullrequest_title = v.UnicodeString(strip=True, required=True)
551 552 pullrequest_desc = v.UnicodeString(strip=True, required=False)
552 553
553 554 return _PullRequestForm
554 555
555 556
556 557 def IssueTrackerPatternsForm():
557 558 class _IssueTrackerPatternsForm(formencode.Schema):
558 559 allow_extra_fields = True
559 560 filter_extra_fields = False
560 561 chained_validators = [v.ValidPattern()]
561 562 return _IssueTrackerPatternsForm
@@ -1,802 +1,810 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 import os
22 22 import hashlib
23 23 import logging
24 24 from collections import namedtuple
25 25 from functools import wraps
26 26
27 27 from rhodecode.lib import caches
28 28 from rhodecode.lib.utils2 import (
29 29 Optional, AttributeDict, safe_str, remove_prefix, str2bool)
30 30 from rhodecode.lib.vcs.backends import base
31 31 from rhodecode.model import BaseModel
32 32 from rhodecode.model.db import (
33 33 RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi, RhodeCodeSetting)
34 34 from rhodecode.model.meta import Session
35 35
36 36
37 37 log = logging.getLogger(__name__)
38 38
39 39
40 40 UiSetting = namedtuple(
41 41 'UiSetting', ['section', 'key', 'value', 'active'])
42 42
43 43 SOCIAL_PLUGINS_LIST = ['github', 'bitbucket', 'twitter', 'google']
44 44
45 45
46 46 class SettingNotFound(Exception):
47 47 def __init__(self):
48 48 super(SettingNotFound, self).__init__('Setting is not found')
49 49
50 50
51 51 class SettingsModel(BaseModel):
52 52 BUILTIN_HOOKS = (
53 53 RhodeCodeUi.HOOK_REPO_SIZE, RhodeCodeUi.HOOK_PUSH,
54 54 RhodeCodeUi.HOOK_PRE_PUSH, RhodeCodeUi.HOOK_PRETX_PUSH,
55 55 RhodeCodeUi.HOOK_PULL, RhodeCodeUi.HOOK_PRE_PULL)
56 56 HOOKS_SECTION = 'hooks'
57 57
58 58 def __init__(self, sa=None, repo=None):
59 59 self.repo = repo
60 60 self.UiDbModel = RepoRhodeCodeUi if repo else RhodeCodeUi
61 61 self.SettingsDbModel = (
62 62 RepoRhodeCodeSetting if repo else RhodeCodeSetting)
63 63 super(SettingsModel, self).__init__(sa)
64 64
65 65 def get_ui_by_key(self, key):
66 66 q = self.UiDbModel.query()
67 67 q = q.filter(self.UiDbModel.ui_key == key)
68 68 q = self._filter_by_repo(RepoRhodeCodeUi, q)
69 69 return q.scalar()
70 70
71 71 def get_ui_by_section(self, section):
72 72 q = self.UiDbModel.query()
73 73 q = q.filter(self.UiDbModel.ui_section == section)
74 74 q = self._filter_by_repo(RepoRhodeCodeUi, q)
75 75 return q.all()
76 76
77 77 def get_ui_by_section_and_key(self, section, key):
78 78 q = self.UiDbModel.query()
79 79 q = q.filter(self.UiDbModel.ui_section == section)
80 80 q = q.filter(self.UiDbModel.ui_key == key)
81 81 q = self._filter_by_repo(RepoRhodeCodeUi, q)
82 82 return q.scalar()
83 83
84 84 def get_ui(self, section=None, key=None):
85 85 q = self.UiDbModel.query()
86 86 q = self._filter_by_repo(RepoRhodeCodeUi, q)
87 87
88 88 if section:
89 89 q = q.filter(self.UiDbModel.ui_section == section)
90 90 if key:
91 91 q = q.filter(self.UiDbModel.ui_key == key)
92 92
93 93 # TODO: mikhail: add caching
94 94 result = [
95 95 UiSetting(
96 96 section=safe_str(r.ui_section), key=safe_str(r.ui_key),
97 97 value=safe_str(r.ui_value), active=r.ui_active
98 98 )
99 99 for r in q.all()
100 100 ]
101 101 return result
102 102
103 103 def get_builtin_hooks(self):
104 104 q = self.UiDbModel.query()
105 105 q = q.filter(self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
106 106 return self._get_hooks(q)
107 107
108 108 def get_custom_hooks(self):
109 109 q = self.UiDbModel.query()
110 110 q = q.filter(~self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
111 111 return self._get_hooks(q)
112 112
113 113 def create_ui_section_value(self, section, val, key=None, active=True):
114 114 new_ui = self.UiDbModel()
115 115 new_ui.ui_section = section
116 116 new_ui.ui_value = val
117 117 new_ui.ui_active = active
118 118
119 119 if self.repo:
120 120 repo = self._get_repo(self.repo)
121 121 repository_id = repo.repo_id
122 122 new_ui.repository_id = repository_id
123 123
124 124 if not key:
125 125 # keys are unique so they need appended info
126 126 if self.repo:
127 127 key = hashlib.sha1(
128 128 '{}{}{}'.format(section, val, repository_id)).hexdigest()
129 129 else:
130 130 key = hashlib.sha1('{}{}'.format(section, val)).hexdigest()
131 131
132 132 new_ui.ui_key = key
133 133
134 134 Session().add(new_ui)
135 135 return new_ui
136 136
137 137 def create_or_update_hook(self, key, value):
138 138 ui = (
139 139 self.get_ui_by_section_and_key(self.HOOKS_SECTION, key) or
140 140 self.UiDbModel())
141 141 ui.ui_section = self.HOOKS_SECTION
142 142 ui.ui_active = True
143 143 ui.ui_key = key
144 144 ui.ui_value = value
145 145
146 146 if self.repo:
147 147 repo = self._get_repo(self.repo)
148 148 repository_id = repo.repo_id
149 149 ui.repository_id = repository_id
150 150
151 151 Session().add(ui)
152 152 return ui
153 153
154 154 def delete_ui(self, id_):
155 155 ui = self.UiDbModel.get(id_)
156 156 if not ui:
157 157 raise SettingNotFound()
158 158 Session().delete(ui)
159 159
160 160 def get_setting_by_name(self, name):
161 161 q = self._get_settings_query()
162 162 q = q.filter(self.SettingsDbModel.app_settings_name == name)
163 163 return q.scalar()
164 164
165 165 def create_or_update_setting(
166 166 self, name, val=Optional(''), type_=Optional('unicode')):
167 167 """
168 168 Creates or updates RhodeCode setting. If updates is triggered it will
169 169 only update parameters that are explicityl set Optional instance will
170 170 be skipped
171 171
172 172 :param name:
173 173 :param val:
174 174 :param type_:
175 175 :return:
176 176 """
177 177
178 178 res = self.get_setting_by_name(name)
179 179 repo = self._get_repo(self.repo) if self.repo else None
180 180
181 181 if not res:
182 182 val = Optional.extract(val)
183 183 type_ = Optional.extract(type_)
184 184
185 185 args = (
186 186 (repo.repo_id, name, val, type_)
187 187 if repo else (name, val, type_))
188 188 res = self.SettingsDbModel(*args)
189 189
190 190 else:
191 191 if self.repo:
192 192 res.repository_id = repo.repo_id
193 193
194 194 res.app_settings_name = name
195 195 if not isinstance(type_, Optional):
196 196 # update if set
197 197 res.app_settings_type = type_
198 198 if not isinstance(val, Optional):
199 199 # update if set
200 200 res.app_settings_value = val
201 201
202 202 Session().add(res)
203 203 return res
204 204
205 205 def invalidate_settings_cache(self):
206 206 namespace = 'rhodecode_settings'
207 207 cache_manager = caches.get_cache_manager('sql_cache_short', namespace)
208 208 caches.clear_cache_manager(cache_manager)
209 209
210 210 def get_all_settings(self, cache=False):
211 211
212 212 def _compute():
213 213 q = self._get_settings_query()
214 214 if not q:
215 215 raise Exception('Could not get application settings !')
216 216
217 217 settings = {
218 218 'rhodecode_' + result.app_settings_name: result.app_settings_value
219 219 for result in q
220 220 }
221 221 return settings
222 222
223 223 if cache:
224 224 log.debug('Fetching app settings using cache')
225 225 repo = self._get_repo(self.repo) if self.repo else None
226 226 namespace = 'rhodecode_settings'
227 227 cache_manager = caches.get_cache_manager(
228 228 'sql_cache_short', namespace)
229 229 _cache_key = (
230 230 "get_repo_{}_settings".format(repo.repo_id)
231 231 if repo else "get_app_settings")
232 232
233 233 return cache_manager.get(_cache_key, createfunc=_compute)
234 234
235 235 else:
236 236 return _compute()
237 237
238 238 def get_auth_settings(self):
239 239 q = self._get_settings_query()
240 240 q = q.filter(
241 241 self.SettingsDbModel.app_settings_name.startswith('auth_'))
242 242 rows = q.all()
243 243 auth_settings = {
244 244 row.app_settings_name: row.app_settings_value for row in rows}
245 245 return auth_settings
246 246
247 247 def get_auth_plugins(self):
248 248 auth_plugins = self.get_setting_by_name("auth_plugins")
249 249 return auth_plugins.app_settings_value
250 250
251 251 def get_default_repo_settings(self, strip_prefix=False):
252 252 q = self._get_settings_query()
253 253 q = q.filter(
254 254 self.SettingsDbModel.app_settings_name.startswith('default_'))
255 255 rows = q.all()
256 256
257 257 result = {}
258 258 for row in rows:
259 259 key = row.app_settings_name
260 260 if strip_prefix:
261 261 key = remove_prefix(key, prefix='default_')
262 262 result.update({key: row.app_settings_value})
263 263 return result
264 264
265 265 def get_repo(self):
266 266 repo = self._get_repo(self.repo)
267 267 if not repo:
268 268 raise Exception(
269 269 'Repository `{}` cannot be found inside the database'.format(
270 270 self.repo))
271 271 return repo
272 272
273 273 def _filter_by_repo(self, model, query):
274 274 if self.repo:
275 275 repo = self.get_repo()
276 276 query = query.filter(model.repository_id == repo.repo_id)
277 277 return query
278 278
279 279 def _get_hooks(self, query):
280 280 query = query.filter(self.UiDbModel.ui_section == self.HOOKS_SECTION)
281 281 query = self._filter_by_repo(RepoRhodeCodeUi, query)
282 282 return query.all()
283 283
284 284 def _get_settings_query(self):
285 285 q = self.SettingsDbModel.query()
286 286 return self._filter_by_repo(RepoRhodeCodeSetting, q)
287 287
288 288 def list_enabled_social_plugins(self, settings):
289 289 enabled = []
290 290 for plug in SOCIAL_PLUGINS_LIST:
291 291 if str2bool(settings.get('rhodecode_auth_{}_enabled'.format(plug)
292 292 )):
293 293 enabled.append(plug)
294 294 return enabled
295 295
296 296
297 297 def assert_repo_settings(func):
298 298 @wraps(func)
299 299 def _wrapper(self, *args, **kwargs):
300 300 if not self.repo_settings:
301 301 raise Exception('Repository is not specified')
302 302 return func(self, *args, **kwargs)
303 303 return _wrapper
304 304
305 305
306 306 class IssueTrackerSettingsModel(object):
307 307 INHERIT_SETTINGS = 'inherit_issue_tracker_settings'
308 308 SETTINGS_PREFIX = 'issuetracker_'
309 309
310 310 def __init__(self, sa=None, repo=None):
311 311 self.global_settings = SettingsModel(sa=sa)
312 312 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
313 313
314 314 @property
315 315 def inherit_global_settings(self):
316 316 if not self.repo_settings:
317 317 return True
318 318 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
319 319 return setting.app_settings_value if setting else True
320 320
321 321 @inherit_global_settings.setter
322 322 def inherit_global_settings(self, value):
323 323 if self.repo_settings:
324 324 settings = self.repo_settings.create_or_update_setting(
325 325 self.INHERIT_SETTINGS, value, type_='bool')
326 326 Session().add(settings)
327 327
328 328 def _get_keyname(self, key, uid, prefix=''):
329 329 return '{0}{1}{2}_{3}'.format(
330 330 prefix, self.SETTINGS_PREFIX, key, uid)
331 331
332 332 def _make_dict_for_settings(self, qs):
333 333 prefix_match = self._get_keyname('pat', '', 'rhodecode_')
334 334
335 335 issuetracker_entries = {}
336 336 # create keys
337 337 for k, v in qs.items():
338 338 if k.startswith(prefix_match):
339 339 uid = k[len(prefix_match):]
340 340 issuetracker_entries[uid] = None
341 341
342 342 # populate
343 343 for uid in issuetracker_entries:
344 344 issuetracker_entries[uid] = AttributeDict({
345 345 'pat': qs.get(self._get_keyname('pat', uid, 'rhodecode_')),
346 346 'url': qs.get(self._get_keyname('url', uid, 'rhodecode_')),
347 347 'pref': qs.get(self._get_keyname('pref', uid, 'rhodecode_')),
348 348 'desc': qs.get(self._get_keyname('desc', uid, 'rhodecode_')),
349 349 })
350 350 return issuetracker_entries
351 351
352 352 def get_global_settings(self, cache=False):
353 353 """
354 354 Returns list of global issue tracker settings
355 355 """
356 356 defaults = self.global_settings.get_all_settings(cache=cache)
357 357 settings = self._make_dict_for_settings(defaults)
358 358 return settings
359 359
360 360 def get_repo_settings(self, cache=False):
361 361 """
362 362 Returns list of issue tracker settings per repository
363 363 """
364 364 if not self.repo_settings:
365 365 raise Exception('Repository is not specified')
366 366 all_settings = self.repo_settings.get_all_settings(cache=cache)
367 367 settings = self._make_dict_for_settings(all_settings)
368 368 return settings
369 369
370 370 def get_settings(self, cache=False):
371 371 if self.inherit_global_settings:
372 372 return self.get_global_settings(cache=cache)
373 373 else:
374 374 return self.get_repo_settings(cache=cache)
375 375
376 376 def delete_entries(self, uid):
377 377 if self.repo_settings:
378 378 all_patterns = self.get_repo_settings()
379 379 settings_model = self.repo_settings
380 380 else:
381 381 all_patterns = self.get_global_settings()
382 382 settings_model = self.global_settings
383 383 entries = all_patterns.get(uid)
384 384
385 385 for del_key in entries:
386 386 setting_name = self._get_keyname(del_key, uid)
387 387 entry = settings_model.get_setting_by_name(setting_name)
388 388 if entry:
389 389 Session().delete(entry)
390 390
391 391 Session().commit()
392 392
393 393 def create_or_update_setting(
394 394 self, name, val=Optional(''), type_=Optional('unicode')):
395 395 if self.repo_settings:
396 396 setting = self.repo_settings.create_or_update_setting(
397 397 name, val, type_)
398 398 else:
399 399 setting = self.global_settings.create_or_update_setting(
400 400 name, val, type_)
401 401 return setting
402 402
403 403
404 404 class VcsSettingsModel(object):
405 405
406 406 INHERIT_SETTINGS = 'inherit_vcs_settings'
407 407 GENERAL_SETTINGS = (
408 408 'use_outdated_comments',
409 409 'pr_merge_enabled',
410 410 'hg_use_rebase_for_merging')
411 411
412 412 HOOKS_SETTINGS = (
413 413 ('hooks', 'changegroup.repo_size'),
414 414 ('hooks', 'changegroup.push_logger'),
415 415 ('hooks', 'outgoing.pull_logger'),)
416 416 HG_SETTINGS = (
417 417 ('extensions', 'largefiles'),
418 ('phases', 'publish'),)
418 ('phases', 'publish'),
419 ('extensions', 'evolve'),)
419 420 GIT_SETTINGS = (
420 421 ('vcs_git_lfs', 'enabled'),)
421
422 422 GLOBAL_HG_SETTINGS = (
423 423 ('extensions', 'largefiles'),
424 424 ('largefiles', 'usercache'),
425 425 ('phases', 'publish'),
426 ('extensions', 'hgsubversion'))
426 ('extensions', 'hgsubversion'),
427 ('extensions', 'evolve'),)
427 428 GLOBAL_GIT_SETTINGS = (
428 429 ('vcs_git_lfs', 'enabled'),
429 430 ('vcs_git_lfs', 'store_location'))
430 431 GLOBAL_SVN_SETTINGS = (
431 432 ('vcs_svn_proxy', 'http_requests_enabled'),
432 433 ('vcs_svn_proxy', 'http_server_url'))
433 434
434 435 SVN_BRANCH_SECTION = 'vcs_svn_branch'
435 436 SVN_TAG_SECTION = 'vcs_svn_tag'
436 437 SSL_SETTING = ('web', 'push_ssl')
437 438 PATH_SETTING = ('paths', '/')
438 439
439 440 def __init__(self, sa=None, repo=None):
440 441 self.global_settings = SettingsModel(sa=sa)
441 442 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
442 443 self._ui_settings = (
443 444 self.HG_SETTINGS + self.GIT_SETTINGS + self.HOOKS_SETTINGS)
444 445 self._svn_sections = (self.SVN_BRANCH_SECTION, self.SVN_TAG_SECTION)
445 446
446 447 @property
447 448 @assert_repo_settings
448 449 def inherit_global_settings(self):
449 450 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
450 451 return setting.app_settings_value if setting else True
451 452
452 453 @inherit_global_settings.setter
453 454 @assert_repo_settings
454 455 def inherit_global_settings(self, value):
455 456 self.repo_settings.create_or_update_setting(
456 457 self.INHERIT_SETTINGS, value, type_='bool')
457 458
458 459 def get_global_svn_branch_patterns(self):
459 460 return self.global_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
460 461
461 462 @assert_repo_settings
462 463 def get_repo_svn_branch_patterns(self):
463 464 return self.repo_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
464 465
465 466 def get_global_svn_tag_patterns(self):
466 467 return self.global_settings.get_ui_by_section(self.SVN_TAG_SECTION)
467 468
468 469 @assert_repo_settings
469 470 def get_repo_svn_tag_patterns(self):
470 471 return self.repo_settings.get_ui_by_section(self.SVN_TAG_SECTION)
471 472
472 473 def get_global_settings(self):
473 474 return self._collect_all_settings(global_=True)
474 475
475 476 @assert_repo_settings
476 477 def get_repo_settings(self):
477 478 return self._collect_all_settings(global_=False)
478 479
479 480 @assert_repo_settings
480 481 def create_or_update_repo_settings(
481 482 self, data, inherit_global_settings=False):
482 483 from rhodecode.model.scm import ScmModel
483 484
484 485 self.inherit_global_settings = inherit_global_settings
485 486
486 487 repo = self.repo_settings.get_repo()
487 488 if not inherit_global_settings:
488 489 if repo.repo_type == 'svn':
489 490 self.create_repo_svn_settings(data)
490 491 else:
491 492 self.create_or_update_repo_hook_settings(data)
492 493 self.create_or_update_repo_pr_settings(data)
493 494
494 495 if repo.repo_type == 'hg':
495 496 self.create_or_update_repo_hg_settings(data)
496 497
497 498 if repo.repo_type == 'git':
498 499 self.create_or_update_repo_git_settings(data)
499 500
500 501 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
501 502
502 503 @assert_repo_settings
503 504 def create_or_update_repo_hook_settings(self, data):
504 505 for section, key in self.HOOKS_SETTINGS:
505 506 data_key = self._get_form_ui_key(section, key)
506 507 if data_key not in data:
507 508 raise ValueError(
508 509 'The given data does not contain {} key'.format(data_key))
509 510
510 511 active = data.get(data_key)
511 512 repo_setting = self.repo_settings.get_ui_by_section_and_key(
512 513 section, key)
513 514 if not repo_setting:
514 515 global_setting = self.global_settings.\
515 516 get_ui_by_section_and_key(section, key)
516 517 self.repo_settings.create_ui_section_value(
517 518 section, global_setting.ui_value, key=key, active=active)
518 519 else:
519 520 repo_setting.ui_active = active
520 521 Session().add(repo_setting)
521 522
522 523 def update_global_hook_settings(self, data):
523 524 for section, key in self.HOOKS_SETTINGS:
524 525 data_key = self._get_form_ui_key(section, key)
525 526 if data_key not in data:
526 527 raise ValueError(
527 528 'The given data does not contain {} key'.format(data_key))
528 529 active = data.get(data_key)
529 530 repo_setting = self.global_settings.get_ui_by_section_and_key(
530 531 section, key)
531 532 repo_setting.ui_active = active
532 533 Session().add(repo_setting)
533 534
534 535 @assert_repo_settings
535 536 def create_or_update_repo_pr_settings(self, data):
536 537 return self._create_or_update_general_settings(
537 538 self.repo_settings, data)
538 539
539 540 def create_or_update_global_pr_settings(self, data):
540 541 return self._create_or_update_general_settings(
541 542 self.global_settings, data)
542 543
543 544 @assert_repo_settings
544 545 def create_repo_svn_settings(self, data):
545 546 return self._create_svn_settings(self.repo_settings, data)
546 547
547 548 @assert_repo_settings
548 549 def create_or_update_repo_hg_settings(self, data):
549 largefiles, phases = \
550 largefiles, phases, evolve = \
550 551 self.HG_SETTINGS
551 largefiles_key, phases_key = \
552 largefiles_key, phases_key, evolve_key = \
552 553 self._get_settings_keys(self.HG_SETTINGS, data)
553 554
554 555 self._create_or_update_ui(
555 556 self.repo_settings, *largefiles, value='',
556 557 active=data[largefiles_key])
557 558 self._create_or_update_ui(
559 self.repo_settings, *evolve, value='',
560 active=data[evolve_key])
561 self._create_or_update_ui(
558 562 self.repo_settings, *phases, value=safe_str(data[phases_key]))
559 563
560 564 def create_or_update_global_hg_settings(self, data):
561 largefiles, largefiles_store, phases, hgsubversion \
565 largefiles, largefiles_store, phases, hgsubversion, evolve \
562 566 = self.GLOBAL_HG_SETTINGS
563 largefiles_key, largefiles_store_key, phases_key, subversion_key \
567 largefiles_key, largefiles_store_key, phases_key, subversion_key, evolve_key \
564 568 = self._get_settings_keys(self.GLOBAL_HG_SETTINGS, data)
569
565 570 self._create_or_update_ui(
566 571 self.global_settings, *largefiles, value='',
567 572 active=data[largefiles_key])
568 573 self._create_or_update_ui(
569 574 self.global_settings, *largefiles_store,
570 575 value=data[largefiles_store_key])
571 576 self._create_or_update_ui(
572 577 self.global_settings, *phases, value=safe_str(data[phases_key]))
573 578 self._create_or_update_ui(
574 579 self.global_settings, *hgsubversion, active=data[subversion_key])
580 self._create_or_update_ui(
581 self.global_settings, *evolve, value='',
582 active=data[evolve_key])
575 583
576 584 def create_or_update_repo_git_settings(self, data):
577 585 # NOTE(marcink): # comma make unpack work properly
578 586 lfs_enabled, \
579 587 = self.GIT_SETTINGS
580 588
581 589 lfs_enabled_key, \
582 590 = self._get_settings_keys(self.GIT_SETTINGS, data)
583 591
584 592 self._create_or_update_ui(
585 593 self.repo_settings, *lfs_enabled, value=data[lfs_enabled_key],
586 594 active=data[lfs_enabled_key])
587 595
588 596 def create_or_update_global_git_settings(self, data):
589 597 lfs_enabled, lfs_store_location \
590 598 = self.GLOBAL_GIT_SETTINGS
591 599 lfs_enabled_key, lfs_store_location_key \
592 600 = self._get_settings_keys(self.GLOBAL_GIT_SETTINGS, data)
593 601
594 602 self._create_or_update_ui(
595 603 self.global_settings, *lfs_enabled, value=data[lfs_enabled_key],
596 604 active=data[lfs_enabled_key])
597 605 self._create_or_update_ui(
598 606 self.global_settings, *lfs_store_location,
599 607 value=data[lfs_store_location_key])
600 608
601 609 def create_or_update_global_svn_settings(self, data):
602 610 # branch/tags patterns
603 611 self._create_svn_settings(self.global_settings, data)
604 612
605 613 http_requests_enabled, http_server_url = self.GLOBAL_SVN_SETTINGS
606 614 http_requests_enabled_key, http_server_url_key = self._get_settings_keys(
607 615 self.GLOBAL_SVN_SETTINGS, data)
608 616
609 617 self._create_or_update_ui(
610 618 self.global_settings, *http_requests_enabled,
611 619 value=safe_str(data[http_requests_enabled_key]))
612 620 self._create_or_update_ui(
613 621 self.global_settings, *http_server_url,
614 622 value=data[http_server_url_key])
615 623
616 624 def update_global_ssl_setting(self, value):
617 625 self._create_or_update_ui(
618 626 self.global_settings, *self.SSL_SETTING, value=value)
619 627
620 628 def update_global_path_setting(self, value):
621 629 self._create_or_update_ui(
622 630 self.global_settings, *self.PATH_SETTING, value=value)
623 631
624 632 @assert_repo_settings
625 633 def delete_repo_svn_pattern(self, id_):
626 634 self.repo_settings.delete_ui(id_)
627 635
628 636 def delete_global_svn_pattern(self, id_):
629 637 self.global_settings.delete_ui(id_)
630 638
631 639 @assert_repo_settings
632 640 def get_repo_ui_settings(self, section=None, key=None):
633 641 global_uis = self.global_settings.get_ui(section, key)
634 642 repo_uis = self.repo_settings.get_ui(section, key)
635 643 filtered_repo_uis = self._filter_ui_settings(repo_uis)
636 644 filtered_repo_uis_keys = [
637 645 (s.section, s.key) for s in filtered_repo_uis]
638 646
639 647 def _is_global_ui_filtered(ui):
640 648 return (
641 649 (ui.section, ui.key) in filtered_repo_uis_keys
642 650 or ui.section in self._svn_sections)
643 651
644 652 filtered_global_uis = [
645 653 ui for ui in global_uis if not _is_global_ui_filtered(ui)]
646 654
647 655 return filtered_global_uis + filtered_repo_uis
648 656
649 657 def get_global_ui_settings(self, section=None, key=None):
650 658 return self.global_settings.get_ui(section, key)
651 659
652 660 def get_ui_settings_as_config_obj(self, section=None, key=None):
653 661 config = base.Config()
654 662
655 663 ui_settings = self.get_ui_settings(section=section, key=key)
656 664
657 665 for entry in ui_settings:
658 666 config.set(entry.section, entry.key, entry.value)
659 667
660 668 return config
661 669
662 670 def get_ui_settings(self, section=None, key=None):
663 671 if not self.repo_settings or self.inherit_global_settings:
664 672 return self.get_global_ui_settings(section, key)
665 673 else:
666 674 return self.get_repo_ui_settings(section, key)
667 675
668 676 def get_svn_patterns(self, section=None):
669 677 if not self.repo_settings:
670 678 return self.get_global_ui_settings(section)
671 679 else:
672 680 return self.get_repo_ui_settings(section)
673 681
674 682 @assert_repo_settings
675 683 def get_repo_general_settings(self):
676 684 global_settings = self.global_settings.get_all_settings()
677 685 repo_settings = self.repo_settings.get_all_settings()
678 686 filtered_repo_settings = self._filter_general_settings(repo_settings)
679 687 global_settings.update(filtered_repo_settings)
680 688 return global_settings
681 689
682 690 def get_global_general_settings(self):
683 691 return self.global_settings.get_all_settings()
684 692
685 693 def get_general_settings(self):
686 694 if not self.repo_settings or self.inherit_global_settings:
687 695 return self.get_global_general_settings()
688 696 else:
689 697 return self.get_repo_general_settings()
690 698
691 699 def get_repos_location(self):
692 700 return self.global_settings.get_ui_by_key('/').ui_value
693 701
694 702 def _filter_ui_settings(self, settings):
695 703 filtered_settings = [
696 704 s for s in settings if self._should_keep_setting(s)]
697 705 return filtered_settings
698 706
699 707 def _should_keep_setting(self, setting):
700 708 keep = (
701 709 (setting.section, setting.key) in self._ui_settings or
702 710 setting.section in self._svn_sections)
703 711 return keep
704 712
705 713 def _filter_general_settings(self, settings):
706 714 keys = ['rhodecode_{}'.format(key) for key in self.GENERAL_SETTINGS]
707 715 return {
708 716 k: settings[k]
709 717 for k in settings if k in keys}
710 718
711 719 def _collect_all_settings(self, global_=False):
712 720 settings = self.global_settings if global_ else self.repo_settings
713 721 result = {}
714 722
715 723 for section, key in self._ui_settings:
716 724 ui = settings.get_ui_by_section_and_key(section, key)
717 725 result_key = self._get_form_ui_key(section, key)
718 726
719 727 if ui:
720 728 if section in ('hooks', 'extensions'):
721 729 result[result_key] = ui.ui_active
722 730 elif result_key in ['vcs_git_lfs_enabled']:
723 731 result[result_key] = ui.ui_active
724 732 else:
725 733 result[result_key] = ui.ui_value
726 734
727 735 for name in self.GENERAL_SETTINGS:
728 736 setting = settings.get_setting_by_name(name)
729 737 if setting:
730 738 result_key = 'rhodecode_{}'.format(name)
731 739 result[result_key] = setting.app_settings_value
732 740
733 741 return result
734 742
735 743 def _get_form_ui_key(self, section, key):
736 744 return '{section}_{key}'.format(
737 745 section=section, key=key.replace('.', '_'))
738 746
739 747 def _create_or_update_ui(
740 748 self, settings, section, key, value=None, active=None):
741 749 ui = settings.get_ui_by_section_and_key(section, key)
742 750 if not ui:
743 751 active = True if active is None else active
744 752 settings.create_ui_section_value(
745 753 section, value, key=key, active=active)
746 754 else:
747 755 if active is not None:
748 756 ui.ui_active = active
749 757 if value is not None:
750 758 ui.ui_value = value
751 759 Session().add(ui)
752 760
753 761 def _create_svn_settings(self, settings, data):
754 762 svn_settings = {
755 763 'new_svn_branch': self.SVN_BRANCH_SECTION,
756 764 'new_svn_tag': self.SVN_TAG_SECTION
757 765 }
758 766 for key in svn_settings:
759 767 if data.get(key):
760 768 settings.create_ui_section_value(svn_settings[key], data[key])
761 769
762 770 def _create_or_update_general_settings(self, settings, data):
763 771 for name in self.GENERAL_SETTINGS:
764 772 data_key = 'rhodecode_{}'.format(name)
765 773 if data_key not in data:
766 774 raise ValueError(
767 775 'The given data does not contain {} key'.format(data_key))
768 776 setting = settings.create_or_update_setting(
769 777 name, data[data_key], 'bool')
770 778 Session().add(setting)
771 779
772 780 def _get_settings_keys(self, settings, data):
773 781 data_keys = [self._get_form_ui_key(*s) for s in settings]
774 782 for data_key in data_keys:
775 783 if data_key not in data:
776 784 raise ValueError(
777 785 'The given data does not contain {} key'.format(data_key))
778 786 return data_keys
779 787
780 788 def create_largeobjects_dirs_if_needed(self, repo_store_path):
781 789 """
782 790 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
783 791 does a repository scan if enabled in the settings.
784 792 """
785 793
786 794 from rhodecode.lib.vcs.backends.hg import largefiles_store
787 795 from rhodecode.lib.vcs.backends.git import lfs_store
788 796
789 797 paths = [
790 798 largefiles_store(repo_store_path),
791 799 lfs_store(repo_store_path)]
792 800
793 801 for path in paths:
794 802 if os.path.isdir(path):
795 803 continue
796 804 if os.path.isfile(path):
797 805 continue
798 806 # not a file nor dir, we try to create it
799 807 try:
800 808 os.makedirs(path)
801 809 except Exception:
802 810 log.warning('Failed to create largefiles dir:%s', path)
@@ -1,541 +1,546 b''
1 1
2 2 // tables.less
3 3 // For use in RhodeCode application tables;
4 4 // see style guide documentation for guidelines.
5 5
6 6 // TABLES
7 7
8 8 .rctable,
9 9 table.rctable,
10 10 table.dataTable {
11 11 clear:both;
12 12 width: 100%;
13 13 margin: 0 auto @padding;
14 14 padding: 0;
15 15 vertical-align: baseline;
16 16 line-height:1.5em;
17 17 border: none;
18 18 outline: none;
19 19 border-collapse: collapse;
20 20 border-spacing: 0;
21 21 color: @grey2;
22 22
23 23 b {
24 24 font-weight: normal;
25 25 }
26 26
27 27 em {
28 28 font-weight: bold;
29 29 font-style: normal;
30 30 }
31 31
32 32 th,
33 33 td {
34 34 height: auto;
35 35 max-width: 20%;
36 36 padding: .65em 1em .65em 0;
37 37 vertical-align: middle;
38 38 border-bottom: @border-thickness solid @grey5;
39 39 white-space: normal;
40 40
41 41 &.td-radio,
42 42 &.td-checkbox {
43 43 padding-right: 0;
44 44 text-align: center;
45 45
46 46 input {
47 47 margin: 0 1em;
48 48 }
49 49 }
50 50
51 51 &.truncate-wrap {
52 52 white-space: nowrap !important;
53 53 }
54 54
55 55 pre {
56 56 margin: 0;
57 57 }
58 58
59 59 .show_more {
60 60 height: inherit;
61 61 }
62 62 }
63 63
64 64 .expired td {
65 65 background-color: @grey7;
66 66 }
67 67
68 68 .td-radio + .td-owner {
69 69 padding-left: 1em;
70 70 }
71 71
72 72
73 73 th {
74 74 text-align: left;
75 75 font-family: @text-semibold;
76 76 }
77 77
78 78 .hl {
79 79 td {
80 80 background-color: lighten(@alert4,25%);
81 81 }
82 82 }
83 83
84 84 // Special Data Cell Types
85 85 // See style guide for desciptions and examples.
86 86
87 87 td {
88 88
89 89 &.user {
90 90 padding-left: 1em;
91 91 }
92 92
93 93 &.td-rss {
94 94 width: 20px;
95 95 min-width: 0;
96 96 margin: 0;
97 97 }
98 98
99 99 &.quick_repo_menu {
100 100 width: 15px;
101 101 text-align: center;
102 102
103 103 &:hover {
104 104 background-color: @grey5;
105 105 }
106 106 }
107 107
108 108 &.td-hash {
109 109 min-width: 80px;
110 110 width: 200px;
111
112 .obsolete {
113 text-decoration: line-through;
114 color: lighten(@grey2,25%);
115 }
111 116 }
112 117
113 118 &.td-time {
114 119 width: 160px;
115 120 white-space: nowrap;
116 121 }
117 122
118 123 &.annotate{
119 124 padding-right: 0;
120 125
121 126 div.annotatediv{
122 127 margin: 0 0.7em;
123 128 }
124 129 }
125 130
126 131 &.tags-col {
127 132 padding-right: 0;
128 133 }
129 134
130 135 &.td-description {
131 136 min-width: 350px;
132 137
133 138 &.truncate, .truncate-wrap {
134 139 white-space: nowrap;
135 140 overflow: hidden;
136 141 text-overflow: ellipsis;
137 142 max-width: 450px;
138 143 }
139 144 }
140 145
141 146 &.td-componentname {
142 147 white-space: nowrap;
143 148 }
144 149
145 150 &.td-journalaction {
146 151 min-width: 300px;
147 152
148 153 .journal_action_params {
149 154 // waiting for feedback
150 155 }
151 156 }
152 157
153 158 &.td-active {
154 159 padding-left: .65em;
155 160 }
156 161
157 162 &.td-url {
158 163 white-space: nowrap;
159 164 }
160 165
161 166 &.td-comments {
162 167 min-width: 3em;
163 168 }
164 169
165 170 &.td-buttons {
166 171 padding: .3em 0;
167 172 }
168 173
169 174 &.td-action {
170 175 // this is for the remove/delete/edit buttons
171 176 padding-right: 0;
172 177 min-width: 95px;
173 178 text-transform: capitalize;
174 179
175 180 i {
176 181 display: none;
177 182 }
178 183 }
179 184
180 185 // TODO: lisa: this needs to be cleaned up with the buttons
181 186 .grid_edit,
182 187 .grid_delete {
183 188 display: inline-block;
184 189 margin: 0 @padding/3 0 0;
185 190 font-family: @text-light;
186 191
187 192 i {
188 193 display: none;
189 194 }
190 195 }
191 196
192 197 .grid_edit + .grid_delete {
193 198 border-left: @border-thickness solid @grey5;
194 199 padding-left: @padding/2;
195 200 }
196 201
197 202 &.td-compare {
198 203
199 204 input {
200 205 margin-right: 1em;
201 206 }
202 207
203 208 .compare-radio-button {
204 209 margin: 0 1em 0 0;
205 210 }
206 211
207 212
208 213 }
209 214
210 215 &.td-tags {
211 216 padding: .5em 1em .5em 0;
212 217 width: 140px;
213 218
214 219 .tag {
215 220 margin: 1px;
216 221 float: left;
217 222 }
218 223 }
219 224
220 225 .icon-svn, .icon-hg, .icon-git {
221 226 font-size: 1.4em;
222 227 }
223 228
224 229 &.collapse_commit,
225 230 &.expand_commit {
226 231 padding-right: 0;
227 232 padding-left: 1em;
228 233 }
229 234 }
230 235
231 236 .perm_admin_row {
232 237 color: @grey4;
233 238 background-color: @grey6;
234 239 }
235 240
236 241 .noborder {
237 242 border: none;
238 243
239 244 td {
240 245 border: none;
241 246 }
242 247 }
243 248 }
244 249
245 250 // TRUNCATING
246 251 // TODO: lisaq: should this possibly be moved out of tables.less?
247 252 // for truncated text
248 253 // used inside of table cells and in code block headers
249 254 .truncate-wrap {
250 255 white-space: nowrap !important;
251 256
252 257 //truncated text
253 258 .truncate {
254 259 max-width: 450px;
255 260 width: 300px;
256 261 overflow: hidden;
257 262 text-overflow: ellipsis;
258 263 -o-text-overflow: ellipsis;
259 264 -ms-text-overflow: ellipsis;
260 265
261 266 &.autoexpand {
262 267 width: 120px;
263 268 margin-right: 200px;
264 269 }
265 270 }
266 271 &:hover .truncate.autoexpand {
267 272 overflow: visible;
268 273 }
269 274
270 275 .tags-truncate {
271 276 width: 150px;
272 277 height: 22px;
273 278 overflow: hidden;
274 279
275 280 .tag {
276 281 display: inline-block;
277 282 }
278 283
279 284 &.truncate {
280 285 height: 22px;
281 286 max-height:2em;
282 287 width: 140px;
283 288 }
284 289 }
285 290 }
286 291
287 292 .apikeys_wrap {
288 293 margin-bottom: @padding;
289 294
290 295 table.rctable td:first-child {
291 296 width: 340px;
292 297 }
293 298 }
294 299
295 300
296 301
297 302 // SPECIAL CASES
298 303
299 304 // Repository Followers
300 305 table.rctable.followers_data {
301 306 width: 75%;
302 307 margin: 0;
303 308 }
304 309
305 310 // Repository List
306 311 // Group Members List
307 312 table.rctable.group_members,
308 313 table#repo_list_table {
309 314 min-width: 600px;
310 315 }
311 316
312 317 // Keyboard mappings
313 318 table.keyboard-mappings {
314 319 th {
315 320 text-align: left;
316 321 font-family: @text-semibold;
317 322 }
318 323 }
319 324
320 325 // Branches, Tags, and Bookmarks
321 326 #obj_list_table.dataTable {
322 327 td.td-time {
323 328 padding-right: 1em;
324 329 }
325 330 }
326 331
327 332 // User Admin
328 333 .rctable.useremails,
329 334 .rctable.account_emails {
330 335 .tag,
331 336 .btn {
332 337 float: right;
333 338 }
334 339 .btn { //to line up with tags
335 340 margin-right: 1.65em;
336 341 }
337 342 }
338 343
339 344 // User List
340 345 #user_list_table {
341 346
342 347 td.td-user {
343 348 min-width: 100px;
344 349 }
345 350 }
346 351
347 352 // Pull Request List Table
348 353 #pull_request_list_table.dataTable {
349 354
350 355 //TODO: lisa: This needs to be removed once the description is adjusted
351 356 // for using an expand_commit button (see issue 765)
352 357 td {
353 358 vertical-align: middle;
354 359 }
355 360 }
356 361
357 362 // Settings (no border)
358 363 table.rctable.dl-settings {
359 364 td {
360 365 border: none;
361 366 }
362 367 }
363 368
364 369
365 370 // Statistics
366 371 table.trending_language_tbl {
367 372 width: 100%;
368 373 line-height: 1em;
369 374
370 375 td div {
371 376 overflow: visible;
372 377 }
373 378 }
374 379
375 380 .trending_language_tbl, .trending_language_tbl td {
376 381 border: 0;
377 382 margin: 0;
378 383 padding: 0;
379 384 background: transparent;
380 385 }
381 386
382 387 .trending_language_tbl, .trending_language_tbl tr {
383 388 border-spacing: 0 3px;
384 389 }
385 390
386 391 .trending_language {
387 392 position: relative;
388 393 width: 100%;
389 394 height: 19px;
390 395 overflow: hidden;
391 396 background-color: @grey6;
392 397
393 398 span, b{
394 399 position: absolute;
395 400 display: block;
396 401 height: 12px;
397 402 margin-bottom: 0px;
398 403 white-space: pre;
399 404 padding: floor(@basefontsize/4);
400 405 top: 0;
401 406 left: 0;
402 407 }
403 408
404 409 span{
405 410 color: @text-color;
406 411 z-index: 0;
407 412 min-width: 20px;
408 413 }
409 414
410 415 b {
411 416 z-index: 1;
412 417 overflow: hidden;
413 418 background-color: @rcblue;
414 419 color: #FFF;
415 420 text-decoration: none;
416 421 }
417 422
418 423 }
419 424
420 425 // Changesets
421 426 #changesets.rctable {
422 427
423 428 // td must be fixed height for graph
424 429 td {
425 430 height: 32px;
426 431 padding: 0 1em 0 0;
427 432 vertical-align: middle;
428 433 white-space: nowrap;
429 434
430 435 &.td-description {
431 436 white-space: normal;
432 437 }
433 438
434 439 &.expand_commit {
435 440 padding-right: 0;
436 441 }
437 442 }
438 443 }
439 444
440 445 // Compare
441 446 table.compare_view_commits {
442 447 margin-top: @space;
443 448
444 449 td.td-time {
445 450 padding-left: .5em;
446 451 }
447 452
448 453 // special case to not show hover actions on hidden indicator
449 454 tr.compare_select_hidden:hover {
450 455 cursor: inherit;
451 456
452 457 td {
453 458 background-color: inherit;
454 459 }
455 460 }
456 461
457 462 tr:hover {
458 463 cursor: pointer;
459 464
460 465 td {
461 466 background-color: lighten(@alert4,25%);
462 467 }
463 468 }
464 469
465 470
466 471 }
467 472
468 473 .file_history {
469 474 td.td-actions {
470 475 text-align: right;
471 476 }
472 477 }
473 478
474 479 .compare_view_files {
475 480
476 481 td.td-actions {
477 482 text-align: right;
478 483 }
479 484
480 485 .flag_status {
481 486 margin: 0 0 0 5px;
482 487 }
483 488
484 489 td.injected_diff {
485 490
486 491 .code-difftable {
487 492 border:none;
488 493 }
489 494
490 495 .diff-container {
491 496 border: @border-thickness solid @border-default-color;
492 497 .border-radius(@border-radius);
493 498 }
494 499
495 500 div.diffblock {
496 501 border:none;
497 502 }
498 503
499 504 div.code-body {
500 505 max-width: 1152px;
501 506 }
502 507 }
503 508
504 509 .rctable {
505 510
506 511 td {
507 512 padding-top: @space;
508 513 }
509 514
510 515 &:first-child td {
511 516 padding-top: 0;
512 517 }
513 518 }
514 519
515 520 .comment-bubble,
516 521 .show_comments {
517 522 float: right;
518 523 visibility: hidden;
519 524 padding: 0 1em 0 0;
520 525 }
521 526
522 527 .injected_diff {
523 528 padding-bottom: @padding;
524 529 }
525 530 }
526 531
527 532 // Gist List
528 533 #gist_list_table {
529 534 td {
530 535 vertical-align: middle;
531 536
532 537 div{
533 538 display: inline-block;
534 539 vertical-align: middle;
535 540 }
536 541
537 542 img{
538 543 vertical-align: middle;
539 544 }
540 545 }
541 546 }
@@ -1,322 +1,335 b''
1 1 ## snippet for displaying vcs settings
2 2 ## usage:
3 3 ## <%namespace name="vcss" file="/base/vcssettings.mako"/>
4 4 ## ${vcss.vcs_settings_fields()}
5 5
6 6 <%def name="vcs_settings_fields(suffix='', svn_branch_patterns=None, svn_tag_patterns=None, repo_type=None, display_globals=False, allow_repo_location_change=False, **kwargs)">
7 7 % if display_globals:
8 8 <div class="panel panel-default">
9 9 <div class="panel-heading" id="general">
10 10 <h3 class="panel-title">${_('General')}<a class="permalink" href="#general"> ΒΆ</a></h3>
11 11 </div>
12 12 <div class="panel-body">
13 13 <div class="field">
14 14 <div class="checkbox">
15 15 ${h.checkbox('web_push_ssl' + suffix, 'True')}
16 16 <label for="web_push_ssl${suffix}">${_('Require SSL for vcs operations')}</label>
17 17 </div>
18 18 <div class="label">
19 19 <span class="help-block">${_('Activate to set RhodeCode to require SSL for pushing or pulling. If SSL certificate is missing it will return a HTTP Error 406: Not Acceptable.')}</span>
20 20 </div>
21 21 </div>
22 22 </div>
23 23 </div>
24 24 % endif
25 25
26 26 % if display_globals:
27 27 <div class="panel panel-default">
28 28 <div class="panel-heading" id="vcs-storage-options">
29 29 <h3 class="panel-title">${_('Main Storage Location')}<a class="permalink" href="#vcs-storage-options"> ΒΆ</a></h3>
30 30 </div>
31 31 <div class="panel-body">
32 32 <div class="field">
33 33 <div class="inputx locked_input">
34 34 %if allow_repo_location_change:
35 35 ${h.text('paths_root_path',size=59,readonly="readonly", class_="disabled")}
36 36 <span id="path_unlock" class="tooltip"
37 37 title="${h.tooltip(_('Click to unlock. You must restart RhodeCode in order to make this setting take effect.'))}">
38 38 <div class="btn btn-default lock_input_button"><i id="path_unlock_icon" class="icon-lock"></i></div>
39 39 </span>
40 40 %else:
41 41 ${_('Repository location change is disabled. You can enable this by changing the `allow_repo_location_change` inside .ini file.')}
42 42 ## form still requires this but we cannot internally change it anyway
43 43 ${h.hidden('paths_root_path',size=30,readonly="readonly", class_="disabled")}
44 44 %endif
45 45 </div>
46 46 </div>
47 47 <div class="label">
48 48 <span class="help-block">${_('Filesystem location where repositories should be stored. After changing this value a restart and rescan of the repository folder are required.')}</span>
49 49 </div>
50 50 </div>
51 51 </div>
52 52 % endif
53 53
54 54 % if display_globals or repo_type in ['git', 'hg']:
55 55 <div class="panel panel-default">
56 56 <div class="panel-heading" id="vcs-hooks-options">
57 57 <h3 class="panel-title">${_('Internal Hooks')}<a class="permalink" href="#vcs-hooks-options"> ΒΆ</a></h3>
58 58 </div>
59 59 <div class="panel-body">
60 60 <div class="field">
61 61 <div class="checkbox">
62 62 ${h.checkbox('hooks_changegroup_repo_size' + suffix, 'True', **kwargs)}
63 63 <label for="hooks_changegroup_repo_size${suffix}">${_('Show repository size after push')}</label>
64 64 </div>
65 65
66 66 <div class="label">
67 67 <span class="help-block">${_('Trigger a hook that calculates repository size after each push.')}</span>
68 68 </div>
69 69 <div class="checkbox">
70 70 ${h.checkbox('hooks_changegroup_push_logger' + suffix, 'True', **kwargs)}
71 71 <label for="hooks_changegroup_push_logger${suffix}">${_('Execute pre/post push hooks')}</label>
72 72 </div>
73 73 <div class="label">
74 74 <span class="help-block">${_('Execute Built in pre/post push hooks. This also executes rcextensions hooks.')}</span>
75 75 </div>
76 76 <div class="checkbox">
77 77 ${h.checkbox('hooks_outgoing_pull_logger' + suffix, 'True', **kwargs)}
78 78 <label for="hooks_outgoing_pull_logger${suffix}">${_('Execute pre/post pull hooks')}</label>
79 79 </div>
80 80 <div class="label">
81 81 <span class="help-block">${_('Execute Built in pre/post pull hooks. This also executes rcextensions hooks.')}</span>
82 82 </div>
83 83 </div>
84 84 </div>
85 85 </div>
86 86 % endif
87 87
88 88 % if display_globals or repo_type in ['hg']:
89 89 <div class="panel panel-default">
90 90 <div class="panel-heading" id="vcs-hg-options">
91 91 <h3 class="panel-title">${_('Mercurial Settings')}<a class="permalink" href="#vcs-hg-options"> ΒΆ</a></h3>
92 92 </div>
93 93 <div class="panel-body">
94 94 <div class="checkbox">
95 95 ${h.checkbox('extensions_largefiles' + suffix, 'True', **kwargs)}
96 96 <label for="extensions_largefiles${suffix}">${_('Enable largefiles extension')}</label>
97 97 </div>
98 98 <div class="label">
99 99 % if display_globals:
100 100 <span class="help-block">${_('Enable Largefiles extensions for all repositories.')}</span>
101 101 % else:
102 102 <span class="help-block">${_('Enable Largefiles extensions for this repository.')}</span>
103 103 % endif
104 104 </div>
105 105
106 106 % if display_globals:
107 107 <div class="field">
108 108 <div class="input">
109 109 ${h.text('largefiles_usercache' + suffix, size=59)}
110 110 </div>
111 111 </div>
112 112 <div class="label">
113 113 <span class="help-block">${_('Filesystem location where Mercurial largefile objects should be stored.')}</span>
114 114 </div>
115 115 % endif
116 116
117 117 <div class="checkbox">
118 118 ${h.checkbox('phases_publish' + suffix, 'True', **kwargs)}
119 119 <label for="phases_publish${suffix}">${_('Set repositories as publishing') if display_globals else _('Set repository as publishing')}</label>
120 120 </div>
121 121 <div class="label">
122 122 <span class="help-block">${_('When this is enabled all commits in the repository are seen as public commits by clients.')}</span>
123 123 </div>
124 124 % if display_globals:
125 125 <div class="checkbox">
126 126 ${h.checkbox('extensions_hgsubversion' + suffix,'True')}
127 127 <label for="extensions_hgsubversion${suffix}">${_('Enable hgsubversion extension')}</label>
128 128 </div>
129 129 <div class="label">
130 130 <span class="help-block">${_('Requires hgsubversion library to be installed. Allows cloning remote SVN repositories and migrates them to Mercurial type.')}</span>
131 131 </div>
132 132 % endif
133
134 <div class="checkbox">
135 ${h.checkbox('extensions_evolve' + suffix, 'True', **kwargs)}
136 <label for="extensions_evolve${suffix}">${_('Enable evolve extension')}</label>
137 </div>
138 <div class="label">
139 % if display_globals:
140 <span class="help-block">${_('Enable evolve extension for all repositories.')}</span>
141 % else:
142 <span class="help-block">${_('Enable evolve extension for this repository.')}</span>
143 % endif
144 </div>
145
133 146 </div>
134 147 </div>
135 148 ## LABS for HG
136 149 % if c.labs_active:
137 150 <div class="panel panel-danger">
138 151 <div class="panel-heading">
139 152 <h3 class="panel-title">${_('Mercurial Labs Settings')} (${_('These features are considered experimental and may not work as expected.')})</h3>
140 153 </div>
141 154 <div class="panel-body">
142 155
143 156 <div class="checkbox">
144 157 ${h.checkbox('rhodecode_hg_use_rebase_for_merging' + suffix, 'True', **kwargs)}
145 158 <label for="rhodecode_hg_use_rebase_for_merging${suffix}">${_('Use rebase as merge strategy')}</label>
146 159 </div>
147 160 <div class="label">
148 161 <span class="help-block">${_('Use rebase instead of creating a merge commit when merging via web interface.')}</span>
149 162 </div>
150 163
151 164 </div>
152 165 </div>
153 166 % endif
154 167
155 168 % endif
156 169
157 170 % if display_globals or repo_type in ['git']:
158 171 <div class="panel panel-default">
159 172 <div class="panel-heading" id="vcs-git-options">
160 173 <h3 class="panel-title">${_('Git Settings')}<a class="permalink" href="#vcs-git-options"> ΒΆ</a></h3>
161 174 </div>
162 175 <div class="panel-body">
163 176 <div class="checkbox">
164 177 ${h.checkbox('vcs_git_lfs_enabled' + suffix, 'True', **kwargs)}
165 178 <label for="vcs_git_lfs_enabled${suffix}">${_('Enable lfs extension')}</label>
166 179 </div>
167 180 <div class="label">
168 181 % if display_globals:
169 182 <span class="help-block">${_('Enable lfs extensions for all repositories.')}</span>
170 183 % else:
171 184 <span class="help-block">${_('Enable lfs extensions for this repository.')}</span>
172 185 % endif
173 186 </div>
174 187
175 188 % if display_globals:
176 189 <div class="field">
177 190 <div class="input">
178 191 ${h.text('vcs_git_lfs_store_location' + suffix, size=59)}
179 192 </div>
180 193 </div>
181 194 <div class="label">
182 195 <span class="help-block">${_('Filesystem location where Git lfs objects should be stored.')}</span>
183 196 </div>
184 197 % endif
185 198 </div>
186 199 </div>
187 200 % endif
188 201
189 202
190 203 % if display_globals:
191 204 <div class="panel panel-default">
192 205 <div class="panel-heading" id="vcs-global-svn-options">
193 206 <h3 class="panel-title">${_('Global Subversion Settings')}<a class="permalink" href="#vcs-global-svn-options"> ΒΆ</a></h3>
194 207 </div>
195 208 <div class="panel-body">
196 209 <div class="field">
197 210 <div class="checkbox">
198 211 ${h.checkbox('vcs_svn_proxy_http_requests_enabled' + suffix, 'True', **kwargs)}
199 212 <label for="vcs_svn_proxy_http_requests_enabled${suffix}">${_('Proxy subversion HTTP requests')}</label>
200 213 </div>
201 214 <div class="label">
202 215 <span class="help-block">
203 216 ${_('Subversion HTTP Support. Enables communication with SVN over HTTP protocol.')}
204 217 <a href="${h.route_url('enterprise_svn_setup')}" target="_blank">${_('SVN Protocol setup Documentation')}</a>.
205 218 </span>
206 219 </div>
207 220 </div>
208 221 <div class="field">
209 222 <div class="label">
210 223 <label for="vcs_svn_proxy_http_server_url">${_('Subversion HTTP Server URL')}</label><br/>
211 224 </div>
212 225 <div class="input">
213 226 ${h.text('vcs_svn_proxy_http_server_url',size=59)}
214 227 % if c.svn_proxy_generate_config:
215 228 <span class="buttons">
216 229 <button class="btn btn-primary" id="vcs_svn_generate_cfg">${_('Generate Apache Config')}</button>
217 230 </span>
218 231 % endif
219 232 </div>
220 233 </div>
221 234 </div>
222 235 </div>
223 236 % endif
224 237
225 238 % if display_globals or repo_type in ['svn']:
226 239 <div class="panel panel-default">
227 240 <div class="panel-heading" id="vcs-svn-options">
228 241 <h3 class="panel-title">${_('Subversion Settings')}<a class="permalink" href="#vcs-svn-options"> ΒΆ</a></h3>
229 242 </div>
230 243 <div class="panel-body">
231 244 <div class="field">
232 245 <div class="content" >
233 246 <label>${_('Repository patterns')}</label><br/>
234 247 </div>
235 248 </div>
236 249 <div class="label">
237 250 <span class="help-block">${_('Patterns for identifying SVN branches and tags. For recursive search, use "*". Eg.: "/branches/*"')}</span>
238 251 </div>
239 252
240 253 <div class="field branch_patterns">
241 254 <div class="input" >
242 255 <label>${_('Branches')}:</label><br/>
243 256 </div>
244 257 % if svn_branch_patterns:
245 258 % for branch in svn_branch_patterns:
246 259 <div class="input adjacent" id="${'id%s' % branch.ui_id}">
247 260 ${h.hidden('branch_ui_key' + suffix, branch.ui_key)}
248 261 ${h.text('branch_value_%d' % branch.ui_id + suffix, branch.ui_value, size=59, readonly="readonly", class_='disabled')}
249 262 % if kwargs.get('disabled') != 'disabled':
250 263 <span class="btn btn-x" onclick="ajaxDeletePattern(${branch.ui_id},'${'id%s' % branch.ui_id}')">
251 264 ${_('Delete')}
252 265 </span>
253 266 % endif
254 267 </div>
255 268 % endfor
256 269 %endif
257 270 </div>
258 271 % if kwargs.get('disabled') != 'disabled':
259 272 <div class="field branch_patterns">
260 273 <div class="input" >
261 274 ${h.text('new_svn_branch',size=59,placeholder='New branch pattern')}
262 275 </div>
263 276 </div>
264 277 % endif
265 278 <div class="field tag_patterns">
266 279 <div class="input" >
267 280 <label>${_('Tags')}:</label><br/>
268 281 </div>
269 282 % if svn_tag_patterns:
270 283 % for tag in svn_tag_patterns:
271 284 <div class="input" id="${'id%s' % tag.ui_id + suffix}">
272 285 ${h.hidden('tag_ui_key' + suffix, tag.ui_key)}
273 286 ${h.text('tag_ui_value_new_%d' % tag.ui_id + suffix, tag.ui_value, size=59, readonly="readonly", class_='disabled tag_input')}
274 287 % if kwargs.get('disabled') != 'disabled':
275 288 <span class="btn btn-x" onclick="ajaxDeletePattern(${tag.ui_id},'${'id%s' % tag.ui_id}')">
276 289 ${_('Delete')}
277 290 </span>
278 291 %endif
279 292 </div>
280 293 % endfor
281 294 % endif
282 295 </div>
283 296 % if kwargs.get('disabled') != 'disabled':
284 297 <div class="field tag_patterns">
285 298 <div class="input" >
286 299 ${h.text('new_svn_tag' + suffix, size=59, placeholder='New tag pattern')}
287 300 </div>
288 301 </div>
289 302 %endif
290 303 </div>
291 304 </div>
292 305 % else:
293 306 ${h.hidden('new_svn_branch' + suffix, '')}
294 307 ${h.hidden('new_svn_tag' + suffix, '')}
295 308 % endif
296 309
297 310
298 311 % if display_globals or repo_type in ['hg', 'git']:
299 312 <div class="panel panel-default">
300 313 <div class="panel-heading" id="vcs-pull-requests-options">
301 314 <h3 class="panel-title">${_('Pull Request Settings')}<a class="permalink" href="#vcs-pull-requests-options"> ΒΆ</a></h3>
302 315 </div>
303 316 <div class="panel-body">
304 317 <div class="checkbox">
305 318 ${h.checkbox('rhodecode_pr_merge_enabled' + suffix, 'True', **kwargs)}
306 319 <label for="rhodecode_pr_merge_enabled${suffix}">${_('Enable server-side merge for pull requests')}</label>
307 320 </div>
308 321 <div class="label">
309 322 <span class="help-block">${_('Note: when this feature is enabled, it only runs hooks defined in the rcextension package. Custom hooks added on the Admin -> Settings -> Hooks page will not be run when pull requests are automatically merged from the web interface.')}</span>
310 323 </div>
311 324 <div class="checkbox">
312 325 ${h.checkbox('rhodecode_use_outdated_comments' + suffix, 'True', **kwargs)}
313 326 <label for="rhodecode_use_outdated_comments${suffix}">${_('Invalidate and relocate inline comments during update')}</label>
314 327 </div>
315 328 <div class="label">
316 329 <span class="help-block">${_('During the update of a pull request, the position of inline comments will be updated and outdated inline comments will be hidden.')}</span>
317 330 </div>
318 331 </div>
319 332 </div>
320 333 % endif
321 334
322 335 </%def>
@@ -1,127 +1,142 b''
1 1 ## small box that displays changed/added/removed details fetched by AJAX
2 2 <%namespace name="base" file="/base/base.mako"/>
3 3
4 4
5 5 % if c.prev_page:
6 6 <tr>
7 7 <td colspan="9" class="load-more-commits">
8 8 <a class="prev-commits" href="#loadPrevCommits" onclick="commitsController.loadPrev(this, ${c.prev_page}, '${c.branch_name}');return false">
9 9 ${_('load previous')}
10 10 </a>
11 11 </td>
12 12 </tr>
13 13 % endif
14 14
15 15 % for cnt,commit in enumerate(c.pagination):
16 16 <tr id="sha_${commit.raw_id}" class="changelogRow container ${'tablerow%s' % (cnt%2)}">
17 17
18 18 <td class="td-checkbox">
19 19 ${h.checkbox(commit.raw_id,class_="commit-range")}
20 20 </td>
21 21 <td class="td-status">
22 22
23 23 %if c.statuses.get(commit.raw_id):
24 24 <div class="changeset-status-ico">
25 25 %if c.statuses.get(commit.raw_id)[2]:
26 26 <a class="tooltip" title="${_('Commit status: %s\nClick to open associated pull request #%s') % (h.commit_status_lbl(c.statuses.get(commit.raw_id)[0]), c.statuses.get(commit.raw_id)[2])}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(commit.raw_id)[3],pull_request_id=c.statuses.get(commit.raw_id)[2])}">
27 27 <div class="${'flag_status %s' % c.statuses.get(commit.raw_id)[0]}"></div>
28 28 </a>
29 29 %else:
30 30 <a class="tooltip" title="${_('Commit status: %s') % h.commit_status_lbl(c.statuses.get(commit.raw_id)[0])}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id,anchor='comment-%s' % c.comments[commit.raw_id][0].comment_id)}">
31 31 <div class="${'flag_status %s' % c.statuses.get(commit.raw_id)[0]}"></div>
32 32 </a>
33 33 %endif
34 34 </div>
35 35 %else:
36 36 <div class="tooltip flag_status not_reviewed" title="${_('Commit status: Not Reviewed')}"></div>
37 37 %endif
38 38 </td>
39 39 <td class="td-comments comments-col">
40 40 %if c.comments.get(commit.raw_id):
41 41 <a title="${_('Commit has comments')}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id,anchor='comment-%s' % c.comments[commit.raw_id][0].comment_id)}">
42 42 <i class="icon-comment"></i> ${len(c.comments[commit.raw_id])}
43 43 </a>
44 44 %endif
45 45 </td>
46 46 <td class="td-hash">
47 47 <code>
48 48 <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id)}">
49 <span class="commit_hash">${h.show_id(commit)}</span>
49 <span class="${'commit_hash obsolete' if getattr(commit, 'obsolete', None) else 'commit_hash'}">${h.show_id(commit)}</span>
50 50 </a>
51 51 % if hasattr(commit, 'phase'):
52 52 % if commit.phase != 'public':
53 <span class="tag phase-${commit.phase} tooltip" title="${_('commit phase')}">${commit.phase}</span>
53 <span class="tag phase-${commit.phase} tooltip" title="${_('Commit phase')}">${commit.phase}</span>
54 54 % endif
55 55 % endif
56
57 ## obsolete commits
58 % if hasattr(commit, 'obsolete'):
59 % if commit.obsolete:
60 <span class="tag obsolete-${commit.obsolete} tooltip" title="${_('Evolve State')}">${_('obsolete')}</span>
61 % endif
62 % endif
63
64 ## hidden commits
65 % if hasattr(commit, 'hidden'):
66 % if commit.hidden:
67 <span class="tag obsolete-${commit.hidden} tooltip" title="${_('Evolve State')}">${_('hidden')}</span>
68 % endif
69 % endif
70
56 71 </code>
57 72 </td>
58 73 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_('Expand commit message')}" onclick="commitsController.expandCommit(this); return false">
59 74 <div class="show_more_col">
60 75 <i class="show_more"></i>&nbsp;
61 76 </div>
62 77 </td>
63 78 <td class="td-description mid">
64 79 <div class="log-container truncate-wrap">
65 80 <div class="message truncate" id="c-${commit.raw_id}">${h.urlify_commit_message(commit.message, c.repo_name)}</div>
66 81 </div>
67 82 </td>
68 83
69 84 <td class="td-time">
70 85 ${h.age_component(commit.date)}
71 86 </td>
72 87 <td class="td-user">
73 88 ${base.gravatar_with_user(commit.author)}
74 89 </td>
75 90
76 91 <td class="td-tags tags-col">
77 92 <div id="t-${commit.raw_id}">
78 93
79 94 ## merge
80 95 %if commit.merge:
81 96 <span class="tag mergetag">
82 97 <i class="icon-merge"></i>${_('merge')}
83 98 </span>
84 99 %endif
85 100
86 101 ## branch
87 102 %if commit.branch:
88 103 <span class="tag branchtag" title="${_('Branch %s') % commit.branch}">
89 104 <a href="${h.url('changelog_home',repo_name=c.repo_name,branch=commit.branch)}"><i class="icon-code-fork"></i>${h.shorter(commit.branch)}</a>
90 105 </span>
91 106 %endif
92 107
93 108 ## bookmarks
94 109 %if h.is_hg(c.rhodecode_repo):
95 110 %for book in commit.bookmarks:
96 111 <span class="tag booktag" title="${_('Bookmark %s') % book}">
97 112 <a href="${h.url('files_home',repo_name=c.repo_name,revision=commit.raw_id)}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
98 113 </span>
99 114 %endfor
100 115 %endif
101 116
102 117 ## tags
103 118 %for tag in commit.tags:
104 119 <span class="tag tagtag" title="${_('Tag %s') % tag}">
105 120 <a href="${h.url('files_home',repo_name=c.repo_name,revision=commit.raw_id)}"><i class="icon-tag"></i>${h.shorter(tag)}</a>
106 121 </span>
107 122 %endfor
108 123
109 124 </div>
110 125 </td>
111 126 </tr>
112 127 % endfor
113 128
114 129 % if c.next_page:
115 130 <tr>
116 131 <td colspan="9" class="load-more-commits">
117 132 <a class="next-commits" href="#loadNextCommits" onclick="commitsController.loadNext(this, ${c.next_page}, '${c.branch_name}');return false">
118 133 ${_('load next')}
119 134 </a>
120 135 </td>
121 136 </tr>
122 137 % endif
123 138 <tr class="chunk-graph-data" style="display:none"
124 139 data-graph='${c.graph_data|n}'
125 140 data-node='${c.prev_page}:${c.next_page}'
126 141 data-commits='${c.graph_commits|n}'>
127 142 </tr> No newline at end of file
@@ -1,336 +1,351 b''
1 1 ## -*- coding: utf-8 -*-
2 2
3 3 <%inherit file="/base/base.mako"/>
4 4 <%namespace name="diff_block" file="/changeset/diff_block.mako"/>
5 5
6 6 <%def name="title()">
7 7 ${_('%s Commit') % c.repo_name} - ${h.show_id(c.commit)}
8 8 %if c.rhodecode_name:
9 9 &middot; ${h.branding(c.rhodecode_name)}
10 10 %endif
11 11 </%def>
12 12
13 13 <%def name="menu_bar_nav()">
14 14 ${self.menu_items(active='repositories')}
15 15 </%def>
16 16
17 17 <%def name="menu_bar_subnav()">
18 18 ${self.repo_menu(active='changelog')}
19 19 </%def>
20 20
21 21 <%def name="main()">
22 22 <script>
23 23 // TODO: marcink switch this to pyroutes
24 24 AJAX_COMMENT_DELETE_URL = "${url('changeset_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
25 25 templateContext.commit_data.commit_id = "${c.commit.raw_id}";
26 26 </script>
27 27 <div class="box">
28 28 <div class="title">
29 29 ${self.repo_page_title(c.rhodecode_db_repo)}
30 30 </div>
31 31
32 32 <div id="changeset_compare_view_content" class="summary changeset">
33 33 <div class="summary-detail">
34 34 <div class="summary-detail-header">
35 35 <span class="breadcrumbs files_location">
36 36 <h4>${_('Commit')}
37 37 <code>
38 38 ${h.show_id(c.commit)}
39 39 % if hasattr(c.commit, 'phase'):
40 <span class="tag phase-${c.commit.phase} tooltip" title="${_('commit phase')}">${c.commit.phase}</span>
40 <span class="tag phase-${c.commit.phase} tooltip" title="${_('Commit phase')}">${c.commit.phase}</span>
41 % endif
42
43 ## obsolete commits
44 % if hasattr(c.commit, 'obsolete'):
45 % if c.commit.obsolete:
46 <span class="tag obsolete-${c.commit.obsolete} tooltip" title="${_('Evolve State')}">${_('obsolete')}</span>
47 % endif
41 48 % endif
49
50 ## hidden commits
51 % if hasattr(c.commit, 'hidden'):
52 % if c.commit.hidden:
53 <span class="tag hidden-${c.commit.hidden} tooltip" title="${_('Evolve State')}">${_('hidden')}</span>
54 % endif
55 % endif
56
42 57 </code>
43 58 </h4>
44 59 </span>
45 60 <div class="pull-right">
46 61 <span id="parent_link">
47 62 <a href="#" title="${_('Parent Commit')}">${_('Parent')}</a>
48 63 </span>
49 64 |
50 65 <span id="child_link">
51 66 <a href="#" title="${_('Child Commit')}">${_('Child')}</a>
52 67 </span>
53 68 </div>
54 69 </div>
55 70
56 71 <div class="fieldset">
57 72 <div class="left-label">
58 73 ${_('Description')}:
59 74 </div>
60 75 <div class="right-content">
61 76 <div id="trimmed_message_box" class="commit">${h.urlify_commit_message(c.commit.message,c.repo_name)}</div>
62 77 <div id="message_expand" style="display:none;">
63 78 ${_('Expand')}
64 79 </div>
65 80 </div>
66 81 </div>
67 82
68 83 %if c.statuses:
69 84 <div class="fieldset">
70 85 <div class="left-label">
71 86 ${_('Commit status')}:
72 87 </div>
73 88 <div class="right-content">
74 89 <div class="changeset-status-ico">
75 90 <div class="${'flag_status %s' % c.statuses[0]} pull-left"></div>
76 91 </div>
77 92 <div title="${_('Commit status')}" class="changeset-status-lbl">[${h.commit_status_lbl(c.statuses[0])}]</div>
78 93 </div>
79 94 </div>
80 95 %endif
81 96
82 97 <div class="fieldset">
83 98 <div class="left-label">
84 99 ${_('References')}:
85 100 </div>
86 101 <div class="right-content">
87 102 <div class="tags">
88 103
89 104 %if c.commit.merge:
90 105 <span class="mergetag tag">
91 106 <i class="icon-merge"></i>${_('merge')}
92 107 </span>
93 108 %endif
94 109
95 110 %if h.is_hg(c.rhodecode_repo):
96 111 %for book in c.commit.bookmarks:
97 112 <span class="booktag tag" title="${_('Bookmark %s') % book}">
98 113 <a href="${h.url('files_home',repo_name=c.repo_name,revision=c.commit.raw_id)}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
99 114 </span>
100 115 %endfor
101 116 %endif
102 117
103 118 %for tag in c.commit.tags:
104 119 <span class="tagtag tag" title="${_('Tag %s') % tag}">
105 120 <a href="${h.url('files_home',repo_name=c.repo_name,revision=c.commit.raw_id)}"><i class="icon-tag"></i>${tag}</a>
106 121 </span>
107 122 %endfor
108 123
109 124 %if c.commit.branch:
110 125 <span class="branchtag tag" title="${_('Branch %s') % c.commit.branch}">
111 126 <a href="${h.url('files_home',repo_name=c.repo_name,revision=c.commit.raw_id)}"><i class="icon-code-fork"></i>${h.shorter(c.commit.branch)}</a>
112 127 </span>
113 128 %endif
114 129 </div>
115 130 </div>
116 131 </div>
117 132
118 133 <div class="fieldset">
119 134 <div class="left-label">
120 135 ${_('Diff options')}:
121 136 </div>
122 137 <div class="right-content">
123 138 <div class="diff-actions">
124 139 <a href="${h.url('changeset_raw_home',repo_name=c.repo_name,revision=c.commit.raw_id)}" class="tooltip" title="${h.tooltip(_('Raw diff'))}">
125 140 ${_('Raw Diff')}
126 141 </a>
127 142 |
128 143 <a href="${h.url('changeset_patch_home',repo_name=c.repo_name,revision=c.commit.raw_id)}" class="tooltip" title="${h.tooltip(_('Patch diff'))}">
129 144 ${_('Patch Diff')}
130 145 </a>
131 146 |
132 147 <a href="${h.url('changeset_download_home',repo_name=c.repo_name,revision=c.commit.raw_id,diff='download')}" class="tooltip" title="${h.tooltip(_('Download diff'))}">
133 148 ${_('Download Diff')}
134 149 </a>
135 150 |
136 151 ${c.ignorews_url(request.GET)}
137 152 |
138 153 ${c.context_url(request.GET)}
139 154 </div>
140 155 </div>
141 156 </div>
142 157
143 158 <div class="fieldset">
144 159 <div class="left-label">
145 160 ${_('Comments')}:
146 161 </div>
147 162 <div class="right-content">
148 163 <div class="comments-number">
149 164 %if c.comments:
150 165 <a href="#comments">${ungettext("%d Commit comment", "%d Commit comments", len(c.comments)) % len(c.comments)}</a>,
151 166 %else:
152 167 ${ungettext("%d Commit comment", "%d Commit comments", len(c.comments)) % len(c.comments)}
153 168 %endif
154 169 %if c.inline_cnt:
155 170 <a href="#" onclick="return Rhodecode.comments.nextComment();" id="inline-comments-counter">${ungettext("%d Inline Comment", "%d Inline Comments", c.inline_cnt) % c.inline_cnt}</a>
156 171 %else:
157 172 ${ungettext("%d Inline Comment", "%d Inline Comments", c.inline_cnt) % c.inline_cnt}
158 173 %endif
159 174 </div>
160 175 </div>
161 176 </div>
162 177
163 178 <div class="fieldset">
164 179 <div class="left-label">
165 180 ${_('Unresolved TODOs')}:
166 181 </div>
167 182 <div class="right-content">
168 183 <div class="comments-number">
169 184 % if c.unresolved_comments:
170 185 % for co in c.unresolved_comments:
171 186 <a class="permalink" href="#comment-${co.comment_id}" onclick="Rhodecode.comments.scrollToComment($('#comment-${co.comment_id}'))"> #${co.comment_id}</a>${'' if loop.last else ','}
172 187 % endfor
173 188 % else:
174 189 ${_('There are no unresolved TODOs')}
175 190 % endif
176 191 </div>
177 192 </div>
178 193 </div>
179 194
180 195 </div> <!-- end summary-detail -->
181 196
182 197 <div id="commit-stats" class="sidebar-right">
183 198 <div class="summary-detail-header">
184 199 <h4 class="item">
185 200 ${_('Author')}
186 201 </h4>
187 202 </div>
188 203 <div class="sidebar-right-content">
189 204 ${self.gravatar_with_user(c.commit.author)}
190 205 <div class="user-inline-data">- ${h.age_component(c.commit.date)}</div>
191 206 </div>
192 207 </div><!-- end sidebar -->
193 208 </div> <!-- end summary -->
194 209 <div class="cs_files">
195 210 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
196 211 ${cbdiffs.render_diffset_menu()}
197 212 ${cbdiffs.render_diffset(
198 213 c.changes[c.commit.raw_id], commit=c.commit, use_comments=True)}
199 214 </div>
200 215
201 216 ## template for inline comment form
202 217 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
203 218
204 219 ## render comments
205 220 ${comment.generate_comments(c.comments)}
206 221
207 222 ## main comment form and it status
208 223 ${comment.comments(h.url('changeset_comment', repo_name=c.repo_name, revision=c.commit.raw_id),
209 224 h.commit_status(c.rhodecode_db_repo, c.commit.raw_id))}
210 225 </div>
211 226
212 227 ## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS
213 228 <script type="text/javascript">
214 229
215 230 $(document).ready(function() {
216 231
217 232 var boxmax = parseInt($('#trimmed_message_box').css('max-height'), 10);
218 233 if($('#trimmed_message_box').height() === boxmax){
219 234 $('#message_expand').show();
220 235 }
221 236
222 237 $('#message_expand').on('click', function(e){
223 238 $('#trimmed_message_box').css('max-height', 'none');
224 239 $(this).hide();
225 240 });
226 241
227 242 $('.show-inline-comments').on('click', function(e){
228 243 var boxid = $(this).attr('data-comment-id');
229 244 var button = $(this);
230 245
231 246 if(button.hasClass("comments-visible")) {
232 247 $('#{0} .inline-comments'.format(boxid)).each(function(index){
233 248 $(this).hide();
234 249 });
235 250 button.removeClass("comments-visible");
236 251 } else {
237 252 $('#{0} .inline-comments'.format(boxid)).each(function(index){
238 253 $(this).show();
239 254 });
240 255 button.addClass("comments-visible");
241 256 }
242 257 });
243 258
244 259
245 260 // next links
246 261 $('#child_link').on('click', function(e){
247 262 // fetch via ajax what is going to be the next link, if we have
248 263 // >1 links show them to user to choose
249 264 if(!$('#child_link').hasClass('disabled')){
250 265 $.ajax({
251 266 url: '${h.url('changeset_children',repo_name=c.repo_name, revision=c.commit.raw_id)}',
252 267 success: function(data) {
253 268 if(data.results.length === 0){
254 269 $('#child_link').html("${_('No Child Commits')}").addClass('disabled');
255 270 }
256 271 if(data.results.length === 1){
257 272 var commit = data.results[0];
258 273 window.location = pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': commit.raw_id});
259 274 }
260 275 else if(data.results.length === 2){
261 276 $('#child_link').addClass('disabled');
262 277 $('#child_link').addClass('double');
263 278 var _html = '';
264 279 _html +='<a title="__title__" href="__url__">__rev__</a> '
265 280 .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6)))
266 281 .replace('__title__', data.results[0].message)
267 282 .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[0].raw_id}));
268 283 _html +=' | ';
269 284 _html +='<a title="__title__" href="__url__">__rev__</a> '
270 285 .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
271 286 .replace('__title__', data.results[1].message)
272 287 .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[1].raw_id}));
273 288 $('#child_link').html(_html);
274 289 }
275 290 }
276 291 });
277 292 e.preventDefault();
278 293 }
279 294 });
280 295
281 296 // prev links
282 297 $('#parent_link').on('click', function(e){
283 298 // fetch via ajax what is going to be the next link, if we have
284 299 // >1 links show them to user to choose
285 300 if(!$('#parent_link').hasClass('disabled')){
286 301 $.ajax({
287 302 url: '${h.url("changeset_parents",repo_name=c.repo_name, revision=c.commit.raw_id)}',
288 303 success: function(data) {
289 304 if(data.results.length === 0){
290 305 $('#parent_link').html('${_('No Parent Commits')}').addClass('disabled');
291 306 }
292 307 if(data.results.length === 1){
293 308 var commit = data.results[0];
294 309 window.location = pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': commit.raw_id});
295 310 }
296 311 else if(data.results.length === 2){
297 312 $('#parent_link').addClass('disabled');
298 313 $('#parent_link').addClass('double');
299 314 var _html = '';
300 315 _html +='<a title="__title__" href="__url__">Parent __rev__</a>'
301 316 .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6)))
302 317 .replace('__title__', data.results[0].message)
303 318 .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[0].raw_id}));
304 319 _html +=' | ';
305 320 _html +='<a title="__title__" href="__url__">Parent __rev__</a>'
306 321 .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
307 322 .replace('__title__', data.results[1].message)
308 323 .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[1].raw_id}));
309 324 $('#parent_link').html(_html);
310 325 }
311 326 }
312 327 });
313 328 e.preventDefault();
314 329 }
315 330 });
316 331
317 332 if (location.hash) {
318 333 var result = splitDelimitedHash(location.hash);
319 334 var line = $('html').find(result.loc);
320 335 if (line.length > 0){
321 336 offsetScroll(line, 70);
322 337 }
323 338 }
324 339
325 340 // browse tree @ revision
326 341 $('#files_link').on('click', function(e){
327 342 window.location = '${h.url('files_home',repo_name=c.repo_name, revision=c.commit.raw_id, f_path='')}';
328 343 e.preventDefault();
329 344 });
330 345
331 346 // inject comments into their proper positions
332 347 var file_comments = $('.inline-comment-placeholder');
333 348 })
334 349 </script>
335 350
336 351 </%def>
@@ -1,1207 +1,1208 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 import urllib
22 22
23 23 import mock
24 24 import pytest
25 25
26 26 from rhodecode.lib import auth
27 27 from rhodecode.lib.utils2 import safe_str, str2bool, safe_unicode
28 28 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
29 29 from rhodecode.model.db import Repository, RepoGroup, UserRepoToPerm, User,\
30 30 Permission
31 31 from rhodecode.model.meta import Session
32 32 from rhodecode.model.repo import RepoModel
33 33 from rhodecode.model.repo_group import RepoGroupModel
34 34 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
35 35 from rhodecode.model.user import UserModel
36 36 from rhodecode.tests import (
37 37 login_user_session, url, assert_session_flash, TEST_USER_ADMIN_LOGIN,
38 38 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, HG_REPO, GIT_REPO,
39 39 logout_user_session)
40 40 from rhodecode.tests.fixture import Fixture, error_function
41 41 from rhodecode.tests.utils import AssertResponse, repo_on_filesystem
42 42
43 43 fixture = Fixture()
44 44
45 45
46 46 @pytest.mark.usefixtures("app")
47 47 class TestAdminRepos(object):
48 48
49 49 def test_index(self):
50 50 self.app.get(url('repos'))
51 51
52 52 def test_create_page_restricted(self, autologin_user, backend):
53 53 with mock.patch('rhodecode.BACKENDS', {'git': 'git'}):
54 54 response = self.app.get(url('new_repo'), status=200)
55 55 assert_response = AssertResponse(response)
56 56 element = assert_response.get_element('#repo_type')
57 57 assert element.text_content() == '\ngit\n'
58 58
59 59 def test_create_page_non_restricted(self, autologin_user, backend):
60 60 response = self.app.get(url('new_repo'), status=200)
61 61 assert_response = AssertResponse(response)
62 62 assert_response.element_contains('#repo_type', 'git')
63 63 assert_response.element_contains('#repo_type', 'svn')
64 64 assert_response.element_contains('#repo_type', 'hg')
65 65
66 66 @pytest.mark.parametrize("suffix",
67 67 [u'', u'xxa'], ids=['', 'non-ascii'])
68 68 def test_create(self, autologin_user, backend, suffix, csrf_token):
69 69 repo_name_unicode = backend.new_repo_name(suffix=suffix)
70 70 repo_name = repo_name_unicode.encode('utf8')
71 71 description_unicode = u'description for newly created repo' + suffix
72 72 description = description_unicode.encode('utf8')
73 73 response = self.app.post(
74 74 url('repos'),
75 75 fixture._get_repo_create_params(
76 76 repo_private=False,
77 77 repo_name=repo_name,
78 78 repo_type=backend.alias,
79 79 repo_description=description,
80 80 csrf_token=csrf_token),
81 81 status=302)
82 82
83 83 self.assert_repository_is_created_correctly(
84 84 repo_name, description, backend)
85 85
86 86 def test_create_numeric(self, autologin_user, backend, csrf_token):
87 87 numeric_repo = '1234'
88 88 repo_name = numeric_repo
89 89 description = 'description for newly created repo' + numeric_repo
90 90 self.app.post(
91 91 url('repos'),
92 92 fixture._get_repo_create_params(
93 93 repo_private=False,
94 94 repo_name=repo_name,
95 95 repo_type=backend.alias,
96 96 repo_description=description,
97 97 csrf_token=csrf_token))
98 98
99 99 self.assert_repository_is_created_correctly(
100 100 repo_name, description, backend)
101 101
102 102 @pytest.mark.parametrize("suffix", [u'', u'Δ…Δ‡Δ™'], ids=['', 'non-ascii'])
103 103 def test_create_in_group(
104 104 self, autologin_user, backend, suffix, csrf_token):
105 105 # create GROUP
106 106 group_name = 'sometest_%s' % backend.alias
107 107 gr = RepoGroupModel().create(group_name=group_name,
108 108 group_description='test',
109 109 owner=TEST_USER_ADMIN_LOGIN)
110 110 Session().commit()
111 111
112 112 repo_name = u'ingroup' + suffix
113 113 repo_name_full = RepoGroup.url_sep().join(
114 114 [group_name, repo_name])
115 115 description = u'description for newly created repo'
116 116 self.app.post(
117 117 url('repos'),
118 118 fixture._get_repo_create_params(
119 119 repo_private=False,
120 120 repo_name=safe_str(repo_name),
121 121 repo_type=backend.alias,
122 122 repo_description=description,
123 123 repo_group=gr.group_id,
124 124 csrf_token=csrf_token))
125 125
126 126 # TODO: johbo: Cleanup work to fixture
127 127 try:
128 128 self.assert_repository_is_created_correctly(
129 129 repo_name_full, description, backend)
130 130
131 131 new_repo = RepoModel().get_by_repo_name(repo_name_full)
132 132 inherited_perms = UserRepoToPerm.query().filter(
133 133 UserRepoToPerm.repository_id == new_repo.repo_id).all()
134 134 assert len(inherited_perms) == 1
135 135 finally:
136 136 RepoModel().delete(repo_name_full)
137 137 RepoGroupModel().delete(group_name)
138 138 Session().commit()
139 139
140 140 def test_create_in_group_numeric(
141 141 self, autologin_user, backend, csrf_token):
142 142 # create GROUP
143 143 group_name = 'sometest_%s' % backend.alias
144 144 gr = RepoGroupModel().create(group_name=group_name,
145 145 group_description='test',
146 146 owner=TEST_USER_ADMIN_LOGIN)
147 147 Session().commit()
148 148
149 149 repo_name = '12345'
150 150 repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
151 151 description = 'description for newly created repo'
152 152 self.app.post(
153 153 url('repos'),
154 154 fixture._get_repo_create_params(
155 155 repo_private=False,
156 156 repo_name=repo_name,
157 157 repo_type=backend.alias,
158 158 repo_description=description,
159 159 repo_group=gr.group_id,
160 160 csrf_token=csrf_token))
161 161
162 162 # TODO: johbo: Cleanup work to fixture
163 163 try:
164 164 self.assert_repository_is_created_correctly(
165 165 repo_name_full, description, backend)
166 166
167 167 new_repo = RepoModel().get_by_repo_name(repo_name_full)
168 168 inherited_perms = UserRepoToPerm.query()\
169 169 .filter(UserRepoToPerm.repository_id == new_repo.repo_id).all()
170 170 assert len(inherited_perms) == 1
171 171 finally:
172 172 RepoModel().delete(repo_name_full)
173 173 RepoGroupModel().delete(group_name)
174 174 Session().commit()
175 175
176 176 def test_create_in_group_without_needed_permissions(self, backend):
177 177 session = login_user_session(
178 178 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
179 179 csrf_token = auth.get_csrf_token(session)
180 180 # revoke
181 181 user_model = UserModel()
182 182 # disable fork and create on default user
183 183 user_model.revoke_perm(User.DEFAULT_USER, 'hg.create.repository')
184 184 user_model.grant_perm(User.DEFAULT_USER, 'hg.create.none')
185 185 user_model.revoke_perm(User.DEFAULT_USER, 'hg.fork.repository')
186 186 user_model.grant_perm(User.DEFAULT_USER, 'hg.fork.none')
187 187
188 188 # disable on regular user
189 189 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.repository')
190 190 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.none')
191 191 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.repository')
192 192 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.none')
193 193 Session().commit()
194 194
195 195 # create GROUP
196 196 group_name = 'reg_sometest_%s' % backend.alias
197 197 gr = RepoGroupModel().create(group_name=group_name,
198 198 group_description='test',
199 199 owner=TEST_USER_ADMIN_LOGIN)
200 200 Session().commit()
201 201
202 202 group_name_allowed = 'reg_sometest_allowed_%s' % backend.alias
203 203 gr_allowed = RepoGroupModel().create(
204 204 group_name=group_name_allowed,
205 205 group_description='test',
206 206 owner=TEST_USER_REGULAR_LOGIN)
207 207 Session().commit()
208 208
209 209 repo_name = 'ingroup'
210 210 description = 'description for newly created repo'
211 211 response = self.app.post(
212 212 url('repos'),
213 213 fixture._get_repo_create_params(
214 214 repo_private=False,
215 215 repo_name=repo_name,
216 216 repo_type=backend.alias,
217 217 repo_description=description,
218 218 repo_group=gr.group_id,
219 219 csrf_token=csrf_token))
220 220
221 221 response.mustcontain('Invalid value')
222 222
223 223 # user is allowed to create in this group
224 224 repo_name = 'ingroup'
225 225 repo_name_full = RepoGroup.url_sep().join(
226 226 [group_name_allowed, repo_name])
227 227 description = 'description for newly created repo'
228 228 response = self.app.post(
229 229 url('repos'),
230 230 fixture._get_repo_create_params(
231 231 repo_private=False,
232 232 repo_name=repo_name,
233 233 repo_type=backend.alias,
234 234 repo_description=description,
235 235 repo_group=gr_allowed.group_id,
236 236 csrf_token=csrf_token))
237 237
238 238 # TODO: johbo: Cleanup in pytest fixture
239 239 try:
240 240 self.assert_repository_is_created_correctly(
241 241 repo_name_full, description, backend)
242 242
243 243 new_repo = RepoModel().get_by_repo_name(repo_name_full)
244 244 inherited_perms = UserRepoToPerm.query().filter(
245 245 UserRepoToPerm.repository_id == new_repo.repo_id).all()
246 246 assert len(inherited_perms) == 1
247 247
248 248 assert repo_on_filesystem(repo_name_full)
249 249 finally:
250 250 RepoModel().delete(repo_name_full)
251 251 RepoGroupModel().delete(group_name)
252 252 RepoGroupModel().delete(group_name_allowed)
253 253 Session().commit()
254 254
255 255 def test_create_in_group_inherit_permissions(self, autologin_user, backend,
256 256 csrf_token):
257 257 # create GROUP
258 258 group_name = 'sometest_%s' % backend.alias
259 259 gr = RepoGroupModel().create(group_name=group_name,
260 260 group_description='test',
261 261 owner=TEST_USER_ADMIN_LOGIN)
262 262 perm = Permission.get_by_key('repository.write')
263 263 RepoGroupModel().grant_user_permission(
264 264 gr, TEST_USER_REGULAR_LOGIN, perm)
265 265
266 266 # add repo permissions
267 267 Session().commit()
268 268
269 269 repo_name = 'ingroup_inherited_%s' % backend.alias
270 270 repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
271 271 description = 'description for newly created repo'
272 272 self.app.post(
273 273 url('repos'),
274 274 fixture._get_repo_create_params(
275 275 repo_private=False,
276 276 repo_name=repo_name,
277 277 repo_type=backend.alias,
278 278 repo_description=description,
279 279 repo_group=gr.group_id,
280 280 repo_copy_permissions=True,
281 281 csrf_token=csrf_token))
282 282
283 283 # TODO: johbo: Cleanup to pytest fixture
284 284 try:
285 285 self.assert_repository_is_created_correctly(
286 286 repo_name_full, description, backend)
287 287 except Exception:
288 288 RepoGroupModel().delete(group_name)
289 289 Session().commit()
290 290 raise
291 291
292 292 # check if inherited permissions are applied
293 293 new_repo = RepoModel().get_by_repo_name(repo_name_full)
294 294 inherited_perms = UserRepoToPerm.query().filter(
295 295 UserRepoToPerm.repository_id == new_repo.repo_id).all()
296 296 assert len(inherited_perms) == 2
297 297
298 298 assert TEST_USER_REGULAR_LOGIN in [
299 299 x.user.username for x in inherited_perms]
300 300 assert 'repository.write' in [
301 301 x.permission.permission_name for x in inherited_perms]
302 302
303 303 RepoModel().delete(repo_name_full)
304 304 RepoGroupModel().delete(group_name)
305 305 Session().commit()
306 306
307 307 @pytest.mark.xfail_backends(
308 308 "git", "hg", reason="Missing reposerver support")
309 309 def test_create_with_clone_uri(self, autologin_user, backend, reposerver,
310 310 csrf_token):
311 311 source_repo = backend.create_repo(number_of_commits=2)
312 312 source_repo_name = source_repo.repo_name
313 313 reposerver.serve(source_repo.scm_instance())
314 314
315 315 repo_name = backend.new_repo_name()
316 316 response = self.app.post(
317 317 url('repos'),
318 318 fixture._get_repo_create_params(
319 319 repo_private=False,
320 320 repo_name=repo_name,
321 321 repo_type=backend.alias,
322 322 repo_description='',
323 323 clone_uri=reposerver.url,
324 324 csrf_token=csrf_token),
325 325 status=302)
326 326
327 327 # Should be redirected to the creating page
328 328 response.mustcontain('repo_creating')
329 329
330 330 # Expecting that both repositories have same history
331 331 source_repo = RepoModel().get_by_repo_name(source_repo_name)
332 332 source_vcs = source_repo.scm_instance()
333 333 repo = RepoModel().get_by_repo_name(repo_name)
334 334 repo_vcs = repo.scm_instance()
335 335 assert source_vcs[0].message == repo_vcs[0].message
336 336 assert source_vcs.count() == repo_vcs.count()
337 337 assert source_vcs.commit_ids == repo_vcs.commit_ids
338 338
339 339 @pytest.mark.xfail_backends("svn", reason="Depends on import support")
340 340 def test_create_remote_repo_wrong_clone_uri(self, autologin_user, backend,
341 341 csrf_token):
342 342 repo_name = backend.new_repo_name()
343 343 description = 'description for newly created repo'
344 344 response = self.app.post(
345 345 url('repos'),
346 346 fixture._get_repo_create_params(
347 347 repo_private=False,
348 348 repo_name=repo_name,
349 349 repo_type=backend.alias,
350 350 repo_description=description,
351 351 clone_uri='http://repo.invalid/repo',
352 352 csrf_token=csrf_token))
353 353 response.mustcontain('invalid clone url')
354 354
355 355 @pytest.mark.xfail_backends("svn", reason="Depends on import support")
356 356 def test_create_remote_repo_wrong_clone_uri_hg_svn(
357 357 self, autologin_user, backend, csrf_token):
358 358 repo_name = backend.new_repo_name()
359 359 description = 'description for newly created repo'
360 360 response = self.app.post(
361 361 url('repos'),
362 362 fixture._get_repo_create_params(
363 363 repo_private=False,
364 364 repo_name=repo_name,
365 365 repo_type=backend.alias,
366 366 repo_description=description,
367 367 clone_uri='svn+http://svn.invalid/repo',
368 368 csrf_token=csrf_token))
369 369 response.mustcontain('invalid clone url')
370 370
371 371 def test_create_with_git_suffix(
372 372 self, autologin_user, backend, csrf_token):
373 373 repo_name = backend.new_repo_name() + ".git"
374 374 description = 'description for newly created repo'
375 375 response = self.app.post(
376 376 url('repos'),
377 377 fixture._get_repo_create_params(
378 378 repo_private=False,
379 379 repo_name=repo_name,
380 380 repo_type=backend.alias,
381 381 repo_description=description,
382 382 csrf_token=csrf_token))
383 383 response.mustcontain('Repository name cannot end with .git')
384 384
385 385 @pytest.mark.parametrize("suffix", [u'', u'Δ…Δ™Ε‚'], ids=['', 'non-ascii'])
386 386 def test_delete(self, autologin_user, backend, suffix, csrf_token):
387 387 repo = backend.create_repo(name_suffix=suffix)
388 388 repo_name = repo.repo_name
389 389
390 390 response = self.app.post(url('repo', repo_name=repo_name),
391 391 params={'_method': 'delete',
392 392 'csrf_token': csrf_token})
393 393 assert_session_flash(response, 'Deleted repository %s' % (repo_name))
394 394 response.follow()
395 395
396 396 # check if repo was deleted from db
397 397 assert RepoModel().get_by_repo_name(repo_name) is None
398 398 assert not repo_on_filesystem(repo_name)
399 399
400 400 def test_show(self, autologin_user, backend):
401 401 self.app.get(url('repo', repo_name=backend.repo_name))
402 402
403 403 def test_default_user_cannot_access_private_repo_in_a_group(
404 404 self, autologin_user, user_util, backend, csrf_token):
405 405
406 406 group = user_util.create_repo_group()
407 407
408 408 repo = backend.create_repo(
409 409 repo_private=True, repo_group=group, repo_copy_permissions=True)
410 410
411 411 permissions = _get_permission_for_user(
412 412 user='default', repo=repo.repo_name)
413 413 assert len(permissions) == 1
414 414 assert permissions[0].permission.permission_name == 'repository.none'
415 415 assert permissions[0].repository.private is True
416 416
417 417 def test_set_repo_fork_has_no_self_id(self, autologin_user, backend):
418 418 repo = backend.repo
419 419 response = self.app.get(
420 420 url('edit_repo_advanced', repo_name=backend.repo_name))
421 421 opt = """<option value="%s">vcs_test_git</option>""" % repo.repo_id
422 422 response.mustcontain(no=[opt])
423 423
424 424 def test_set_fork_of_target_repo(
425 425 self, autologin_user, backend, csrf_token):
426 426 target_repo = 'target_%s' % backend.alias
427 427 fixture.create_repo(target_repo, repo_type=backend.alias)
428 428 repo2 = Repository.get_by_repo_name(target_repo)
429 429 response = self.app.post(
430 430 url('edit_repo_advanced_fork', repo_name=backend.repo_name),
431 431 params={'id_fork_of': repo2.repo_id, '_method': 'put',
432 432 'csrf_token': csrf_token})
433 433 repo = Repository.get_by_repo_name(backend.repo_name)
434 434 repo2 = Repository.get_by_repo_name(target_repo)
435 435 assert_session_flash(
436 436 response,
437 437 'Marked repo %s as fork of %s' % (repo.repo_name, repo2.repo_name))
438 438
439 439 assert repo.fork == repo2
440 440 response = response.follow()
441 441 # check if given repo is selected
442 442
443 443 opt = 'This repository is a fork of <a href="%s">%s</a>' % (
444 444 url('summary_home', repo_name=repo2.repo_name), repo2.repo_name)
445 445
446 446 response.mustcontain(opt)
447 447
448 448 fixture.destroy_repo(target_repo, forks='detach')
449 449
450 450 @pytest.mark.backends("hg", "git")
451 451 def test_set_fork_of_other_type_repo(self, autologin_user, backend,
452 452 csrf_token):
453 453 TARGET_REPO_MAP = {
454 454 'git': {
455 455 'type': 'hg',
456 456 'repo_name': HG_REPO},
457 457 'hg': {
458 458 'type': 'git',
459 459 'repo_name': GIT_REPO},
460 460 }
461 461 target_repo = TARGET_REPO_MAP[backend.alias]
462 462
463 463 repo2 = Repository.get_by_repo_name(target_repo['repo_name'])
464 464 response = self.app.post(
465 465 url('edit_repo_advanced_fork', repo_name=backend.repo_name),
466 466 params={'id_fork_of': repo2.repo_id, '_method': 'put',
467 467 'csrf_token': csrf_token})
468 468 assert_session_flash(
469 469 response,
470 470 'Cannot set repository as fork of repository with other type')
471 471
472 472 def test_set_fork_of_none(self, autologin_user, backend, csrf_token):
473 473 # mark it as None
474 474 response = self.app.post(
475 475 url('edit_repo_advanced_fork', repo_name=backend.repo_name),
476 476 params={'id_fork_of': None, '_method': 'put',
477 477 'csrf_token': csrf_token})
478 478 assert_session_flash(
479 479 response,
480 480 'Marked repo %s as fork of %s'
481 481 % (backend.repo_name, "Nothing"))
482 482 assert backend.repo.fork is None
483 483
484 484 def test_set_fork_of_same_repo(self, autologin_user, backend, csrf_token):
485 485 repo = Repository.get_by_repo_name(backend.repo_name)
486 486 response = self.app.post(
487 487 url('edit_repo_advanced_fork', repo_name=backend.repo_name),
488 488 params={'id_fork_of': repo.repo_id, '_method': 'put',
489 489 'csrf_token': csrf_token})
490 490 assert_session_flash(
491 491 response, 'An error occurred during this operation')
492 492
493 493 def test_create_on_top_level_without_permissions(self, backend):
494 494 session = login_user_session(
495 495 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
496 496 csrf_token = auth.get_csrf_token(session)
497 497
498 498 # revoke
499 499 user_model = UserModel()
500 500 # disable fork and create on default user
501 501 user_model.revoke_perm(User.DEFAULT_USER, 'hg.create.repository')
502 502 user_model.grant_perm(User.DEFAULT_USER, 'hg.create.none')
503 503 user_model.revoke_perm(User.DEFAULT_USER, 'hg.fork.repository')
504 504 user_model.grant_perm(User.DEFAULT_USER, 'hg.fork.none')
505 505
506 506 # disable on regular user
507 507 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.repository')
508 508 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.none')
509 509 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.repository')
510 510 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.none')
511 511 Session().commit()
512 512
513 513 repo_name = backend.new_repo_name()
514 514 description = 'description for newly created repo'
515 515 response = self.app.post(
516 516 url('repos'),
517 517 fixture._get_repo_create_params(
518 518 repo_private=False,
519 519 repo_name=repo_name,
520 520 repo_type=backend.alias,
521 521 repo_description=description,
522 522 csrf_token=csrf_token))
523 523
524 524 response.mustcontain(
525 525 u"You do not have the permission to store repositories in "
526 526 u"the root location.")
527 527
528 528 @mock.patch.object(RepoModel, '_create_filesystem_repo', error_function)
529 529 def test_create_repo_when_filesystem_op_fails(
530 530 self, autologin_user, backend, csrf_token):
531 531 repo_name = backend.new_repo_name()
532 532 description = 'description for newly created repo'
533 533
534 534 response = self.app.post(
535 535 url('repos'),
536 536 fixture._get_repo_create_params(
537 537 repo_private=False,
538 538 repo_name=repo_name,
539 539 repo_type=backend.alias,
540 540 repo_description=description,
541 541 csrf_token=csrf_token))
542 542
543 543 assert_session_flash(
544 544 response, 'Error creating repository %s' % repo_name)
545 545 # repo must not be in db
546 546 assert backend.repo is None
547 547 # repo must not be in filesystem !
548 548 assert not repo_on_filesystem(repo_name)
549 549
550 550 def assert_repository_is_created_correctly(
551 551 self, repo_name, description, backend):
552 552 repo_name_utf8 = safe_str(repo_name)
553 553
554 554 # run the check page that triggers the flash message
555 555 response = self.app.get(url('repo_check_home', repo_name=repo_name))
556 556 assert response.json == {u'result': True}
557 557
558 558 flash_msg = u'Created repository <a href="/{}">{}</a>'.format(
559 559 urllib.quote(repo_name_utf8), repo_name)
560 560 assert_session_flash(response, flash_msg)
561 561
562 562 # test if the repo was created in the database
563 563 new_repo = RepoModel().get_by_repo_name(repo_name)
564 564
565 565 assert new_repo.repo_name == repo_name
566 566 assert new_repo.description == description
567 567
568 568 # test if the repository is visible in the list ?
569 569 response = self.app.get(url('summary_home', repo_name=repo_name))
570 570 response.mustcontain(repo_name)
571 571 response.mustcontain(backend.alias)
572 572
573 573 assert repo_on_filesystem(repo_name)
574 574
575 575
576 576 @pytest.mark.usefixtures("app")
577 577 class TestVcsSettings(object):
578 578 FORM_DATA = {
579 579 'inherit_global_settings': False,
580 580 'hooks_changegroup_repo_size': False,
581 581 'hooks_changegroup_push_logger': False,
582 582 'hooks_outgoing_pull_logger': False,
583 583 'extensions_largefiles': False,
584 'extensions_evolve': False,
584 585 'phases_publish': 'False',
585 586 'rhodecode_pr_merge_enabled': False,
586 587 'rhodecode_use_outdated_comments': False,
587 588 'new_svn_branch': '',
588 589 'new_svn_tag': ''
589 590 }
590 591
591 592 @pytest.mark.skip_backends('svn')
592 593 def test_global_settings_initial_values(self, autologin_user, backend):
593 594 repo_name = backend.repo_name
594 595 response = self.app.get(url('repo_vcs_settings', repo_name=repo_name))
595 596
596 597 expected_settings = (
597 598 'rhodecode_use_outdated_comments', 'rhodecode_pr_merge_enabled',
598 599 'hooks_changegroup_repo_size', 'hooks_changegroup_push_logger',
599 600 'hooks_outgoing_pull_logger'
600 601 )
601 602 for setting in expected_settings:
602 603 self.assert_repo_value_equals_global_value(response, setting)
603 604
604 605 def test_show_settings_requires_repo_admin_permission(
605 606 self, backend, user_util, settings_util):
606 607 repo = backend.create_repo()
607 608 repo_name = repo.repo_name
608 609 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
609 610 user_util.grant_user_permission_to_repo(repo, user, 'repository.admin')
610 611 login_user_session(
611 612 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
612 613 self.app.get(url('repo_vcs_settings', repo_name=repo_name), status=200)
613 614
614 615 def test_inherit_global_settings_flag_is_true_by_default(
615 616 self, autologin_user, backend):
616 617 repo_name = backend.repo_name
617 618 response = self.app.get(url('repo_vcs_settings', repo_name=repo_name))
618 619
619 620 assert_response = AssertResponse(response)
620 621 element = assert_response.get_element('#inherit_global_settings')
621 622 assert element.checked
622 623
623 624 @pytest.mark.parametrize('checked_value', [True, False])
624 625 def test_inherit_global_settings_value(
625 626 self, autologin_user, backend, checked_value, settings_util):
626 627 repo = backend.create_repo()
627 628 repo_name = repo.repo_name
628 629 settings_util.create_repo_rhodecode_setting(
629 630 repo, 'inherit_vcs_settings', checked_value, 'bool')
630 631 response = self.app.get(url('repo_vcs_settings', repo_name=repo_name))
631 632
632 633 assert_response = AssertResponse(response)
633 634 element = assert_response.get_element('#inherit_global_settings')
634 635 assert element.checked == checked_value
635 636
636 637 @pytest.mark.skip_backends('svn')
637 638 def test_hooks_settings_are_created(
638 639 self, autologin_user, backend, csrf_token):
639 640 repo_name = backend.repo_name
640 641 data = self.FORM_DATA.copy()
641 642 data['csrf_token'] = csrf_token
642 643 self.app.post(
643 644 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
644 645 settings = SettingsModel(repo=repo_name)
645 646 try:
646 647 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
647 648 ui = settings.get_ui_by_section_and_key(section, key)
648 649 assert ui.ui_active is False
649 650 finally:
650 651 self._cleanup_repo_settings(settings)
651 652
652 653 def test_hooks_settings_are_not_created_for_svn(
653 654 self, autologin_user, backend_svn, csrf_token):
654 655 repo_name = backend_svn.repo_name
655 656 data = self.FORM_DATA.copy()
656 657 data['csrf_token'] = csrf_token
657 658 self.app.post(
658 659 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
659 660 settings = SettingsModel(repo=repo_name)
660 661 try:
661 662 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
662 663 ui = settings.get_ui_by_section_and_key(section, key)
663 664 assert ui is None
664 665 finally:
665 666 self._cleanup_repo_settings(settings)
666 667
667 668 @pytest.mark.skip_backends('svn')
668 669 def test_hooks_settings_are_updated(
669 670 self, autologin_user, backend, csrf_token):
670 671 repo_name = backend.repo_name
671 672 settings = SettingsModel(repo=repo_name)
672 673 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
673 674 settings.create_ui_section_value(section, '', key=key, active=True)
674 675
675 676 data = self.FORM_DATA.copy()
676 677 data['csrf_token'] = csrf_token
677 678 self.app.post(
678 679 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
679 680 try:
680 681 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
681 682 ui = settings.get_ui_by_section_and_key(section, key)
682 683 assert ui.ui_active is False
683 684 finally:
684 685 self._cleanup_repo_settings(settings)
685 686
686 687 def test_hooks_settings_are_not_updated_for_svn(
687 688 self, autologin_user, backend_svn, csrf_token):
688 689 repo_name = backend_svn.repo_name
689 690 settings = SettingsModel(repo=repo_name)
690 691 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
691 692 settings.create_ui_section_value(section, '', key=key, active=True)
692 693
693 694 data = self.FORM_DATA.copy()
694 695 data['csrf_token'] = csrf_token
695 696 self.app.post(
696 697 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
697 698 try:
698 699 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
699 700 ui = settings.get_ui_by_section_and_key(section, key)
700 701 assert ui.ui_active is True
701 702 finally:
702 703 self._cleanup_repo_settings(settings)
703 704
704 705 @pytest.mark.skip_backends('svn')
705 706 def test_pr_settings_are_created(
706 707 self, autologin_user, backend, csrf_token):
707 708 repo_name = backend.repo_name
708 709 data = self.FORM_DATA.copy()
709 710 data['csrf_token'] = csrf_token
710 711 self.app.post(
711 712 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
712 713 settings = SettingsModel(repo=repo_name)
713 714 try:
714 715 for name in VcsSettingsModel.GENERAL_SETTINGS:
715 716 setting = settings.get_setting_by_name(name)
716 717 assert setting.app_settings_value is False
717 718 finally:
718 719 self._cleanup_repo_settings(settings)
719 720
720 721 def test_pr_settings_are_not_created_for_svn(
721 722 self, autologin_user, backend_svn, csrf_token):
722 723 repo_name = backend_svn.repo_name
723 724 data = self.FORM_DATA.copy()
724 725 data['csrf_token'] = csrf_token
725 726 self.app.post(
726 727 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
727 728 settings = SettingsModel(repo=repo_name)
728 729 try:
729 730 for name in VcsSettingsModel.GENERAL_SETTINGS:
730 731 setting = settings.get_setting_by_name(name)
731 732 assert setting is None
732 733 finally:
733 734 self._cleanup_repo_settings(settings)
734 735
735 736 def test_pr_settings_creation_requires_repo_admin_permission(
736 737 self, backend, user_util, settings_util, csrf_token):
737 738 repo = backend.create_repo()
738 739 repo_name = repo.repo_name
739 740
740 741 logout_user_session(self.app, csrf_token)
741 742 session = login_user_session(
742 743 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
743 744 new_csrf_token = auth.get_csrf_token(session)
744 745
745 746 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
746 747 repo = Repository.get_by_repo_name(repo_name)
747 748 user_util.grant_user_permission_to_repo(repo, user, 'repository.admin')
748 749 data = self.FORM_DATA.copy()
749 750 data['csrf_token'] = new_csrf_token
750 751 settings = SettingsModel(repo=repo_name)
751 752
752 753 try:
753 754 self.app.post(
754 755 url('repo_vcs_settings', repo_name=repo_name), data,
755 756 status=302)
756 757 finally:
757 758 self._cleanup_repo_settings(settings)
758 759
759 760 @pytest.mark.skip_backends('svn')
760 761 def test_pr_settings_are_updated(
761 762 self, autologin_user, backend, csrf_token):
762 763 repo_name = backend.repo_name
763 764 settings = SettingsModel(repo=repo_name)
764 765 for name in VcsSettingsModel.GENERAL_SETTINGS:
765 766 settings.create_or_update_setting(name, True, 'bool')
766 767
767 768 data = self.FORM_DATA.copy()
768 769 data['csrf_token'] = csrf_token
769 770 self.app.post(
770 771 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
771 772 try:
772 773 for name in VcsSettingsModel.GENERAL_SETTINGS:
773 774 setting = settings.get_setting_by_name(name)
774 775 assert setting.app_settings_value is False
775 776 finally:
776 777 self._cleanup_repo_settings(settings)
777 778
778 779 def test_pr_settings_are_not_updated_for_svn(
779 780 self, autologin_user, backend_svn, csrf_token):
780 781 repo_name = backend_svn.repo_name
781 782 settings = SettingsModel(repo=repo_name)
782 783 for name in VcsSettingsModel.GENERAL_SETTINGS:
783 784 settings.create_or_update_setting(name, True, 'bool')
784 785
785 786 data = self.FORM_DATA.copy()
786 787 data['csrf_token'] = csrf_token
787 788 self.app.post(
788 789 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
789 790 try:
790 791 for name in VcsSettingsModel.GENERAL_SETTINGS:
791 792 setting = settings.get_setting_by_name(name)
792 793 assert setting.app_settings_value is True
793 794 finally:
794 795 self._cleanup_repo_settings(settings)
795 796
796 797 def test_svn_settings_are_created(
797 798 self, autologin_user, backend_svn, csrf_token, settings_util):
798 799 repo_name = backend_svn.repo_name
799 800 data = self.FORM_DATA.copy()
800 801 data['new_svn_tag'] = 'svn-tag'
801 802 data['new_svn_branch'] = 'svn-branch'
802 803 data['csrf_token'] = csrf_token
803 804
804 805 # Create few global settings to make sure that uniqueness validators
805 806 # are not triggered
806 807 settings_util.create_rhodecode_ui(
807 808 VcsSettingsModel.SVN_BRANCH_SECTION, 'svn-branch')
808 809 settings_util.create_rhodecode_ui(
809 810 VcsSettingsModel.SVN_TAG_SECTION, 'svn-tag')
810 811
811 812 self.app.post(
812 813 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
813 814 settings = SettingsModel(repo=repo_name)
814 815 try:
815 816 svn_branches = settings.get_ui_by_section(
816 817 VcsSettingsModel.SVN_BRANCH_SECTION)
817 818 svn_branch_names = [b.ui_value for b in svn_branches]
818 819 svn_tags = settings.get_ui_by_section(
819 820 VcsSettingsModel.SVN_TAG_SECTION)
820 821 svn_tag_names = [b.ui_value for b in svn_tags]
821 822 assert 'svn-branch' in svn_branch_names
822 823 assert 'svn-tag' in svn_tag_names
823 824 finally:
824 825 self._cleanup_repo_settings(settings)
825 826
826 827 def test_svn_settings_are_unique(
827 828 self, autologin_user, backend_svn, csrf_token, settings_util):
828 829 repo = backend_svn.repo
829 830 repo_name = repo.repo_name
830 831 data = self.FORM_DATA.copy()
831 832 data['new_svn_tag'] = 'test_tag'
832 833 data['new_svn_branch'] = 'test_branch'
833 834 data['csrf_token'] = csrf_token
834 835 settings_util.create_repo_rhodecode_ui(
835 836 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch')
836 837 settings_util.create_repo_rhodecode_ui(
837 838 repo, VcsSettingsModel.SVN_TAG_SECTION, 'test_tag')
838 839
839 840 response = self.app.post(
840 841 url('repo_vcs_settings', repo_name=repo_name), data, status=200)
841 842 response.mustcontain('Pattern already exists')
842 843
843 844 def test_svn_settings_with_empty_values_are_not_created(
844 845 self, autologin_user, backend_svn, csrf_token):
845 846 repo_name = backend_svn.repo_name
846 847 data = self.FORM_DATA.copy()
847 848 data['csrf_token'] = csrf_token
848 849 self.app.post(
849 850 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
850 851 settings = SettingsModel(repo=repo_name)
851 852 try:
852 853 svn_branches = settings.get_ui_by_section(
853 854 VcsSettingsModel.SVN_BRANCH_SECTION)
854 855 svn_tags = settings.get_ui_by_section(
855 856 VcsSettingsModel.SVN_TAG_SECTION)
856 857 assert len(svn_branches) == 0
857 858 assert len(svn_tags) == 0
858 859 finally:
859 860 self._cleanup_repo_settings(settings)
860 861
861 862 def test_svn_settings_are_shown_for_svn_repository(
862 863 self, autologin_user, backend_svn, csrf_token):
863 864 repo_name = backend_svn.repo_name
864 865 response = self.app.get(
865 866 url('repo_vcs_settings', repo_name=repo_name), status=200)
866 867 response.mustcontain('Subversion Settings')
867 868
868 869 @pytest.mark.skip_backends('svn')
869 870 def test_svn_settings_are_not_created_for_not_svn_repository(
870 871 self, autologin_user, backend, csrf_token):
871 872 repo_name = backend.repo_name
872 873 data = self.FORM_DATA.copy()
873 874 data['csrf_token'] = csrf_token
874 875 self.app.post(
875 876 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
876 877 settings = SettingsModel(repo=repo_name)
877 878 try:
878 879 svn_branches = settings.get_ui_by_section(
879 880 VcsSettingsModel.SVN_BRANCH_SECTION)
880 881 svn_tags = settings.get_ui_by_section(
881 882 VcsSettingsModel.SVN_TAG_SECTION)
882 883 assert len(svn_branches) == 0
883 884 assert len(svn_tags) == 0
884 885 finally:
885 886 self._cleanup_repo_settings(settings)
886 887
887 888 @pytest.mark.skip_backends('svn')
888 889 def test_svn_settings_are_shown_only_for_svn_repository(
889 890 self, autologin_user, backend, csrf_token):
890 891 repo_name = backend.repo_name
891 892 response = self.app.get(
892 893 url('repo_vcs_settings', repo_name=repo_name), status=200)
893 894 response.mustcontain(no='Subversion Settings')
894 895
895 896 def test_hg_settings_are_created(
896 897 self, autologin_user, backend_hg, csrf_token):
897 898 repo_name = backend_hg.repo_name
898 899 data = self.FORM_DATA.copy()
899 900 data['new_svn_tag'] = 'svn-tag'
900 901 data['new_svn_branch'] = 'svn-branch'
901 902 data['csrf_token'] = csrf_token
902 903 self.app.post(
903 904 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
904 905 settings = SettingsModel(repo=repo_name)
905 906 try:
906 907 largefiles_ui = settings.get_ui_by_section_and_key(
907 908 'extensions', 'largefiles')
908 909 assert largefiles_ui.ui_active is False
909 910 phases_ui = settings.get_ui_by_section_and_key(
910 911 'phases', 'publish')
911 912 assert str2bool(phases_ui.ui_value) is False
912 913 finally:
913 914 self._cleanup_repo_settings(settings)
914 915
915 916 def test_hg_settings_are_updated(
916 917 self, autologin_user, backend_hg, csrf_token):
917 918 repo_name = backend_hg.repo_name
918 919 settings = SettingsModel(repo=repo_name)
919 920 settings.create_ui_section_value(
920 921 'extensions', '', key='largefiles', active=True)
921 922 settings.create_ui_section_value(
922 923 'phases', '1', key='publish', active=True)
923 924
924 925 data = self.FORM_DATA.copy()
925 926 data['csrf_token'] = csrf_token
926 927 self.app.post(
927 928 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
928 929 try:
929 930 largefiles_ui = settings.get_ui_by_section_and_key(
930 931 'extensions', 'largefiles')
931 932 assert largefiles_ui.ui_active is False
932 933 phases_ui = settings.get_ui_by_section_and_key(
933 934 'phases', 'publish')
934 935 assert str2bool(phases_ui.ui_value) is False
935 936 finally:
936 937 self._cleanup_repo_settings(settings)
937 938
938 939 def test_hg_settings_are_shown_for_hg_repository(
939 940 self, autologin_user, backend_hg, csrf_token):
940 941 repo_name = backend_hg.repo_name
941 942 response = self.app.get(
942 943 url('repo_vcs_settings', repo_name=repo_name), status=200)
943 944 response.mustcontain('Mercurial Settings')
944 945
945 946 @pytest.mark.skip_backends('hg')
946 947 def test_hg_settings_are_created_only_for_hg_repository(
947 948 self, autologin_user, backend, csrf_token):
948 949 repo_name = backend.repo_name
949 950 data = self.FORM_DATA.copy()
950 951 data['csrf_token'] = csrf_token
951 952 self.app.post(
952 953 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
953 954 settings = SettingsModel(repo=repo_name)
954 955 try:
955 956 largefiles_ui = settings.get_ui_by_section_and_key(
956 957 'extensions', 'largefiles')
957 958 assert largefiles_ui is None
958 959 phases_ui = settings.get_ui_by_section_and_key(
959 960 'phases', 'publish')
960 961 assert phases_ui is None
961 962 finally:
962 963 self._cleanup_repo_settings(settings)
963 964
964 965 @pytest.mark.skip_backends('hg')
965 966 def test_hg_settings_are_shown_only_for_hg_repository(
966 967 self, autologin_user, backend, csrf_token):
967 968 repo_name = backend.repo_name
968 969 response = self.app.get(
969 970 url('repo_vcs_settings', repo_name=repo_name), status=200)
970 971 response.mustcontain(no='Mercurial Settings')
971 972
972 973 @pytest.mark.skip_backends('hg')
973 974 def test_hg_settings_are_updated_only_for_hg_repository(
974 975 self, autologin_user, backend, csrf_token):
975 976 repo_name = backend.repo_name
976 977 settings = SettingsModel(repo=repo_name)
977 978 settings.create_ui_section_value(
978 979 'extensions', '', key='largefiles', active=True)
979 980 settings.create_ui_section_value(
980 981 'phases', '1', key='publish', active=True)
981 982
982 983 data = self.FORM_DATA.copy()
983 984 data['csrf_token'] = csrf_token
984 985 self.app.post(
985 986 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
986 987 try:
987 988 largefiles_ui = settings.get_ui_by_section_and_key(
988 989 'extensions', 'largefiles')
989 990 assert largefiles_ui.ui_active is True
990 991 phases_ui = settings.get_ui_by_section_and_key(
991 992 'phases', 'publish')
992 993 assert phases_ui.ui_value == '1'
993 994 finally:
994 995 self._cleanup_repo_settings(settings)
995 996
996 997 def test_per_repo_svn_settings_are_displayed(
997 998 self, autologin_user, backend_svn, settings_util):
998 999 repo = backend_svn.create_repo()
999 1000 repo_name = repo.repo_name
1000 1001 branches = [
1001 1002 settings_util.create_repo_rhodecode_ui(
1002 1003 repo, VcsSettingsModel.SVN_BRANCH_SECTION,
1003 1004 'branch_{}'.format(i))
1004 1005 for i in range(10)]
1005 1006 tags = [
1006 1007 settings_util.create_repo_rhodecode_ui(
1007 1008 repo, VcsSettingsModel.SVN_TAG_SECTION, 'tag_{}'.format(i))
1008 1009 for i in range(10)]
1009 1010
1010 1011 response = self.app.get(
1011 1012 url('repo_vcs_settings', repo_name=repo_name), status=200)
1012 1013 assert_response = AssertResponse(response)
1013 1014 for branch in branches:
1014 1015 css_selector = '[name=branch_value_{}]'.format(branch.ui_id)
1015 1016 element = assert_response.get_element(css_selector)
1016 1017 assert element.value == branch.ui_value
1017 1018 for tag in tags:
1018 1019 css_selector = '[name=tag_ui_value_new_{}]'.format(tag.ui_id)
1019 1020 element = assert_response.get_element(css_selector)
1020 1021 assert element.value == tag.ui_value
1021 1022
1022 1023 def test_per_repo_hg_and_pr_settings_are_not_displayed_for_svn(
1023 1024 self, autologin_user, backend_svn, settings_util):
1024 1025 repo = backend_svn.create_repo()
1025 1026 repo_name = repo.repo_name
1026 1027 response = self.app.get(
1027 1028 url('repo_vcs_settings', repo_name=repo_name), status=200)
1028 1029 response.mustcontain(no='<label>Hooks:</label>')
1029 1030 response.mustcontain(no='<label>Pull Request Settings:</label>')
1030 1031
1031 1032 def test_inherit_global_settings_value_is_saved(
1032 1033 self, autologin_user, backend, csrf_token):
1033 1034 repo_name = backend.repo_name
1034 1035 data = self.FORM_DATA.copy()
1035 1036 data['csrf_token'] = csrf_token
1036 1037 data['inherit_global_settings'] = True
1037 1038 self.app.post(
1038 1039 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
1039 1040
1040 1041 settings = SettingsModel(repo=repo_name)
1041 1042 vcs_settings = VcsSettingsModel(repo=repo_name)
1042 1043 try:
1043 1044 assert vcs_settings.inherit_global_settings is True
1044 1045 finally:
1045 1046 self._cleanup_repo_settings(settings)
1046 1047
1047 1048 def test_repo_cache_is_invalidated_when_settings_are_updated(
1048 1049 self, autologin_user, backend, csrf_token):
1049 1050 repo_name = backend.repo_name
1050 1051 data = self.FORM_DATA.copy()
1051 1052 data['csrf_token'] = csrf_token
1052 1053 data['inherit_global_settings'] = True
1053 1054 settings = SettingsModel(repo=repo_name)
1054 1055
1055 1056 invalidation_patcher = mock.patch(
1056 1057 'rhodecode.controllers.admin.repos.ScmModel.mark_for_invalidation')
1057 1058 with invalidation_patcher as invalidation_mock:
1058 1059 self.app.post(
1059 1060 url('repo_vcs_settings', repo_name=repo_name), data,
1060 1061 status=302)
1061 1062 try:
1062 1063 invalidation_mock.assert_called_once_with(repo_name, delete=True)
1063 1064 finally:
1064 1065 self._cleanup_repo_settings(settings)
1065 1066
1066 1067 def test_other_settings_not_saved_inherit_global_settings_is_true(
1067 1068 self, autologin_user, backend, csrf_token):
1068 1069 repo_name = backend.repo_name
1069 1070 data = self.FORM_DATA.copy()
1070 1071 data['csrf_token'] = csrf_token
1071 1072 data['inherit_global_settings'] = True
1072 1073 self.app.post(
1073 1074 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
1074 1075
1075 1076 settings = SettingsModel(repo=repo_name)
1076 1077 ui_settings = (
1077 1078 VcsSettingsModel.HOOKS_SETTINGS + VcsSettingsModel.HG_SETTINGS)
1078 1079
1079 1080 vcs_settings = []
1080 1081 try:
1081 1082 for section, key in ui_settings:
1082 1083 ui = settings.get_ui_by_section_and_key(section, key)
1083 1084 if ui:
1084 1085 vcs_settings.append(ui)
1085 1086 vcs_settings.extend(settings.get_ui_by_section(
1086 1087 VcsSettingsModel.SVN_BRANCH_SECTION))
1087 1088 vcs_settings.extend(settings.get_ui_by_section(
1088 1089 VcsSettingsModel.SVN_TAG_SECTION))
1089 1090 for name in VcsSettingsModel.GENERAL_SETTINGS:
1090 1091 setting = settings.get_setting_by_name(name)
1091 1092 if setting:
1092 1093 vcs_settings.append(setting)
1093 1094 assert vcs_settings == []
1094 1095 finally:
1095 1096 self._cleanup_repo_settings(settings)
1096 1097
1097 1098 def test_delete_svn_branch_and_tag_patterns(
1098 1099 self, autologin_user, backend_svn, settings_util, csrf_token):
1099 1100 repo = backend_svn.create_repo()
1100 1101 repo_name = repo.repo_name
1101 1102 branch = settings_util.create_repo_rhodecode_ui(
1102 1103 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch',
1103 1104 cleanup=False)
1104 1105 tag = settings_util.create_repo_rhodecode_ui(
1105 1106 repo, VcsSettingsModel.SVN_TAG_SECTION, 'test_tag', cleanup=False)
1106 1107 data = {
1107 1108 '_method': 'delete',
1108 1109 'csrf_token': csrf_token
1109 1110 }
1110 1111 for id_ in (branch.ui_id, tag.ui_id):
1111 1112 data['delete_svn_pattern'] = id_,
1112 1113 self.app.post(
1113 1114 url('repo_vcs_settings', repo_name=repo_name), data,
1114 1115 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200)
1115 1116 settings = VcsSettingsModel(repo=repo_name)
1116 1117 assert settings.get_repo_svn_branch_patterns() == []
1117 1118
1118 1119 def test_delete_svn_branch_requires_repo_admin_permission(
1119 1120 self, backend_svn, user_util, settings_util, csrf_token):
1120 1121 repo = backend_svn.create_repo()
1121 1122 repo_name = repo.repo_name
1122 1123
1123 1124 logout_user_session(self.app, csrf_token)
1124 1125 session = login_user_session(
1125 1126 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
1126 1127 csrf_token = auth.get_csrf_token(session)
1127 1128
1128 1129 repo = Repository.get_by_repo_name(repo_name)
1129 1130 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
1130 1131 user_util.grant_user_permission_to_repo(repo, user, 'repository.admin')
1131 1132 branch = settings_util.create_repo_rhodecode_ui(
1132 1133 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch',
1133 1134 cleanup=False)
1134 1135 data = {
1135 1136 '_method': 'delete',
1136 1137 'csrf_token': csrf_token,
1137 1138 'delete_svn_pattern': branch.ui_id
1138 1139 }
1139 1140 self.app.post(
1140 1141 url('repo_vcs_settings', repo_name=repo_name), data,
1141 1142 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200)
1142 1143
1143 1144 def test_delete_svn_branch_raises_400_when_not_found(
1144 1145 self, autologin_user, backend_svn, settings_util, csrf_token):
1145 1146 repo_name = backend_svn.repo_name
1146 1147 data = {
1147 1148 '_method': 'delete',
1148 1149 'delete_svn_pattern': 123,
1149 1150 'csrf_token': csrf_token
1150 1151 }
1151 1152 self.app.post(
1152 1153 url('repo_vcs_settings', repo_name=repo_name), data,
1153 1154 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=400)
1154 1155
1155 1156 def test_delete_svn_branch_raises_400_when_no_id_specified(
1156 1157 self, autologin_user, backend_svn, settings_util, csrf_token):
1157 1158 repo_name = backend_svn.repo_name
1158 1159 data = {
1159 1160 '_method': 'delete',
1160 1161 'csrf_token': csrf_token
1161 1162 }
1162 1163 self.app.post(
1163 1164 url('repo_vcs_settings', repo_name=repo_name), data,
1164 1165 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=400)
1165 1166
1166 1167 def _cleanup_repo_settings(self, settings_model):
1167 1168 cleanup = []
1168 1169 ui_settings = (
1169 1170 VcsSettingsModel.HOOKS_SETTINGS + VcsSettingsModel.HG_SETTINGS)
1170 1171
1171 1172 for section, key in ui_settings:
1172 1173 ui = settings_model.get_ui_by_section_and_key(section, key)
1173 1174 if ui:
1174 1175 cleanup.append(ui)
1175 1176
1176 1177 cleanup.extend(settings_model.get_ui_by_section(
1177 1178 VcsSettingsModel.INHERIT_SETTINGS))
1178 1179 cleanup.extend(settings_model.get_ui_by_section(
1179 1180 VcsSettingsModel.SVN_BRANCH_SECTION))
1180 1181 cleanup.extend(settings_model.get_ui_by_section(
1181 1182 VcsSettingsModel.SVN_TAG_SECTION))
1182 1183
1183 1184 for name in VcsSettingsModel.GENERAL_SETTINGS:
1184 1185 setting = settings_model.get_setting_by_name(name)
1185 1186 if setting:
1186 1187 cleanup.append(setting)
1187 1188
1188 1189 for object_ in cleanup:
1189 1190 Session().delete(object_)
1190 1191 Session().commit()
1191 1192
1192 1193 def assert_repo_value_equals_global_value(self, response, setting):
1193 1194 assert_response = AssertResponse(response)
1194 1195 global_css_selector = '[name={}_inherited]'.format(setting)
1195 1196 repo_css_selector = '[name={}]'.format(setting)
1196 1197 repo_element = assert_response.get_element(repo_css_selector)
1197 1198 global_element = assert_response.get_element(global_css_selector)
1198 1199 assert repo_element.value == global_element.value
1199 1200
1200 1201
1201 1202 def _get_permission_for_user(user, repo):
1202 1203 perm = UserRepoToPerm.query()\
1203 1204 .filter(UserRepoToPerm.repository ==
1204 1205 Repository.get_by_repo_name(repo))\
1205 1206 .filter(UserRepoToPerm.user == User.get_by_username(user))\
1206 1207 .all()
1207 1208 return perm
@@ -1,665 +1,682 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 import mock
22 22 import pytest
23 23
24 24 import rhodecode
25 25 from rhodecode.config.routing import ADMIN_PREFIX
26 26 from rhodecode.lib.utils2 import md5
27 27 from rhodecode.model.db import RhodeCodeUi
28 28 from rhodecode.model.meta import Session
29 29 from rhodecode.model.settings import SettingsModel, IssueTrackerSettingsModel
30 30 from rhodecode.tests import url, assert_session_flash
31 31 from rhodecode.tests.utils import AssertResponse
32 32
33 33
34 34 UPDATE_DATA_QUALNAME = (
35 35 'rhodecode.apps.admin.views.system_info.AdminSystemInfoSettingsView.get_update_data')
36 36
37 37
38 38 @pytest.mark.usefixtures('autologin_user', 'app')
39 39 class TestAdminSettingsController(object):
40 40
41 41 @pytest.mark.parametrize('urlname', [
42 42 'admin_settings_vcs',
43 43 'admin_settings_mapping',
44 44 'admin_settings_global',
45 45 'admin_settings_visual',
46 46 'admin_settings_email',
47 47 'admin_settings_hooks',
48 48 'admin_settings_search',
49 49 ])
50 50 def test_simple_get(self, urlname, app):
51 51 app.get(url(urlname))
52 52
53 53 def test_create_custom_hook(self, csrf_token):
54 54 response = self.app.post(
55 55 url('admin_settings_hooks'),
56 56 params={
57 57 'new_hook_ui_key': 'test_hooks_1',
58 58 'new_hook_ui_value': 'cd /tmp',
59 59 'csrf_token': csrf_token})
60 60
61 61 response = response.follow()
62 62 response.mustcontain('test_hooks_1')
63 63 response.mustcontain('cd /tmp')
64 64
65 65 def test_create_custom_hook_delete(self, csrf_token):
66 66 response = self.app.post(
67 67 url('admin_settings_hooks'),
68 68 params={
69 69 'new_hook_ui_key': 'test_hooks_2',
70 70 'new_hook_ui_value': 'cd /tmp2',
71 71 'csrf_token': csrf_token})
72 72
73 73 response = response.follow()
74 74 response.mustcontain('test_hooks_2')
75 75 response.mustcontain('cd /tmp2')
76 76
77 77 hook_id = SettingsModel().get_ui_by_key('test_hooks_2').ui_id
78 78
79 79 # delete
80 80 self.app.post(
81 81 url('admin_settings_hooks'),
82 82 params={'hook_id': hook_id, 'csrf_token': csrf_token})
83 83 response = self.app.get(url('admin_settings_hooks'))
84 84 response.mustcontain(no=['test_hooks_2'])
85 85 response.mustcontain(no=['cd /tmp2'])
86 86
87 87
88 88 @pytest.mark.usefixtures('autologin_user', 'app')
89 89 class TestAdminSettingsGlobal(object):
90 90
91 91 def test_pre_post_code_code_active(self, csrf_token):
92 92 pre_code = 'rc-pre-code-187652122'
93 93 post_code = 'rc-postcode-98165231'
94 94
95 95 response = self.post_and_verify_settings({
96 96 'rhodecode_pre_code': pre_code,
97 97 'rhodecode_post_code': post_code,
98 98 'csrf_token': csrf_token,
99 99 })
100 100
101 101 response = response.follow()
102 102 response.mustcontain(pre_code, post_code)
103 103
104 104 def test_pre_post_code_code_inactive(self, csrf_token):
105 105 pre_code = 'rc-pre-code-187652122'
106 106 post_code = 'rc-postcode-98165231'
107 107 response = self.post_and_verify_settings({
108 108 'rhodecode_pre_code': '',
109 109 'rhodecode_post_code': '',
110 110 'csrf_token': csrf_token,
111 111 })
112 112
113 113 response = response.follow()
114 114 response.mustcontain(no=[pre_code, post_code])
115 115
116 116 def test_captcha_activate(self, csrf_token):
117 117 self.post_and_verify_settings({
118 118 'rhodecode_captcha_private_key': '1234567890',
119 119 'rhodecode_captcha_public_key': '1234567890',
120 120 'csrf_token': csrf_token,
121 121 })
122 122
123 123 response = self.app.get(ADMIN_PREFIX + '/register')
124 124 response.mustcontain('captcha')
125 125
126 126 def test_captcha_deactivate(self, csrf_token):
127 127 self.post_and_verify_settings({
128 128 'rhodecode_captcha_private_key': '',
129 129 'rhodecode_captcha_public_key': '1234567890',
130 130 'csrf_token': csrf_token,
131 131 })
132 132
133 133 response = self.app.get(ADMIN_PREFIX + '/register')
134 134 response.mustcontain(no=['captcha'])
135 135
136 136 def test_title_change(self, csrf_token):
137 137 old_title = 'RhodeCode'
138 138 new_title = old_title + '_changed'
139 139
140 140 for new_title in ['Changed', 'Ε»Γ³Ε‚wik', old_title]:
141 141 response = self.post_and_verify_settings({
142 142 'rhodecode_title': new_title,
143 143 'csrf_token': csrf_token,
144 144 })
145 145
146 146 response = response.follow()
147 147 response.mustcontain(
148 148 """<div class="branding">- %s</div>""" % new_title)
149 149
150 150 def post_and_verify_settings(self, settings):
151 151 old_title = 'RhodeCode'
152 152 old_realm = 'RhodeCode authentication'
153 153 params = {
154 154 'rhodecode_title': old_title,
155 155 'rhodecode_realm': old_realm,
156 156 'rhodecode_pre_code': '',
157 157 'rhodecode_post_code': '',
158 158 'rhodecode_captcha_private_key': '',
159 159 'rhodecode_captcha_public_key': '',
160 160 'rhodecode_create_personal_repo_group': False,
161 161 'rhodecode_personal_repo_group_pattern': '${username}',
162 162 }
163 163 params.update(settings)
164 164 response = self.app.post(url('admin_settings_global'), params=params)
165 165
166 166 assert_session_flash(response, 'Updated application settings')
167 167 app_settings = SettingsModel().get_all_settings()
168 168 del settings['csrf_token']
169 169 for key, value in settings.iteritems():
170 170 assert app_settings[key] == value.decode('utf-8')
171 171
172 172 return response
173 173
174 174
175 175 @pytest.mark.usefixtures('autologin_user', 'app')
176 176 class TestAdminSettingsVcs(object):
177 177
178 178 def test_contains_svn_default_patterns(self, app):
179 179 response = app.get(url('admin_settings_vcs'))
180 180 expected_patterns = [
181 181 '/trunk',
182 182 '/branches/*',
183 183 '/tags/*',
184 184 ]
185 185 for pattern in expected_patterns:
186 186 response.mustcontain(pattern)
187 187
188 188 def test_add_new_svn_branch_and_tag_pattern(
189 189 self, app, backend_svn, form_defaults, disable_sql_cache,
190 190 csrf_token):
191 191 form_defaults.update({
192 192 'new_svn_branch': '/exp/branches/*',
193 193 'new_svn_tag': '/important_tags/*',
194 194 'csrf_token': csrf_token,
195 195 })
196 196
197 197 response = app.post(
198 198 url('admin_settings_vcs'), params=form_defaults, status=302)
199 199 response = response.follow()
200 200
201 201 # Expect to find the new values on the page
202 202 response.mustcontain('/exp/branches/*')
203 203 response.mustcontain('/important_tags/*')
204 204
205 205 # Expect that those patterns are used to match branches and tags now
206 206 repo = backend_svn['svn-simple-layout'].scm_instance()
207 207 assert 'exp/branches/exp-sphinx-docs' in repo.branches
208 208 assert 'important_tags/v0.5' in repo.tags
209 209
210 210 def test_add_same_svn_value_twice_shows_an_error_message(
211 211 self, app, form_defaults, csrf_token, settings_util):
212 212 settings_util.create_rhodecode_ui('vcs_svn_branch', '/test')
213 213 settings_util.create_rhodecode_ui('vcs_svn_tag', '/test')
214 214
215 215 response = app.post(
216 216 url('admin_settings_vcs'),
217 217 params={
218 218 'paths_root_path': form_defaults['paths_root_path'],
219 219 'new_svn_branch': '/test',
220 220 'new_svn_tag': '/test',
221 221 'csrf_token': csrf_token,
222 222 },
223 223 status=200)
224 224
225 225 response.mustcontain("Pattern already exists")
226 226 response.mustcontain("Some form inputs contain invalid data.")
227 227
228 228 @pytest.mark.parametrize('section', [
229 229 'vcs_svn_branch',
230 230 'vcs_svn_tag',
231 231 ])
232 232 def test_delete_svn_patterns(
233 233 self, section, app, csrf_token, settings_util):
234 234 setting = settings_util.create_rhodecode_ui(
235 235 section, '/test_delete', cleanup=False)
236 236
237 237 app.post(
238 238 url('admin_settings_vcs'),
239 239 params={
240 240 '_method': 'delete',
241 241 'delete_svn_pattern': setting.ui_id,
242 242 'csrf_token': csrf_token},
243 243 headers={'X-REQUESTED-WITH': 'XMLHttpRequest'})
244 244
245 245 @pytest.mark.parametrize('section', [
246 246 'vcs_svn_branch',
247 247 'vcs_svn_tag',
248 248 ])
249 249 def test_delete_svn_patterns_raises_400_when_no_xhr(
250 250 self, section, app, csrf_token, settings_util):
251 251 setting = settings_util.create_rhodecode_ui(section, '/test_delete')
252 252
253 253 app.post(
254 254 url('admin_settings_vcs'),
255 255 params={
256 256 '_method': 'delete',
257 257 'delete_svn_pattern': setting.ui_id,
258 258 'csrf_token': csrf_token},
259 259 status=400)
260 260
261 261 def test_extensions_hgsubversion(self, app, form_defaults, csrf_token):
262 262 form_defaults.update({
263 263 'csrf_token': csrf_token,
264 264 'extensions_hgsubversion': 'True',
265 265 })
266 266 response = app.post(
267 267 url('admin_settings_vcs'),
268 268 params=form_defaults,
269 269 status=302)
270 270
271 271 response = response.follow()
272 272 extensions_input = (
273 273 '<input id="extensions_hgsubversion" '
274 274 'name="extensions_hgsubversion" type="checkbox" '
275 275 'value="True" checked="checked" />')
276 276 response.mustcontain(extensions_input)
277 277
278 def test_extensions_hgevolve(self, app, form_defaults, csrf_token):
279 form_defaults.update({
280 'csrf_token': csrf_token,
281 'extensions_evolve': 'True',
282 })
283 response = app.post(
284 url('admin_settings_vcs'),
285 params=form_defaults,
286 status=302)
287
288 response = response.follow()
289 extensions_input = (
290 '<input id="extensions_evolve" '
291 'name="extensions_evolve" type="checkbox" '
292 'value="True" checked="checked" />')
293 response.mustcontain(extensions_input)
294
278 295 def test_has_a_section_for_pull_request_settings(self, app):
279 296 response = app.get(url('admin_settings_vcs'))
280 297 response.mustcontain('Pull Request Settings')
281 298
282 299 def test_has_an_input_for_invalidation_of_inline_comments(
283 300 self, app):
284 301 response = app.get(url('admin_settings_vcs'))
285 302 assert_response = AssertResponse(response)
286 303 assert_response.one_element_exists(
287 304 '[name=rhodecode_use_outdated_comments]')
288 305
289 306 @pytest.mark.parametrize('new_value', [True, False])
290 307 def test_allows_to_change_invalidation_of_inline_comments(
291 308 self, app, form_defaults, csrf_token, new_value):
292 309 setting_key = 'use_outdated_comments'
293 310 setting = SettingsModel().create_or_update_setting(
294 311 setting_key, not new_value, 'bool')
295 312 Session().add(setting)
296 313 Session().commit()
297 314
298 315 form_defaults.update({
299 316 'csrf_token': csrf_token,
300 317 'rhodecode_use_outdated_comments': str(new_value),
301 318 })
302 319 response = app.post(
303 320 url('admin_settings_vcs'),
304 321 params=form_defaults,
305 322 status=302)
306 323 response = response.follow()
307 324 setting = SettingsModel().get_setting_by_name(setting_key)
308 325 assert setting.app_settings_value is new_value
309 326
310 327 def test_has_a_section_for_labs_settings_if_enabled(self, app):
311 328 with mock.patch.dict(
312 329 rhodecode.CONFIG, {'labs_settings_active': 'true'}):
313 330 response = self.app.get(url('admin_settings_vcs'))
314 331 response.mustcontain('Labs Settings')
315 332
316 333 def test_has_not_a_section_for_labs_settings_if_disables(self, app):
317 334 with mock.patch.dict(
318 335 rhodecode.CONFIG, {'labs_settings_active': 'false'}):
319 336 response = self.app.get(url('admin_settings_vcs'))
320 337 response.mustcontain(no='Labs Settings')
321 338
322 339 @pytest.mark.parametrize('new_value', [True, False])
323 340 def test_allows_to_change_hg_rebase_merge_strategy(
324 341 self, app, form_defaults, csrf_token, new_value):
325 342 setting_key = 'hg_use_rebase_for_merging'
326 343
327 344 form_defaults.update({
328 345 'csrf_token': csrf_token,
329 346 'rhodecode_' + setting_key: str(new_value),
330 347 })
331 348
332 349 with mock.patch.dict(
333 350 rhodecode.CONFIG, {'labs_settings_active': 'true'}):
334 351 app.post(
335 352 url('admin_settings_vcs'),
336 353 params=form_defaults,
337 354 status=302)
338 355
339 356 setting = SettingsModel().get_setting_by_name(setting_key)
340 357 assert setting.app_settings_value is new_value
341 358
342 359 @pytest.fixture
343 360 def disable_sql_cache(self, request):
344 361 patcher = mock.patch(
345 362 'rhodecode.lib.caching_query.FromCache.process_query')
346 363 request.addfinalizer(patcher.stop)
347 364 patcher.start()
348 365
349 366 @pytest.fixture
350 367 def form_defaults(self):
351 368 from rhodecode.controllers.admin.settings import SettingsController
352 369 controller = SettingsController()
353 370 return controller._form_defaults()
354 371
355 372 # TODO: johbo: What we really want is to checkpoint before a test run and
356 373 # reset the session afterwards.
357 374 @pytest.fixture(scope='class', autouse=True)
358 375 def cleanup_settings(self, request, pylonsapp):
359 376 ui_id = RhodeCodeUi.ui_id
360 377 original_ids = list(
361 378 r.ui_id for r in RhodeCodeUi.query().values(ui_id))
362 379
363 380 @request.addfinalizer
364 381 def cleanup():
365 382 RhodeCodeUi.query().filter(
366 383 ui_id.notin_(original_ids)).delete(False)
367 384
368 385
369 386 @pytest.mark.usefixtures('autologin_user', 'app')
370 387 class TestLabsSettings(object):
371 388 def test_get_settings_page_disabled(self):
372 389 with mock.patch.dict(rhodecode.CONFIG,
373 390 {'labs_settings_active': 'false'}):
374 391 response = self.app.get(url('admin_settings_labs'), status=302)
375 392
376 393 assert response.location.endswith(url('admin_settings'))
377 394
378 395 def test_get_settings_page_enabled(self):
379 396 from rhodecode.controllers.admin import settings
380 397 lab_settings = [
381 398 settings.LabSetting(
382 399 key='rhodecode_bool',
383 400 type='bool',
384 401 group='bool group',
385 402 label='bool label',
386 403 help='bool help'
387 404 ),
388 405 settings.LabSetting(
389 406 key='rhodecode_text',
390 407 type='unicode',
391 408 group='text group',
392 409 label='text label',
393 410 help='text help'
394 411 ),
395 412 ]
396 413 with mock.patch.dict(rhodecode.CONFIG,
397 414 {'labs_settings_active': 'true'}):
398 415 with mock.patch.object(settings, '_LAB_SETTINGS', lab_settings):
399 416 response = self.app.get(url('admin_settings_labs'))
400 417
401 418 assert '<label>bool group:</label>' in response
402 419 assert '<label for="rhodecode_bool">bool label</label>' in response
403 420 assert '<p class="help-block">bool help</p>' in response
404 421 assert 'name="rhodecode_bool" type="checkbox"' in response
405 422
406 423 assert '<label>text group:</label>' in response
407 424 assert '<label for="rhodecode_text">text label</label>' in response
408 425 assert '<p class="help-block">text help</p>' in response
409 426 assert 'name="rhodecode_text" size="60" type="text"' in response
410 427
411 428
412 429 @pytest.mark.usefixtures('app')
413 430 class TestOpenSourceLicenses(object):
414 431
415 432 def _get_url(self):
416 433 return ADMIN_PREFIX + '/settings/open_source'
417 434
418 435 def test_records_are_displayed(self, autologin_user):
419 436 sample_licenses = {
420 437 "python2.7-pytest-2.7.1": {
421 438 "UNKNOWN": None
422 439 },
423 440 "python2.7-Markdown-2.6.2": {
424 441 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
425 442 }
426 443 }
427 444 read_licenses_patch = mock.patch(
428 445 'rhodecode.apps.admin.views.open_source_licenses.read_opensource_licenses',
429 446 return_value=sample_licenses)
430 447 with read_licenses_patch:
431 448 response = self.app.get(self._get_url(), status=200)
432 449
433 450 assert_response = AssertResponse(response)
434 451 assert_response.element_contains(
435 452 '.panel-heading', 'Licenses of Third Party Packages')
436 453 for name in sample_licenses:
437 454 response.mustcontain(name)
438 455 for license in sample_licenses[name]:
439 456 assert_response.element_contains('.panel-body', license)
440 457
441 458 def test_records_can_be_read(self, autologin_user):
442 459 response = self.app.get(self._get_url(), status=200)
443 460 assert_response = AssertResponse(response)
444 461 assert_response.element_contains(
445 462 '.panel-heading', 'Licenses of Third Party Packages')
446 463
447 464 def test_forbidden_when_normal_user(self, autologin_regular_user):
448 465 self.app.get(self._get_url(), status=403)
449 466
450 467
451 468 @pytest.mark.usefixtures('app')
452 469 class TestUserSessions(object):
453 470
454 471 def _get_url(self, name='admin_settings_sessions'):
455 472 return {
456 473 'admin_settings_sessions': ADMIN_PREFIX + '/settings/sessions',
457 474 'admin_settings_sessions_cleanup': ADMIN_PREFIX + '/settings/sessions/cleanup'
458 475 }[name]
459 476
460 477 def test_forbidden_when_normal_user(self, autologin_regular_user):
461 478 self.app.get(self._get_url(), status=403)
462 479
463 480 def test_show_sessions_page(self, autologin_user):
464 481 response = self.app.get(self._get_url(), status=200)
465 482 response.mustcontain('file')
466 483
467 484 def test_cleanup_old_sessions(self, autologin_user, csrf_token):
468 485
469 486 post_data = {
470 487 'csrf_token': csrf_token,
471 488 'expire_days': '60'
472 489 }
473 490 response = self.app.post(
474 491 self._get_url('admin_settings_sessions_cleanup'), params=post_data,
475 492 status=302)
476 493 assert_session_flash(response, 'Cleaned up old sessions')
477 494
478 495
479 496 @pytest.mark.usefixtures('app')
480 497 class TestAdminSystemInfo(object):
481 498 def _get_url(self, name='admin_settings_system'):
482 499 return {
483 500 'admin_settings_system': ADMIN_PREFIX + '/settings/system',
484 501 'admin_settings_system_update': ADMIN_PREFIX + '/settings/system/updates',
485 502 }[name]
486 503
487 504 def test_forbidden_when_normal_user(self, autologin_regular_user):
488 505 self.app.get(self._get_url(), status=403)
489 506
490 507 def test_system_info_page(self, autologin_user):
491 508 response = self.app.get(self._get_url())
492 509 response.mustcontain('RhodeCode Community Edition, version {}'.format(
493 510 rhodecode.__version__))
494 511
495 512 def test_system_update_new_version(self, autologin_user):
496 513 update_data = {
497 514 'versions': [
498 515 {
499 516 'version': '100.3.1415926535',
500 517 'general': 'The latest version we are ever going to ship'
501 518 },
502 519 {
503 520 'version': '0.0.0',
504 521 'general': 'The first version we ever shipped'
505 522 }
506 523 ]
507 524 }
508 525 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
509 526 response = self.app.get(self._get_url('admin_settings_system_update'))
510 527 response.mustcontain('A <b>new version</b> is available')
511 528
512 529 def test_system_update_nothing_new(self, autologin_user):
513 530 update_data = {
514 531 'versions': [
515 532 {
516 533 'version': '0.0.0',
517 534 'general': 'The first version we ever shipped'
518 535 }
519 536 ]
520 537 }
521 538 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
522 539 response = self.app.get(self._get_url('admin_settings_system_update'))
523 540 response.mustcontain(
524 541 'You already have the <b>latest</b> stable version.')
525 542
526 543 def test_system_update_bad_response(self, autologin_user):
527 544 with mock.patch(UPDATE_DATA_QUALNAME, side_effect=ValueError('foo')):
528 545 response = self.app.get(self._get_url('admin_settings_system_update'))
529 546 response.mustcontain(
530 547 'Bad data sent from update server')
531 548
532 549
533 550 @pytest.mark.usefixtures("app")
534 551 class TestAdminSettingsIssueTracker(object):
535 552 RC_PREFIX = 'rhodecode_'
536 553 SHORT_PATTERN_KEY = 'issuetracker_pat_'
537 554 PATTERN_KEY = RC_PREFIX + SHORT_PATTERN_KEY
538 555
539 556 def test_issuetracker_index(self, autologin_user):
540 557 response = self.app.get(url('admin_settings_issuetracker'))
541 558 assert response.status_code == 200
542 559
543 560 def test_add_empty_issuetracker_pattern(
544 561 self, request, autologin_user, csrf_token):
545 562 post_url = url('admin_settings_issuetracker_save')
546 563 post_data = {
547 564 'csrf_token': csrf_token
548 565 }
549 566 self.app.post(post_url, post_data, status=302)
550 567
551 568 def test_add_issuetracker_pattern(
552 569 self, request, autologin_user, csrf_token):
553 570 pattern = 'issuetracker_pat'
554 571 another_pattern = pattern+'1'
555 572 post_url = url('admin_settings_issuetracker_save')
556 573 post_data = {
557 574 'new_pattern_pattern_0': pattern,
558 575 'new_pattern_url_0': 'url',
559 576 'new_pattern_prefix_0': 'prefix',
560 577 'new_pattern_description_0': 'description',
561 578 'new_pattern_pattern_1': another_pattern,
562 579 'new_pattern_url_1': 'url1',
563 580 'new_pattern_prefix_1': 'prefix1',
564 581 'new_pattern_description_1': 'description1',
565 582 'csrf_token': csrf_token
566 583 }
567 584 self.app.post(post_url, post_data, status=302)
568 585 settings = SettingsModel().get_all_settings()
569 586 self.uid = md5(pattern)
570 587 assert settings[self.PATTERN_KEY+self.uid] == pattern
571 588 self.another_uid = md5(another_pattern)
572 589 assert settings[self.PATTERN_KEY+self.another_uid] == another_pattern
573 590
574 591 @request.addfinalizer
575 592 def cleanup():
576 593 defaults = SettingsModel().get_all_settings()
577 594
578 595 entries = [name for name in defaults if (
579 596 (self.uid in name) or (self.another_uid) in name)]
580 597 start = len(self.RC_PREFIX)
581 598 for del_key in entries:
582 599 # TODO: anderson: get_by_name needs name without prefix
583 600 entry = SettingsModel().get_setting_by_name(del_key[start:])
584 601 Session().delete(entry)
585 602
586 603 Session().commit()
587 604
588 605 def test_edit_issuetracker_pattern(
589 606 self, autologin_user, backend, csrf_token, request):
590 607 old_pattern = 'issuetracker_pat'
591 608 old_uid = md5(old_pattern)
592 609 pattern = 'issuetracker_pat_new'
593 610 self.new_uid = md5(pattern)
594 611
595 612 SettingsModel().create_or_update_setting(
596 613 self.SHORT_PATTERN_KEY+old_uid, old_pattern, 'unicode')
597 614
598 615 post_url = url('admin_settings_issuetracker_save')
599 616 post_data = {
600 617 'new_pattern_pattern_0': pattern,
601 618 'new_pattern_url_0': 'url',
602 619 'new_pattern_prefix_0': 'prefix',
603 620 'new_pattern_description_0': 'description',
604 621 'uid': old_uid,
605 622 'csrf_token': csrf_token
606 623 }
607 624 self.app.post(post_url, post_data, status=302)
608 625 settings = SettingsModel().get_all_settings()
609 626 assert settings[self.PATTERN_KEY+self.new_uid] == pattern
610 627 assert self.PATTERN_KEY+old_uid not in settings
611 628
612 629 @request.addfinalizer
613 630 def cleanup():
614 631 IssueTrackerSettingsModel().delete_entries(self.new_uid)
615 632
616 633 def test_replace_issuetracker_pattern_description(
617 634 self, autologin_user, csrf_token, request, settings_util):
618 635 prefix = 'issuetracker'
619 636 pattern = 'issuetracker_pat'
620 637 self.uid = md5(pattern)
621 638 pattern_key = '_'.join([prefix, 'pat', self.uid])
622 639 rc_pattern_key = '_'.join(['rhodecode', pattern_key])
623 640 desc_key = '_'.join([prefix, 'desc', self.uid])
624 641 rc_desc_key = '_'.join(['rhodecode', desc_key])
625 642 new_description = 'new_description'
626 643
627 644 settings_util.create_rhodecode_setting(
628 645 pattern_key, pattern, 'unicode', cleanup=False)
629 646 settings_util.create_rhodecode_setting(
630 647 desc_key, 'old description', 'unicode', cleanup=False)
631 648
632 649 post_url = url('admin_settings_issuetracker_save')
633 650 post_data = {
634 651 'new_pattern_pattern_0': pattern,
635 652 'new_pattern_url_0': 'url',
636 653 'new_pattern_prefix_0': 'prefix',
637 654 'new_pattern_description_0': new_description,
638 655 'uid': self.uid,
639 656 'csrf_token': csrf_token
640 657 }
641 658 self.app.post(post_url, post_data, status=302)
642 659 settings = SettingsModel().get_all_settings()
643 660 assert settings[rc_pattern_key] == pattern
644 661 assert settings[rc_desc_key] == new_description
645 662
646 663 @request.addfinalizer
647 664 def cleanup():
648 665 IssueTrackerSettingsModel().delete_entries(self.uid)
649 666
650 667 def test_delete_issuetracker_pattern(
651 668 self, autologin_user, backend, csrf_token, settings_util):
652 669 pattern = 'issuetracker_pat'
653 670 uid = md5(pattern)
654 671 settings_util.create_rhodecode_setting(
655 672 self.SHORT_PATTERN_KEY+uid, pattern, 'unicode', cleanup=False)
656 673
657 674 post_url = url('admin_issuetracker_delete')
658 675 post_data = {
659 676 '_method': 'delete',
660 677 'uid': uid,
661 678 'csrf_token': csrf_token
662 679 }
663 680 self.app.post(post_url, post_data, status=302)
664 681 settings = SettingsModel().get_all_settings()
665 682 assert 'rhodecode_%s%s' % (self.SHORT_PATTERN_KEY, uid) not in settings
@@ -1,1061 +1,1069 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 import mock
22 22 import pytest
23 23
24 24 from rhodecode.lib.utils2 import str2bool
25 25 from rhodecode.model.meta import Session
26 26 from rhodecode.model.settings import VcsSettingsModel, UiSetting
27 27
28 28
29 29 HOOKS_FORM_DATA = {
30 30 'hooks_changegroup_repo_size': True,
31 31 'hooks_changegroup_push_logger': True,
32 32 'hooks_outgoing_pull_logger': True
33 33 }
34 34
35 35 SVN_FORM_DATA = {
36 36 'new_svn_branch': 'test-branch',
37 37 'new_svn_tag': 'test-tag'
38 38 }
39 39
40 40 GENERAL_FORM_DATA = {
41 41 'rhodecode_pr_merge_enabled': True,
42 42 'rhodecode_use_outdated_comments': True,
43 43 'rhodecode_hg_use_rebase_for_merging': True,
44 44 }
45 45
46 46
47 47 class TestInheritGlobalSettingsProperty(object):
48 48 def test_get_raises_exception_when_repository_not_specified(self):
49 49 model = VcsSettingsModel()
50 50 with pytest.raises(Exception) as exc_info:
51 51 model.inherit_global_settings
52 52 assert exc_info.value.message == 'Repository is not specified'
53 53
54 54 def test_true_is_returned_when_value_is_not_found(self, repo_stub):
55 55 model = VcsSettingsModel(repo=repo_stub.repo_name)
56 56 assert model.inherit_global_settings is True
57 57
58 58 def test_value_is_returned(self, repo_stub, settings_util):
59 59 model = VcsSettingsModel(repo=repo_stub.repo_name)
60 60 settings_util.create_repo_rhodecode_setting(
61 61 repo_stub, VcsSettingsModel.INHERIT_SETTINGS, False, 'bool')
62 62 assert model.inherit_global_settings is False
63 63
64 64 def test_value_is_set(self, repo_stub):
65 65 model = VcsSettingsModel(repo=repo_stub.repo_name)
66 66 model.inherit_global_settings = False
67 67 setting = model.repo_settings.get_setting_by_name(
68 68 VcsSettingsModel.INHERIT_SETTINGS)
69 69 try:
70 70 assert setting.app_settings_type == 'bool'
71 71 assert setting.app_settings_value is False
72 72 finally:
73 73 Session().delete(setting)
74 74 Session().commit()
75 75
76 76 def test_set_raises_exception_when_repository_not_specified(self):
77 77 model = VcsSettingsModel()
78 78 with pytest.raises(Exception) as exc_info:
79 79 model.inherit_global_settings = False
80 80 assert exc_info.value.message == 'Repository is not specified'
81 81
82 82
83 83 class TestVcsSettingsModel(object):
84 84 def test_global_svn_branch_patterns(self):
85 85 model = VcsSettingsModel()
86 86 expected_result = {'test': 'test'}
87 87 with mock.patch.object(model, 'global_settings') as settings_mock:
88 88 get_settings = settings_mock.get_ui_by_section
89 89 get_settings.return_value = expected_result
90 90 settings_mock.return_value = expected_result
91 91 result = model.get_global_svn_branch_patterns()
92 92
93 93 get_settings.assert_called_once_with(model.SVN_BRANCH_SECTION)
94 94 assert expected_result == result
95 95
96 96 def test_repo_svn_branch_patterns(self):
97 97 model = VcsSettingsModel()
98 98 expected_result = {'test': 'test'}
99 99 with mock.patch.object(model, 'repo_settings') as settings_mock:
100 100 get_settings = settings_mock.get_ui_by_section
101 101 get_settings.return_value = expected_result
102 102 settings_mock.return_value = expected_result
103 103 result = model.get_repo_svn_branch_patterns()
104 104
105 105 get_settings.assert_called_once_with(model.SVN_BRANCH_SECTION)
106 106 assert expected_result == result
107 107
108 108 def test_repo_svn_branch_patterns_raises_exception_when_repo_is_not_set(
109 109 self):
110 110 model = VcsSettingsModel()
111 111 with pytest.raises(Exception) as exc_info:
112 112 model.get_repo_svn_branch_patterns()
113 113 assert exc_info.value.message == 'Repository is not specified'
114 114
115 115 def test_global_svn_tag_patterns(self):
116 116 model = VcsSettingsModel()
117 117 expected_result = {'test': 'test'}
118 118 with mock.patch.object(model, 'global_settings') as settings_mock:
119 119 get_settings = settings_mock.get_ui_by_section
120 120 get_settings.return_value = expected_result
121 121 settings_mock.return_value = expected_result
122 122 result = model.get_global_svn_tag_patterns()
123 123
124 124 get_settings.assert_called_once_with(model.SVN_TAG_SECTION)
125 125 assert expected_result == result
126 126
127 127 def test_repo_svn_tag_patterns(self):
128 128 model = VcsSettingsModel()
129 129 expected_result = {'test': 'test'}
130 130 with mock.patch.object(model, 'repo_settings') as settings_mock:
131 131 get_settings = settings_mock.get_ui_by_section
132 132 get_settings.return_value = expected_result
133 133 settings_mock.return_value = expected_result
134 134 result = model.get_repo_svn_tag_patterns()
135 135
136 136 get_settings.assert_called_once_with(model.SVN_TAG_SECTION)
137 137 assert expected_result == result
138 138
139 139 def test_repo_svn_tag_patterns_raises_exception_when_repo_is_not_set(self):
140 140 model = VcsSettingsModel()
141 141 with pytest.raises(Exception) as exc_info:
142 142 model.get_repo_svn_tag_patterns()
143 143 assert exc_info.value.message == 'Repository is not specified'
144 144
145 145 def test_get_global_settings(self):
146 146 expected_result = {'test': 'test'}
147 147 model = VcsSettingsModel()
148 148 with mock.patch.object(model, '_collect_all_settings') as collect_mock:
149 149 collect_mock.return_value = expected_result
150 150 result = model.get_global_settings()
151 151
152 152 collect_mock.assert_called_once_with(global_=True)
153 153 assert result == expected_result
154 154
155 155 def test_get_repo_settings(self, repo_stub):
156 156 model = VcsSettingsModel(repo=repo_stub.repo_name)
157 157 expected_result = {'test': 'test'}
158 158 with mock.patch.object(model, '_collect_all_settings') as collect_mock:
159 159 collect_mock.return_value = expected_result
160 160 result = model.get_repo_settings()
161 161
162 162 collect_mock.assert_called_once_with(global_=False)
163 163 assert result == expected_result
164 164
165 165 @pytest.mark.parametrize('settings, global_', [
166 166 ('global_settings', True),
167 167 ('repo_settings', False)
168 168 ])
169 169 def test_collect_all_settings(self, settings, global_):
170 170 model = VcsSettingsModel()
171 171 result_mock = self._mock_result()
172 172
173 173 settings_patch = mock.patch.object(model, settings)
174 174 with settings_patch as settings_mock:
175 175 settings_mock.get_ui_by_section_and_key.return_value = result_mock
176 176 settings_mock.get_setting_by_name.return_value = result_mock
177 177 result = model._collect_all_settings(global_=global_)
178 178
179 179 ui_settings = model.HG_SETTINGS + model.GIT_SETTINGS + model.HOOKS_SETTINGS
180 180 self._assert_get_settings_calls(
181 181 settings_mock, ui_settings, model.GENERAL_SETTINGS)
182 182 self._assert_collect_all_settings_result(
183 183 ui_settings, model.GENERAL_SETTINGS, result)
184 184
185 185 @pytest.mark.parametrize('settings, global_', [
186 186 ('global_settings', True),
187 187 ('repo_settings', False)
188 188 ])
189 189 def test_collect_all_settings_without_empty_value(self, settings, global_):
190 190 model = VcsSettingsModel()
191 191
192 192 settings_patch = mock.patch.object(model, settings)
193 193 with settings_patch as settings_mock:
194 194 settings_mock.get_ui_by_section_and_key.return_value = None
195 195 settings_mock.get_setting_by_name.return_value = None
196 196 result = model._collect_all_settings(global_=global_)
197 197
198 198 assert result == {}
199 199
200 200 def _mock_result(self):
201 201 result_mock = mock.Mock()
202 202 result_mock.ui_value = 'ui_value'
203 203 result_mock.ui_active = True
204 204 result_mock.app_settings_value = 'setting_value'
205 205 return result_mock
206 206
207 207 def _assert_get_settings_calls(
208 208 self, settings_mock, ui_settings, general_settings):
209 209 assert (
210 210 settings_mock.get_ui_by_section_and_key.call_count ==
211 211 len(ui_settings))
212 212 assert (
213 213 settings_mock.get_setting_by_name.call_count ==
214 214 len(general_settings))
215 215
216 216 for section, key in ui_settings:
217 217 expected_call = mock.call(section, key)
218 218 assert (
219 219 expected_call in
220 220 settings_mock.get_ui_by_section_and_key.call_args_list)
221 221
222 222 for name in general_settings:
223 223 expected_call = mock.call(name)
224 224 assert (
225 225 expected_call in
226 226 settings_mock.get_setting_by_name.call_args_list)
227 227
228 228 def _assert_collect_all_settings_result(
229 229 self, ui_settings, general_settings, result):
230 230 expected_result = {}
231 231 for section, key in ui_settings:
232 232 key = '{}_{}'.format(section, key.replace('.', '_'))
233 233
234 234 if section in ('extensions', 'hooks'):
235 235 value = True
236 236 elif key in ['vcs_git_lfs_enabled']:
237 237 value = True
238 238 else:
239 239 value = 'ui_value'
240 240 expected_result[key] = value
241 241
242 242 for name in general_settings:
243 243 key = 'rhodecode_' + name
244 244 expected_result[key] = 'setting_value'
245 245
246 246 assert expected_result == result
247 247
248 248
249 249 class TestCreateOrUpdateRepoHookSettings(object):
250 250 def test_create_when_no_repo_object_found(self, repo_stub):
251 251 model = VcsSettingsModel(repo=repo_stub.repo_name)
252 252
253 253 self._create_settings(model, HOOKS_FORM_DATA)
254 254
255 255 cleanup = []
256 256 try:
257 257 for section, key in model.HOOKS_SETTINGS:
258 258 ui = model.repo_settings.get_ui_by_section_and_key(
259 259 section, key)
260 260 assert ui.ui_active is True
261 261 cleanup.append(ui)
262 262 finally:
263 263 for ui in cleanup:
264 264 Session().delete(ui)
265 265 Session().commit()
266 266
267 267 def test_create_raises_exception_when_data_incomplete(self, repo_stub):
268 268 model = VcsSettingsModel(repo=repo_stub.repo_name)
269 269
270 270 deleted_key = 'hooks_changegroup_repo_size'
271 271 data = HOOKS_FORM_DATA.copy()
272 272 data.pop(deleted_key)
273 273
274 274 with pytest.raises(ValueError) as exc_info:
275 275 model.create_or_update_repo_hook_settings(data)
276 276 assert (
277 277 exc_info.value.message ==
278 278 'The given data does not contain {} key'.format(deleted_key))
279 279
280 280 def test_update_when_repo_object_found(self, repo_stub, settings_util):
281 281 model = VcsSettingsModel(repo=repo_stub.repo_name)
282 282 for section, key in model.HOOKS_SETTINGS:
283 283 settings_util.create_repo_rhodecode_ui(
284 284 repo_stub, section, None, key=key, active=False)
285 285 model.create_or_update_repo_hook_settings(HOOKS_FORM_DATA)
286 286 for section, key in model.HOOKS_SETTINGS:
287 287 ui = model.repo_settings.get_ui_by_section_and_key(section, key)
288 288 assert ui.ui_active is True
289 289
290 290 def _create_settings(self, model, data):
291 291 global_patch = mock.patch.object(model, 'global_settings')
292 292 global_setting = mock.Mock()
293 293 global_setting.ui_value = 'Test value'
294 294 with global_patch as global_mock:
295 295 global_mock.get_ui_by_section_and_key.return_value = global_setting
296 296 model.create_or_update_repo_hook_settings(HOOKS_FORM_DATA)
297 297
298 298
299 299 class TestUpdateGlobalHookSettings(object):
300 300 def test_update_raises_exception_when_data_incomplete(self):
301 301 model = VcsSettingsModel()
302 302
303 303 deleted_key = 'hooks_changegroup_repo_size'
304 304 data = HOOKS_FORM_DATA.copy()
305 305 data.pop(deleted_key)
306 306
307 307 with pytest.raises(ValueError) as exc_info:
308 308 model.update_global_hook_settings(data)
309 309 assert (
310 310 exc_info.value.message ==
311 311 'The given data does not contain {} key'.format(deleted_key))
312 312
313 313 def test_update_global_hook_settings(self, settings_util):
314 314 model = VcsSettingsModel()
315 315 setting_mock = mock.MagicMock()
316 316 setting_mock.ui_active = False
317 317 get_settings_patcher = mock.patch.object(
318 318 model.global_settings, 'get_ui_by_section_and_key',
319 319 return_value=setting_mock)
320 320 session_patcher = mock.patch('rhodecode.model.settings.Session')
321 321 with get_settings_patcher as get_settings_mock, session_patcher:
322 322 model.update_global_hook_settings(HOOKS_FORM_DATA)
323 323 assert setting_mock.ui_active is True
324 324 assert get_settings_mock.call_count == 3
325 325
326 326
327 327 class TestCreateOrUpdateRepoGeneralSettings(object):
328 328 def test_calls_create_or_update_general_settings(self, repo_stub):
329 329 model = VcsSettingsModel(repo=repo_stub.repo_name)
330 330 create_patch = mock.patch.object(
331 331 model, '_create_or_update_general_settings')
332 332 with create_patch as create_mock:
333 333 model.create_or_update_repo_pr_settings(GENERAL_FORM_DATA)
334 334 create_mock.assert_called_once_with(
335 335 model.repo_settings, GENERAL_FORM_DATA)
336 336
337 337 def test_raises_exception_when_repository_is_not_specified(self):
338 338 model = VcsSettingsModel()
339 339 with pytest.raises(Exception) as exc_info:
340 340 model.create_or_update_repo_pr_settings(GENERAL_FORM_DATA)
341 341 assert exc_info.value.message == 'Repository is not specified'
342 342
343 343
344 344 class TestCreateOrUpdatGlobalGeneralSettings(object):
345 345 def test_calls_create_or_update_general_settings(self):
346 346 model = VcsSettingsModel()
347 347 create_patch = mock.patch.object(
348 348 model, '_create_or_update_general_settings')
349 349 with create_patch as create_mock:
350 350 model.create_or_update_global_pr_settings(GENERAL_FORM_DATA)
351 351 create_mock.assert_called_once_with(
352 352 model.global_settings, GENERAL_FORM_DATA)
353 353
354 354
355 355 class TestCreateOrUpdateGeneralSettings(object):
356 356 def test_create_when_no_repo_settings_found(self, repo_stub):
357 357 model = VcsSettingsModel(repo=repo_stub.repo_name)
358 358 model._create_or_update_general_settings(
359 359 model.repo_settings, GENERAL_FORM_DATA)
360 360
361 361 cleanup = []
362 362 try:
363 363 for name in model.GENERAL_SETTINGS:
364 364 setting = model.repo_settings.get_setting_by_name(name)
365 365 assert setting.app_settings_value is True
366 366 cleanup.append(setting)
367 367 finally:
368 368 for setting in cleanup:
369 369 Session().delete(setting)
370 370 Session().commit()
371 371
372 372 def test_create_raises_exception_when_data_incomplete(self, repo_stub):
373 373 model = VcsSettingsModel(repo=repo_stub.repo_name)
374 374
375 375 deleted_key = 'rhodecode_pr_merge_enabled'
376 376 data = GENERAL_FORM_DATA.copy()
377 377 data.pop(deleted_key)
378 378
379 379 with pytest.raises(ValueError) as exc_info:
380 380 model._create_or_update_general_settings(model.repo_settings, data)
381 381 assert (
382 382 exc_info.value.message ==
383 383 'The given data does not contain {} key'.format(deleted_key))
384 384
385 385 def test_update_when_repo_setting_found(self, repo_stub, settings_util):
386 386 model = VcsSettingsModel(repo=repo_stub.repo_name)
387 387 for name in model.GENERAL_SETTINGS:
388 388 settings_util.create_repo_rhodecode_setting(
389 389 repo_stub, name, False, 'bool')
390 390
391 391 model._create_or_update_general_settings(
392 392 model.repo_settings, GENERAL_FORM_DATA)
393 393
394 394 for name in model.GENERAL_SETTINGS:
395 395 setting = model.repo_settings.get_setting_by_name(name)
396 396 assert setting.app_settings_value is True
397 397
398 398
399 399 class TestCreateRepoSvnSettings(object):
400 400 def test_calls_create_svn_settings(self, repo_stub):
401 401 model = VcsSettingsModel(repo=repo_stub.repo_name)
402 402 with mock.patch.object(model, '_create_svn_settings') as create_mock:
403 403 model.create_repo_svn_settings(SVN_FORM_DATA)
404 404 create_mock.assert_called_once_with(model.repo_settings, SVN_FORM_DATA)
405 405
406 406 def test_raises_exception_when_repository_is_not_specified(self):
407 407 model = VcsSettingsModel()
408 408 with pytest.raises(Exception) as exc_info:
409 409 model.create_repo_svn_settings(SVN_FORM_DATA)
410 410 assert exc_info.value.message == 'Repository is not specified'
411 411
412 412
413 413 class TestCreateSvnSettings(object):
414 414 def test_create(self, repo_stub):
415 415 model = VcsSettingsModel(repo=repo_stub.repo_name)
416 416 model._create_svn_settings(model.repo_settings, SVN_FORM_DATA)
417 417 Session().commit()
418 418
419 419 branch_ui = model.repo_settings.get_ui_by_section(
420 420 model.SVN_BRANCH_SECTION)
421 421 tag_ui = model.repo_settings.get_ui_by_section(
422 422 model.SVN_TAG_SECTION)
423 423
424 424 try:
425 425 assert len(branch_ui) == 1
426 426 assert len(tag_ui) == 1
427 427 finally:
428 428 Session().delete(branch_ui[0])
429 429 Session().delete(tag_ui[0])
430 430 Session().commit()
431 431
432 432 def test_create_tag(self, repo_stub):
433 433 model = VcsSettingsModel(repo=repo_stub.repo_name)
434 434 data = SVN_FORM_DATA.copy()
435 435 data.pop('new_svn_branch')
436 436 model._create_svn_settings(model.repo_settings, data)
437 437 Session().commit()
438 438
439 439 branch_ui = model.repo_settings.get_ui_by_section(
440 440 model.SVN_BRANCH_SECTION)
441 441 tag_ui = model.repo_settings.get_ui_by_section(
442 442 model.SVN_TAG_SECTION)
443 443
444 444 try:
445 445 assert len(branch_ui) == 0
446 446 assert len(tag_ui) == 1
447 447 finally:
448 448 Session().delete(tag_ui[0])
449 449 Session().commit()
450 450
451 451 def test_create_nothing_when_no_svn_settings_specified(self, repo_stub):
452 452 model = VcsSettingsModel(repo=repo_stub.repo_name)
453 453 model._create_svn_settings(model.repo_settings, {})
454 454 Session().commit()
455 455
456 456 branch_ui = model.repo_settings.get_ui_by_section(
457 457 model.SVN_BRANCH_SECTION)
458 458 tag_ui = model.repo_settings.get_ui_by_section(
459 459 model.SVN_TAG_SECTION)
460 460
461 461 assert len(branch_ui) == 0
462 462 assert len(tag_ui) == 0
463 463
464 464 def test_create_nothing_when_empty_settings_specified(self, repo_stub):
465 465 model = VcsSettingsModel(repo=repo_stub.repo_name)
466 466 data = {
467 467 'new_svn_branch': '',
468 468 'new_svn_tag': ''
469 469 }
470 470 model._create_svn_settings(model.repo_settings, data)
471 471 Session().commit()
472 472
473 473 branch_ui = model.repo_settings.get_ui_by_section(
474 474 model.SVN_BRANCH_SECTION)
475 475 tag_ui = model.repo_settings.get_ui_by_section(
476 476 model.SVN_TAG_SECTION)
477 477
478 478 assert len(branch_ui) == 0
479 479 assert len(tag_ui) == 0
480 480
481 481
482 482 class TestCreateOrUpdateUi(object):
483 483 def test_create(self, repo_stub):
484 484 model = VcsSettingsModel(repo=repo_stub.repo_name)
485 485 model._create_or_update_ui(
486 486 model.repo_settings, 'test-section', 'test-key', active=False,
487 487 value='False')
488 488 Session().commit()
489 489
490 490 created_ui = model.repo_settings.get_ui_by_section_and_key(
491 491 'test-section', 'test-key')
492 492
493 493 try:
494 494 assert created_ui.ui_active is False
495 495 assert str2bool(created_ui.ui_value) is False
496 496 finally:
497 497 Session().delete(created_ui)
498 498 Session().commit()
499 499
500 500 def test_update(self, repo_stub, settings_util):
501 501 model = VcsSettingsModel(repo=repo_stub.repo_name)
502 502
503 largefiles, phases = model.HG_SETTINGS
503 largefiles, phases, evolve = model.HG_SETTINGS
504
504 505 section = 'test-section'
505 506 key = 'test-key'
506 507 settings_util.create_repo_rhodecode_ui(
507 508 repo_stub, section, 'True', key=key, active=True)
508 509
509 510 model._create_or_update_ui(
510 511 model.repo_settings, section, key, active=False, value='False')
511 512 Session().commit()
512 513
513 514 created_ui = model.repo_settings.get_ui_by_section_and_key(
514 515 section, key)
515 516 assert created_ui.ui_active is False
516 517 assert str2bool(created_ui.ui_value) is False
517 518
518 519
519 520 class TestCreateOrUpdateRepoHgSettings(object):
520 521 FORM_DATA = {
521 522 'extensions_largefiles': False,
523 'extensions_evolve': False,
522 524 'phases_publish': False
523 525 }
524 526
525 527 def test_creates_repo_hg_settings_when_data_is_correct(self, repo_stub):
526 528 model = VcsSettingsModel(repo=repo_stub.repo_name)
527 529 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
528 530 model.create_or_update_repo_hg_settings(self.FORM_DATA)
529 531 expected_calls = [
530 532 mock.call(model.repo_settings, 'extensions', 'largefiles',
531 533 active=False, value=''),
534 mock.call(model.repo_settings, 'extensions', 'evolve',
535 active=False, value=''),
532 536 mock.call(model.repo_settings, 'phases', 'publish', value='False'),
533 537 ]
534 538 assert expected_calls == create_mock.call_args_list
535 539
536 540 @pytest.mark.parametrize('field_to_remove', FORM_DATA.keys())
537 541 def test_key_is_not_found(self, repo_stub, field_to_remove):
538 542 model = VcsSettingsModel(repo=repo_stub.repo_name)
539 543 data = self.FORM_DATA.copy()
540 544 data.pop(field_to_remove)
541 545 with pytest.raises(ValueError) as exc_info:
542 546 model.create_or_update_repo_hg_settings(data)
543 547 expected_message = 'The given data does not contain {} key'.format(
544 548 field_to_remove)
545 549 assert exc_info.value.message == expected_message
546 550
547 551 def test_create_raises_exception_when_repository_not_specified(self):
548 552 model = VcsSettingsModel()
549 553 with pytest.raises(Exception) as exc_info:
550 554 model.create_or_update_repo_hg_settings(self.FORM_DATA)
551 555 assert exc_info.value.message == 'Repository is not specified'
552 556
553 557
554 558 class TestUpdateGlobalSslSetting(object):
555 559 def test_updates_global_hg_settings(self):
556 560 model = VcsSettingsModel()
557 561 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
558 562 model.update_global_ssl_setting('False')
559 563 create_mock.assert_called_once_with(
560 564 model.global_settings, 'web', 'push_ssl', value='False')
561 565
562 566
563 567 class TestUpdateGlobalPathSetting(object):
564 568 def test_updates_global_path_settings(self):
565 569 model = VcsSettingsModel()
566 570 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
567 571 model.update_global_path_setting('False')
568 572 create_mock.assert_called_once_with(
569 573 model.global_settings, 'paths', '/', value='False')
570 574
571 575
572 576 class TestCreateOrUpdateGlobalHgSettings(object):
573 577 FORM_DATA = {
574 578 'extensions_largefiles': False,
575 579 'largefiles_usercache': '/example/largefiles-store',
576 580 'phases_publish': False,
577 'extensions_hgsubversion': False
581 'extensions_hgsubversion': False,
582 'extensions_evolve': False
578 583 }
579 584
580 585 def test_creates_repo_hg_settings_when_data_is_correct(self):
581 586 model = VcsSettingsModel()
582 587 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
583 588 model.create_or_update_global_hg_settings(self.FORM_DATA)
584 589 expected_calls = [
585 590 mock.call(model.global_settings, 'extensions', 'largefiles',
586 591 active=False, value=''),
587 592 mock.call(model.global_settings, 'largefiles', 'usercache',
588 593 value='/example/largefiles-store'),
589 594 mock.call(model.global_settings, 'phases', 'publish',
590 595 value='False'),
591 596 mock.call(model.global_settings, 'extensions', 'hgsubversion',
592 active=False)
597 active=False),
598 mock.call(model.global_settings, 'extensions', 'evolve',
599 active=False, value='')
593 600 ]
594 601 assert expected_calls == create_mock.call_args_list
595 602
596 603 @pytest.mark.parametrize('field_to_remove', FORM_DATA.keys())
597 604 def test_key_is_not_found(self, repo_stub, field_to_remove):
598 605 model = VcsSettingsModel(repo=repo_stub.repo_name)
599 606 data = self.FORM_DATA.copy()
600 607 data.pop(field_to_remove)
601 608 with pytest.raises(Exception) as exc_info:
602 609 model.create_or_update_global_hg_settings(data)
603 610 expected_message = 'The given data does not contain {} key'.format(
604 611 field_to_remove)
605 612 assert exc_info.value.message == expected_message
606 613
607 614
608 615 class TestCreateOrUpdateGlobalGitSettings(object):
609 616 FORM_DATA = {
610 617 'vcs_git_lfs_enabled': False,
611 618 'vcs_git_lfs_store_location': '/example/lfs-store',
612 619 }
613 620
614 621 def test_creates_repo_hg_settings_when_data_is_correct(self):
615 622 model = VcsSettingsModel()
616 623 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
617 624 model.create_or_update_global_git_settings(self.FORM_DATA)
618 625 expected_calls = [
619 626 mock.call(model.global_settings, 'vcs_git_lfs', 'enabled',
620 627 active=False, value=False),
621 628 mock.call(model.global_settings, 'vcs_git_lfs', 'store_location',
622 629 value='/example/lfs-store'),
623 630 ]
624 631 assert expected_calls == create_mock.call_args_list
625 632
626 633
627 634 class TestDeleteRepoSvnPattern(object):
628 635 def test_success_when_repo_is_set(self, backend_svn):
629 636 repo_name = backend_svn.repo_name
630 637 model = VcsSettingsModel(repo=repo_name)
631 638 delete_ui_patch = mock.patch.object(model.repo_settings, 'delete_ui')
632 639 with delete_ui_patch as delete_ui_mock:
633 640 model.delete_repo_svn_pattern(123)
634 641 delete_ui_mock.assert_called_once_with(123)
635 642
636 643 def test_raises_exception_when_repository_is_not_specified(self):
637 644 model = VcsSettingsModel()
638 645 with pytest.raises(Exception) as exc_info:
639 646 model.delete_repo_svn_pattern(123)
640 647 assert exc_info.value.message == 'Repository is not specified'
641 648
642 649
643 650 class TestDeleteGlobalSvnPattern(object):
644 651 def test_delete_global_svn_pattern_calls_delete_ui(self):
645 652 model = VcsSettingsModel()
646 653 delete_ui_patch = mock.patch.object(model.global_settings, 'delete_ui')
647 654 with delete_ui_patch as delete_ui_mock:
648 655 model.delete_global_svn_pattern(123)
649 656 delete_ui_mock.assert_called_once_with(123)
650 657
651 658
652 659 class TestFilterUiSettings(object):
653 660 def test_settings_are_filtered(self):
654 661 model = VcsSettingsModel()
655 662 repo_settings = [
656 663 UiSetting('extensions', 'largefiles', '', True),
657 664 UiSetting('phases', 'publish', 'True', True),
658 665 UiSetting('hooks', 'changegroup.repo_size', 'hook', True),
659 666 UiSetting('hooks', 'changegroup.push_logger', 'hook', True),
660 667 UiSetting('hooks', 'outgoing.pull_logger', 'hook', True),
661 668 UiSetting(
662 669 'vcs_svn_branch', '84223c972204fa545ca1b22dac7bef5b68d7442d',
663 670 'test_branch', True),
664 671 UiSetting(
665 672 'vcs_svn_tag', '84229c972204fa545ca1b22dac7bef5b68d7442d',
666 673 'test_tag', True),
667 674 ]
668 675 non_repo_settings = [
669 676 UiSetting('largefiles', 'usercache', '/example/largefiles-store', True),
670 677 UiSetting('test', 'outgoing.pull_logger', 'hook', True),
671 678 UiSetting('hooks', 'test2', 'hook', True),
672 679 UiSetting(
673 680 'vcs_svn_repo', '84229c972204fa545ca1b22dac7bef5b68d7442d',
674 681 'test_tag', True),
675 682 ]
676 683 settings = repo_settings + non_repo_settings
677 684 filtered_settings = model._filter_ui_settings(settings)
678 685 assert sorted(filtered_settings) == sorted(repo_settings)
679 686
680 687
681 688 class TestFilterGeneralSettings(object):
682 689 def test_settings_are_filtered(self):
683 690 model = VcsSettingsModel()
684 691 settings = {
685 692 'rhodecode_abcde': 'value1',
686 693 'rhodecode_vwxyz': 'value2',
687 694 }
688 695 general_settings = {
689 696 'rhodecode_{}'.format(key): 'value'
690 697 for key in VcsSettingsModel.GENERAL_SETTINGS
691 698 }
692 699 settings.update(general_settings)
693 700
694 701 filtered_settings = model._filter_general_settings(general_settings)
695 702 assert sorted(filtered_settings) == sorted(general_settings)
696 703
697 704
698 705 class TestGetRepoUiSettings(object):
699 706 def test_global_uis_are_returned_when_no_repo_uis_found(
700 707 self, repo_stub):
701 708 model = VcsSettingsModel(repo=repo_stub.repo_name)
702 709 result = model.get_repo_ui_settings()
703 710 svn_sections = (
704 711 VcsSettingsModel.SVN_TAG_SECTION,
705 712 VcsSettingsModel.SVN_BRANCH_SECTION)
706 713 expected_result = [
707 714 s for s in model.global_settings.get_ui()
708 715 if s.section not in svn_sections]
709 716 assert sorted(result) == sorted(expected_result)
710 717
711 718 def test_repo_uis_are_overriding_global_uis(
712 719 self, repo_stub, settings_util):
713 720 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
714 721 settings_util.create_repo_rhodecode_ui(
715 722 repo_stub, section, 'repo', key=key, active=False)
716 723 model = VcsSettingsModel(repo=repo_stub.repo_name)
717 724 result = model.get_repo_ui_settings()
718 725 for setting in result:
719 726 locator = (setting.section, setting.key)
720 727 if locator in VcsSettingsModel.HOOKS_SETTINGS:
721 728 assert setting.value == 'repo'
722 729
723 730 assert setting.active is False
724 731
725 732 def test_global_svn_patterns_are_not_in_list(
726 733 self, repo_stub, settings_util):
727 734 svn_sections = (
728 735 VcsSettingsModel.SVN_TAG_SECTION,
729 736 VcsSettingsModel.SVN_BRANCH_SECTION)
730 737 for section in svn_sections:
731 738 settings_util.create_rhodecode_ui(
732 739 section, 'repo', key='deadbeef' + section, active=False)
733 740 model = VcsSettingsModel(repo=repo_stub.repo_name)
734 741 result = model.get_repo_ui_settings()
735 742 for setting in result:
736 743 assert setting.section not in svn_sections
737 744
738 745 def test_repo_uis_filtered_by_section_are_returned(
739 746 self, repo_stub, settings_util):
740 747 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
741 748 settings_util.create_repo_rhodecode_ui(
742 749 repo_stub, section, 'repo', key=key, active=False)
743 750 model = VcsSettingsModel(repo=repo_stub.repo_name)
744 751 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
745 752 result = model.get_repo_ui_settings(section=section)
746 753 for setting in result:
747 754 assert setting.section == section
748 755
749 756 def test_repo_uis_filtered_by_key_are_returned(
750 757 self, repo_stub, settings_util):
751 758 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
752 759 settings_util.create_repo_rhodecode_ui(
753 760 repo_stub, section, 'repo', key=key, active=False)
754 761 model = VcsSettingsModel(repo=repo_stub.repo_name)
755 762 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
756 763 result = model.get_repo_ui_settings(key=key)
757 764 for setting in result:
758 765 assert setting.key == key
759 766
760 767 def test_raises_exception_when_repository_is_not_specified(self):
761 768 model = VcsSettingsModel()
762 769 with pytest.raises(Exception) as exc_info:
763 770 model.get_repo_ui_settings()
764 771 assert exc_info.value.message == 'Repository is not specified'
765 772
766 773
767 774 class TestGetRepoGeneralSettings(object):
768 775 def test_global_settings_are_returned_when_no_repo_settings_found(
769 776 self, repo_stub):
770 777 model = VcsSettingsModel(repo=repo_stub.repo_name)
771 778 result = model.get_repo_general_settings()
772 779 expected_result = model.global_settings.get_all_settings()
773 780 assert sorted(result) == sorted(expected_result)
774 781
775 782 def test_repo_uis_are_overriding_global_uis(
776 783 self, repo_stub, settings_util):
777 784 for key in VcsSettingsModel.GENERAL_SETTINGS:
778 785 settings_util.create_repo_rhodecode_setting(
779 786 repo_stub, key, 'abcde', type_='unicode')
780 787 model = VcsSettingsModel(repo=repo_stub.repo_name)
781 788 result = model.get_repo_ui_settings()
782 789 for key in result:
783 790 if key in VcsSettingsModel.GENERAL_SETTINGS:
784 791 assert result[key] == 'abcde'
785 792
786 793 def test_raises_exception_when_repository_is_not_specified(self):
787 794 model = VcsSettingsModel()
788 795 with pytest.raises(Exception) as exc_info:
789 796 model.get_repo_general_settings()
790 797 assert exc_info.value.message == 'Repository is not specified'
791 798
792 799
793 800 class TestGetGlobalGeneralSettings(object):
794 801 def test_global_settings_are_returned(self, repo_stub):
795 802 model = VcsSettingsModel()
796 803 result = model.get_global_general_settings()
797 804 expected_result = model.global_settings.get_all_settings()
798 805 assert sorted(result) == sorted(expected_result)
799 806
800 807 def test_repo_uis_are_not_overriding_global_uis(
801 808 self, repo_stub, settings_util):
802 809 for key in VcsSettingsModel.GENERAL_SETTINGS:
803 810 settings_util.create_repo_rhodecode_setting(
804 811 repo_stub, key, 'abcde', type_='unicode')
805 812 model = VcsSettingsModel(repo=repo_stub.repo_name)
806 813 result = model.get_global_general_settings()
807 814 expected_result = model.global_settings.get_all_settings()
808 815 assert sorted(result) == sorted(expected_result)
809 816
810 817
811 818 class TestGetGlobalUiSettings(object):
812 819 def test_global_uis_are_returned(self, repo_stub):
813 820 model = VcsSettingsModel()
814 821 result = model.get_global_ui_settings()
815 822 expected_result = model.global_settings.get_ui()
816 823 assert sorted(result) == sorted(expected_result)
817 824
818 825 def test_repo_uis_are_not_overriding_global_uis(
819 826 self, repo_stub, settings_util):
820 827 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
821 828 settings_util.create_repo_rhodecode_ui(
822 829 repo_stub, section, 'repo', key=key, active=False)
823 830 model = VcsSettingsModel(repo=repo_stub.repo_name)
824 831 result = model.get_global_ui_settings()
825 832 expected_result = model.global_settings.get_ui()
826 833 assert sorted(result) == sorted(expected_result)
827 834
828 835 def test_ui_settings_filtered_by_section(
829 836 self, repo_stub, settings_util):
830 837 model = VcsSettingsModel(repo=repo_stub.repo_name)
831 838 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
832 839 result = model.get_global_ui_settings(section=section)
833 840 expected_result = model.global_settings.get_ui(section=section)
834 841 assert sorted(result) == sorted(expected_result)
835 842
836 843 def test_ui_settings_filtered_by_key(
837 844 self, repo_stub, settings_util):
838 845 model = VcsSettingsModel(repo=repo_stub.repo_name)
839 846 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
840 847 result = model.get_global_ui_settings(key=key)
841 848 expected_result = model.global_settings.get_ui(key=key)
842 849 assert sorted(result) == sorted(expected_result)
843 850
844 851
845 852 class TestGetGeneralSettings(object):
846 853 def test_global_settings_are_returned_when_inherited_is_true(
847 854 self, repo_stub, settings_util):
848 855 model = VcsSettingsModel(repo=repo_stub.repo_name)
849 856 model.inherit_global_settings = True
850 857 for key in VcsSettingsModel.GENERAL_SETTINGS:
851 858 settings_util.create_repo_rhodecode_setting(
852 859 repo_stub, key, 'abcde', type_='unicode')
853 860 result = model.get_general_settings()
854 861 expected_result = model.get_global_general_settings()
855 862 assert sorted(result) == sorted(expected_result)
856 863
857 864 def test_repo_settings_are_returned_when_inherited_is_false(
858 865 self, repo_stub, settings_util):
859 866 model = VcsSettingsModel(repo=repo_stub.repo_name)
860 867 model.inherit_global_settings = False
861 868 for key in VcsSettingsModel.GENERAL_SETTINGS:
862 869 settings_util.create_repo_rhodecode_setting(
863 870 repo_stub, key, 'abcde', type_='unicode')
864 871 result = model.get_general_settings()
865 872 expected_result = model.get_repo_general_settings()
866 873 assert sorted(result) == sorted(expected_result)
867 874
868 875 def test_global_settings_are_returned_when_no_repository_specified(self):
869 876 model = VcsSettingsModel()
870 877 result = model.get_general_settings()
871 878 expected_result = model.get_global_general_settings()
872 879 assert sorted(result) == sorted(expected_result)
873 880
874 881
875 882 class TestGetUiSettings(object):
876 883 def test_global_settings_are_returned_when_inherited_is_true(
877 884 self, repo_stub, settings_util):
878 885 model = VcsSettingsModel(repo=repo_stub.repo_name)
879 886 model.inherit_global_settings = True
880 887 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
881 888 settings_util.create_repo_rhodecode_ui(
882 889 repo_stub, section, 'repo', key=key, active=True)
883 890 result = model.get_ui_settings()
884 891 expected_result = model.get_global_ui_settings()
885 892 assert sorted(result) == sorted(expected_result)
886 893
887 894 def test_repo_settings_are_returned_when_inherited_is_false(
888 895 self, repo_stub, settings_util):
889 896 model = VcsSettingsModel(repo=repo_stub.repo_name)
890 897 model.inherit_global_settings = False
891 898 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
892 899 settings_util.create_repo_rhodecode_ui(
893 900 repo_stub, section, 'repo', key=key, active=True)
894 901 result = model.get_ui_settings()
895 902 expected_result = model.get_repo_ui_settings()
896 903 assert sorted(result) == sorted(expected_result)
897 904
898 905 def test_repo_settings_filtered_by_section_and_key(self, repo_stub):
899 906 model = VcsSettingsModel(repo=repo_stub.repo_name)
900 907 model.inherit_global_settings = False
901 908 args = ('section', 'key')
902 909 with mock.patch.object(model, 'get_repo_ui_settings') as settings_mock:
903 910 model.get_ui_settings(*args)
904 911 settings_mock.assert_called_once_with(*args)
905 912
906 913 def test_global_settings_filtered_by_section_and_key(self):
907 914 model = VcsSettingsModel()
908 915 args = ('section', 'key')
909 916 with mock.patch.object(model, 'get_global_ui_settings') as (
910 917 settings_mock):
911 918 model.get_ui_settings(*args)
912 919 settings_mock.assert_called_once_with(*args)
913 920
914 921 def test_global_settings_are_returned_when_no_repository_specified(self):
915 922 model = VcsSettingsModel()
916 923 result = model.get_ui_settings()
917 924 expected_result = model.get_global_ui_settings()
918 925 assert sorted(result) == sorted(expected_result)
919 926
920 927
921 928 class TestGetSvnPatterns(object):
922 929 def test_repo_settings_filtered_by_section_and_key(self, repo_stub):
923 930 model = VcsSettingsModel(repo=repo_stub.repo_name)
924 931 args = ('section', )
925 932 with mock.patch.object(model, 'get_repo_ui_settings') as settings_mock:
926 933 model.get_svn_patterns(*args)
927 934 settings_mock.assert_called_once_with(*args)
928 935
929 936 def test_global_settings_filtered_by_section_and_key(self):
930 937 model = VcsSettingsModel()
931 938 args = ('section', )
932 939 with mock.patch.object(model, 'get_global_ui_settings') as (
933 940 settings_mock):
934 941 model.get_svn_patterns(*args)
935 942 settings_mock.assert_called_once_with(*args)
936 943
937 944
938 945 class TestGetReposLocation(object):
939 946 def test_returns_repos_location(self, repo_stub):
940 947 model = VcsSettingsModel()
941 948
942 949 result_mock = mock.Mock()
943 950 result_mock.ui_value = '/tmp'
944 951
945 952 with mock.patch.object(model, 'global_settings') as settings_mock:
946 953 settings_mock.get_ui_by_key.return_value = result_mock
947 954 result = model.get_repos_location()
948 955
949 956 settings_mock.get_ui_by_key.assert_called_once_with('/')
950 957 assert result == '/tmp'
951 958
952 959
953 960 class TestCreateOrUpdateRepoSettings(object):
954 961 FORM_DATA = {
955 962 'inherit_global_settings': False,
956 963 'hooks_changegroup_repo_size': False,
957 964 'hooks_changegroup_push_logger': False,
958 965 'hooks_outgoing_pull_logger': False,
959 966 'extensions_largefiles': False,
967 'extensions_evolve': False,
960 968 'largefiles_usercache': '/example/largefiles-store',
961 969 'vcs_git_lfs_enabled': False,
962 970 'vcs_git_lfs_store_location': '/',
963 971 'phases_publish': 'False',
964 972 'rhodecode_pr_merge_enabled': False,
965 973 'rhodecode_use_outdated_comments': False,
966 974 'new_svn_branch': '',
967 975 'new_svn_tag': ''
968 976 }
969 977
970 978 def test_get_raises_exception_when_repository_not_specified(self):
971 979 model = VcsSettingsModel()
972 980 with pytest.raises(Exception) as exc_info:
973 981 model.create_or_update_repo_settings(data=self.FORM_DATA)
974 982 assert exc_info.value.message == 'Repository is not specified'
975 983
976 984 def test_only_svn_settings_are_updated_when_type_is_svn(self, backend_svn):
977 985 repo = backend_svn.create_repo()
978 986 model = VcsSettingsModel(repo=repo)
979 987 with self._patch_model(model) as mocks:
980 988 model.create_or_update_repo_settings(
981 989 data=self.FORM_DATA, inherit_global_settings=False)
982 990 mocks['create_repo_svn_settings'].assert_called_once_with(
983 991 self.FORM_DATA)
984 992 non_called_methods = (
985 993 'create_or_update_repo_hook_settings',
986 994 'create_or_update_repo_pr_settings',
987 995 'create_or_update_repo_hg_settings')
988 996 for method in non_called_methods:
989 997 assert mocks[method].call_count == 0
990 998
991 999 def test_non_svn_settings_are_updated_when_type_is_hg(self, backend_hg):
992 1000 repo = backend_hg.create_repo()
993 1001 model = VcsSettingsModel(repo=repo)
994 1002 with self._patch_model(model) as mocks:
995 1003 model.create_or_update_repo_settings(
996 1004 data=self.FORM_DATA, inherit_global_settings=False)
997 1005
998 1006 assert mocks['create_repo_svn_settings'].call_count == 0
999 1007 called_methods = (
1000 1008 'create_or_update_repo_hook_settings',
1001 1009 'create_or_update_repo_pr_settings',
1002 1010 'create_or_update_repo_hg_settings')
1003 1011 for method in called_methods:
1004 1012 mocks[method].assert_called_once_with(self.FORM_DATA)
1005 1013
1006 1014 def test_non_svn_and_hg_settings_are_updated_when_type_is_git(
1007 1015 self, backend_git):
1008 1016 repo = backend_git.create_repo()
1009 1017 model = VcsSettingsModel(repo=repo)
1010 1018 with self._patch_model(model) as mocks:
1011 1019 model.create_or_update_repo_settings(
1012 1020 data=self.FORM_DATA, inherit_global_settings=False)
1013 1021
1014 1022 assert mocks['create_repo_svn_settings'].call_count == 0
1015 1023 called_methods = (
1016 1024 'create_or_update_repo_hook_settings',
1017 1025 'create_or_update_repo_pr_settings')
1018 1026 non_called_methods = (
1019 1027 'create_repo_svn_settings',
1020 1028 'create_or_update_repo_hg_settings'
1021 1029 )
1022 1030 for method in called_methods:
1023 1031 mocks[method].assert_called_once_with(self.FORM_DATA)
1024 1032 for method in non_called_methods:
1025 1033 assert mocks[method].call_count == 0
1026 1034
1027 1035 def test_no_methods_are_called_when_settings_are_inherited(
1028 1036 self, backend):
1029 1037 repo = backend.create_repo()
1030 1038 model = VcsSettingsModel(repo=repo)
1031 1039 with self._patch_model(model) as mocks:
1032 1040 model.create_or_update_repo_settings(
1033 1041 data=self.FORM_DATA, inherit_global_settings=True)
1034 1042 for method_name in mocks:
1035 1043 assert mocks[method_name].call_count == 0
1036 1044
1037 1045 def test_cache_is_marked_for_invalidation(self, repo_stub):
1038 1046 model = VcsSettingsModel(repo=repo_stub)
1039 1047 invalidation_patcher = mock.patch(
1040 1048 'rhodecode.controllers.admin.repos.ScmModel.mark_for_invalidation')
1041 1049 with invalidation_patcher as invalidation_mock:
1042 1050 model.create_or_update_repo_settings(
1043 1051 data=self.FORM_DATA, inherit_global_settings=True)
1044 1052 invalidation_mock.assert_called_once_with(
1045 1053 repo_stub.repo_name, delete=True)
1046 1054
1047 1055 def test_inherit_flag_is_saved(self, repo_stub):
1048 1056 model = VcsSettingsModel(repo=repo_stub)
1049 1057 model.inherit_global_settings = True
1050 1058 with self._patch_model(model):
1051 1059 model.create_or_update_repo_settings(
1052 1060 data=self.FORM_DATA, inherit_global_settings=False)
1053 1061 assert model.inherit_global_settings is False
1054 1062
1055 1063 def _patch_model(self, model):
1056 1064 return mock.patch.multiple(
1057 1065 model,
1058 1066 create_repo_svn_settings=mock.DEFAULT,
1059 1067 create_or_update_repo_hook_settings=mock.DEFAULT,
1060 1068 create_or_update_repo_pr_settings=mock.DEFAULT,
1061 1069 create_or_update_repo_hg_settings=mock.DEFAULT)
General Comments 0
You need to be logged in to leave comments. Login now