##// 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 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import time
22 import time
23
23
24 import rhodecode
24 import rhodecode
25 from rhodecode.api import (
25 from rhodecode.api import (
26 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
26 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
27 from rhodecode.api.utils import (
27 from rhodecode.api.utils import (
28 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
28 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
29 get_user_group_or_error, get_user_or_error, validate_repo_permissions,
29 get_user_group_or_error, get_user_or_error, validate_repo_permissions,
30 get_perm_or_error, parse_args, get_origin, build_commit_data,
30 get_perm_or_error, parse_args, get_origin, build_commit_data,
31 validate_set_owner_permissions)
31 validate_set_owner_permissions)
32 from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi
32 from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi
33 from rhodecode.lib.utils2 import str2bool, time_to_datetime
33 from rhodecode.lib.utils2 import str2bool, time_to_datetime
34 from rhodecode.lib.ext_json import json
34 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
35 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
36 from rhodecode.model.changeset_status import ChangesetStatusModel
36 from rhodecode.model.changeset_status import ChangesetStatusModel
37 from rhodecode.model.comment import CommentsModel
37 from rhodecode.model.comment import CommentsModel
38 from rhodecode.model.db import (
38 from rhodecode.model.db import (
39 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup,
39 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup,
40 ChangesetComment)
40 ChangesetComment)
41 from rhodecode.model.repo import RepoModel
41 from rhodecode.model.repo import RepoModel
42 from rhodecode.model.scm import ScmModel, RepoList
42 from rhodecode.model.scm import ScmModel, RepoList
43 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
43 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
44 from rhodecode.model import validation_schema
44 from rhodecode.model import validation_schema
45 from rhodecode.model.validation_schema.schemas import repo_schema
45 from rhodecode.model.validation_schema.schemas import repo_schema
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49
49
50 @jsonrpc_method()
50 @jsonrpc_method()
51 def get_repo(request, apiuser, repoid, cache=Optional(True)):
51 def get_repo(request, apiuser, repoid, cache=Optional(True)):
52 """
52 """
53 Gets an existing repository by its name or repository_id.
53 Gets an existing repository by its name or repository_id.
54
54
55 The members section so the output returns users groups or users
55 The members section so the output returns users groups or users
56 associated with that repository.
56 associated with that repository.
57
57
58 This command can only be run using an |authtoken| with admin rights,
58 This command can only be run using an |authtoken| with admin rights,
59 or users with at least read rights to the |repo|.
59 or users with at least read rights to the |repo|.
60
60
61 :param apiuser: This is filled automatically from the |authtoken|.
61 :param apiuser: This is filled automatically from the |authtoken|.
62 :type apiuser: AuthUser
62 :type apiuser: AuthUser
63 :param repoid: The repository name or repository id.
63 :param repoid: The repository name or repository id.
64 :type repoid: str or int
64 :type repoid: str or int
65 :param cache: use the cached value for last changeset
65 :param cache: use the cached value for last changeset
66 :type: cache: Optional(bool)
66 :type: cache: Optional(bool)
67
67
68 Example output:
68 Example output:
69
69
70 .. code-block:: bash
70 .. code-block:: bash
71
71
72 {
72 {
73 "error": null,
73 "error": null,
74 "id": <repo_id>,
74 "id": <repo_id>,
75 "result": {
75 "result": {
76 "clone_uri": null,
76 "clone_uri": null,
77 "created_on": "timestamp",
77 "created_on": "timestamp",
78 "description": "repo description",
78 "description": "repo description",
79 "enable_downloads": false,
79 "enable_downloads": false,
80 "enable_locking": false,
80 "enable_locking": false,
81 "enable_statistics": false,
81 "enable_statistics": false,
82 "followers": [
82 "followers": [
83 {
83 {
84 "active": true,
84 "active": true,
85 "admin": false,
85 "admin": false,
86 "api_key": "****************************************",
86 "api_key": "****************************************",
87 "api_keys": [
87 "api_keys": [
88 "****************************************"
88 "****************************************"
89 ],
89 ],
90 "email": "user@example.com",
90 "email": "user@example.com",
91 "emails": [
91 "emails": [
92 "user@example.com"
92 "user@example.com"
93 ],
93 ],
94 "extern_name": "rhodecode",
94 "extern_name": "rhodecode",
95 "extern_type": "rhodecode",
95 "extern_type": "rhodecode",
96 "firstname": "username",
96 "firstname": "username",
97 "ip_addresses": [],
97 "ip_addresses": [],
98 "language": null,
98 "language": null,
99 "last_login": "2015-09-16T17:16:35.854",
99 "last_login": "2015-09-16T17:16:35.854",
100 "lastname": "surname",
100 "lastname": "surname",
101 "user_id": <user_id>,
101 "user_id": <user_id>,
102 "username": "name"
102 "username": "name"
103 }
103 }
104 ],
104 ],
105 "fork_of": "parent-repo",
105 "fork_of": "parent-repo",
106 "landing_rev": [
106 "landing_rev": [
107 "rev",
107 "rev",
108 "tip"
108 "tip"
109 ],
109 ],
110 "last_changeset": {
110 "last_changeset": {
111 "author": "User <user@example.com>",
111 "author": "User <user@example.com>",
112 "branch": "default",
112 "branch": "default",
113 "date": "timestamp",
113 "date": "timestamp",
114 "message": "last commit message",
114 "message": "last commit message",
115 "parents": [
115 "parents": [
116 {
116 {
117 "raw_id": "commit-id"
117 "raw_id": "commit-id"
118 }
118 }
119 ],
119 ],
120 "raw_id": "commit-id",
120 "raw_id": "commit-id",
121 "revision": <revision number>,
121 "revision": <revision number>,
122 "short_id": "short id"
122 "short_id": "short id"
123 },
123 },
124 "lock_reason": null,
124 "lock_reason": null,
125 "locked_by": null,
125 "locked_by": null,
126 "locked_date": null,
126 "locked_date": null,
127 "members": [
127 "members": [
128 {
128 {
129 "name": "super-admin-name",
129 "name": "super-admin-name",
130 "origin": "super-admin",
130 "origin": "super-admin",
131 "permission": "repository.admin",
131 "permission": "repository.admin",
132 "type": "user"
132 "type": "user"
133 },
133 },
134 {
134 {
135 "name": "owner-name",
135 "name": "owner-name",
136 "origin": "owner",
136 "origin": "owner",
137 "permission": "repository.admin",
137 "permission": "repository.admin",
138 "type": "user"
138 "type": "user"
139 },
139 },
140 {
140 {
141 "name": "user-group-name",
141 "name": "user-group-name",
142 "origin": "permission",
142 "origin": "permission",
143 "permission": "repository.write",
143 "permission": "repository.write",
144 "type": "user_group"
144 "type": "user_group"
145 }
145 }
146 ],
146 ],
147 "owner": "owner-name",
147 "owner": "owner-name",
148 "permissions": [
148 "permissions": [
149 {
149 {
150 "name": "super-admin-name",
150 "name": "super-admin-name",
151 "origin": "super-admin",
151 "origin": "super-admin",
152 "permission": "repository.admin",
152 "permission": "repository.admin",
153 "type": "user"
153 "type": "user"
154 },
154 },
155 {
155 {
156 "name": "owner-name",
156 "name": "owner-name",
157 "origin": "owner",
157 "origin": "owner",
158 "permission": "repository.admin",
158 "permission": "repository.admin",
159 "type": "user"
159 "type": "user"
160 },
160 },
161 {
161 {
162 "name": "user-group-name",
162 "name": "user-group-name",
163 "origin": "permission",
163 "origin": "permission",
164 "permission": "repository.write",
164 "permission": "repository.write",
165 "type": "user_group"
165 "type": "user_group"
166 }
166 }
167 ],
167 ],
168 "private": true,
168 "private": true,
169 "repo_id": 676,
169 "repo_id": 676,
170 "repo_name": "user-group/repo-name",
170 "repo_name": "user-group/repo-name",
171 "repo_type": "hg"
171 "repo_type": "hg"
172 }
172 }
173 }
173 }
174 """
174 """
175
175
176 repo = get_repo_or_error(repoid)
176 repo = get_repo_or_error(repoid)
177 cache = Optional.extract(cache)
177 cache = Optional.extract(cache)
178
178
179 include_secrets = False
179 include_secrets = False
180 if has_superadmin_permission(apiuser):
180 if has_superadmin_permission(apiuser):
181 include_secrets = True
181 include_secrets = True
182 else:
182 else:
183 # check if we have at least read permission for this repo !
183 # check if we have at least read permission for this repo !
184 _perms = (
184 _perms = (
185 'repository.admin', 'repository.write', 'repository.read',)
185 'repository.admin', 'repository.write', 'repository.read',)
186 validate_repo_permissions(apiuser, repoid, repo, _perms)
186 validate_repo_permissions(apiuser, repoid, repo, _perms)
187
187
188 permissions = []
188 permissions = []
189 for _user in repo.permissions():
189 for _user in repo.permissions():
190 user_data = {
190 user_data = {
191 'name': _user.username,
191 'name': _user.username,
192 'permission': _user.permission,
192 'permission': _user.permission,
193 'origin': get_origin(_user),
193 'origin': get_origin(_user),
194 'type': "user",
194 'type': "user",
195 }
195 }
196 permissions.append(user_data)
196 permissions.append(user_data)
197
197
198 for _user_group in repo.permission_user_groups():
198 for _user_group in repo.permission_user_groups():
199 user_group_data = {
199 user_group_data = {
200 'name': _user_group.users_group_name,
200 'name': _user_group.users_group_name,
201 'permission': _user_group.permission,
201 'permission': _user_group.permission,
202 'origin': get_origin(_user_group),
202 'origin': get_origin(_user_group),
203 'type': "user_group",
203 'type': "user_group",
204 }
204 }
205 permissions.append(user_group_data)
205 permissions.append(user_group_data)
206
206
207 following_users = [
207 following_users = [
208 user.user.get_api_data(include_secrets=include_secrets)
208 user.user.get_api_data(include_secrets=include_secrets)
209 for user in repo.followers]
209 for user in repo.followers]
210
210
211 if not cache:
211 if not cache:
212 repo.update_commit_cache()
212 repo.update_commit_cache()
213 data = repo.get_api_data(include_secrets=include_secrets)
213 data = repo.get_api_data(include_secrets=include_secrets)
214 data['members'] = permissions # TODO: this should be deprecated soon
214 data['members'] = permissions # TODO: this should be deprecated soon
215 data['permissions'] = permissions
215 data['permissions'] = permissions
216 data['followers'] = following_users
216 data['followers'] = following_users
217 return data
217 return data
218
218
219
219
220 @jsonrpc_method()
220 @jsonrpc_method()
221 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
221 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
222 """
222 """
223 Lists all existing repositories.
223 Lists all existing repositories.
224
224
225 This command can only be run using an |authtoken| with admin rights,
225 This command can only be run using an |authtoken| with admin rights,
226 or users with at least read rights to |repos|.
226 or users with at least read rights to |repos|.
227
227
228 :param apiuser: This is filled automatically from the |authtoken|.
228 :param apiuser: This is filled automatically from the |authtoken|.
229 :type apiuser: AuthUser
229 :type apiuser: AuthUser
230 :param root: specify root repository group to fetch repositories.
230 :param root: specify root repository group to fetch repositories.
231 filters the returned repositories to be members of given root group.
231 filters the returned repositories to be members of given root group.
232 :type root: Optional(None)
232 :type root: Optional(None)
233 :param traverse: traverse given root into subrepositories. With this flag
233 :param traverse: traverse given root into subrepositories. With this flag
234 set to False, it will only return top-level repositories from `root`.
234 set to False, it will only return top-level repositories from `root`.
235 if root is empty it will return just top-level repositories.
235 if root is empty it will return just top-level repositories.
236 :type traverse: Optional(True)
236 :type traverse: Optional(True)
237
237
238
238
239 Example output:
239 Example output:
240
240
241 .. code-block:: bash
241 .. code-block:: bash
242
242
243 id : <id_given_in_input>
243 id : <id_given_in_input>
244 result: [
244 result: [
245 {
245 {
246 "repo_id" : "<repo_id>",
246 "repo_id" : "<repo_id>",
247 "repo_name" : "<reponame>"
247 "repo_name" : "<reponame>"
248 "repo_type" : "<repo_type>",
248 "repo_type" : "<repo_type>",
249 "clone_uri" : "<clone_uri>",
249 "clone_uri" : "<clone_uri>",
250 "private": : "<bool>",
250 "private": : "<bool>",
251 "created_on" : "<datetimecreated>",
251 "created_on" : "<datetimecreated>",
252 "description" : "<description>",
252 "description" : "<description>",
253 "landing_rev": "<landing_rev>",
253 "landing_rev": "<landing_rev>",
254 "owner": "<repo_owner>",
254 "owner": "<repo_owner>",
255 "fork_of": "<name_of_fork_parent>",
255 "fork_of": "<name_of_fork_parent>",
256 "enable_downloads": "<bool>",
256 "enable_downloads": "<bool>",
257 "enable_locking": "<bool>",
257 "enable_locking": "<bool>",
258 "enable_statistics": "<bool>",
258 "enable_statistics": "<bool>",
259 },
259 },
260 ...
260 ...
261 ]
261 ]
262 error: null
262 error: null
263 """
263 """
264
264
265 include_secrets = has_superadmin_permission(apiuser)
265 include_secrets = has_superadmin_permission(apiuser)
266 _perms = ('repository.read', 'repository.write', 'repository.admin',)
266 _perms = ('repository.read', 'repository.write', 'repository.admin',)
267 extras = {'user': apiuser}
267 extras = {'user': apiuser}
268
268
269 root = Optional.extract(root)
269 root = Optional.extract(root)
270 traverse = Optional.extract(traverse, binary=True)
270 traverse = Optional.extract(traverse, binary=True)
271
271
272 if root:
272 if root:
273 # verify parent existance, if it's empty return an error
273 # verify parent existance, if it's empty return an error
274 parent = RepoGroup.get_by_group_name(root)
274 parent = RepoGroup.get_by_group_name(root)
275 if not parent:
275 if not parent:
276 raise JSONRPCError(
276 raise JSONRPCError(
277 'Root repository group `{}` does not exist'.format(root))
277 'Root repository group `{}` does not exist'.format(root))
278
278
279 if traverse:
279 if traverse:
280 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
280 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
281 else:
281 else:
282 repos = RepoModel().get_repos_for_root(root=parent)
282 repos = RepoModel().get_repos_for_root(root=parent)
283 else:
283 else:
284 if traverse:
284 if traverse:
285 repos = RepoModel().get_all()
285 repos = RepoModel().get_all()
286 else:
286 else:
287 # return just top-level
287 # return just top-level
288 repos = RepoModel().get_repos_for_root(root=None)
288 repos = RepoModel().get_repos_for_root(root=None)
289
289
290 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
290 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
291 return [repo.get_api_data(include_secrets=include_secrets)
291 return [repo.get_api_data(include_secrets=include_secrets)
292 for repo in repo_list]
292 for repo in repo_list]
293
293
294
294
295 @jsonrpc_method()
295 @jsonrpc_method()
296 def get_repo_changeset(request, apiuser, repoid, revision,
296 def get_repo_changeset(request, apiuser, repoid, revision,
297 details=Optional('basic')):
297 details=Optional('basic')):
298 """
298 """
299 Returns information about a changeset.
299 Returns information about a changeset.
300
300
301 Additionally parameters define the amount of details returned by
301 Additionally parameters define the amount of details returned by
302 this function.
302 this function.
303
303
304 This command can only be run using an |authtoken| with admin rights,
304 This command can only be run using an |authtoken| with admin rights,
305 or users with at least read rights to the |repo|.
305 or users with at least read rights to the |repo|.
306
306
307 :param apiuser: This is filled automatically from the |authtoken|.
307 :param apiuser: This is filled automatically from the |authtoken|.
308 :type apiuser: AuthUser
308 :type apiuser: AuthUser
309 :param repoid: The repository name or repository id
309 :param repoid: The repository name or repository id
310 :type repoid: str or int
310 :type repoid: str or int
311 :param revision: revision for which listing should be done
311 :param revision: revision for which listing should be done
312 :type revision: str
312 :type revision: str
313 :param details: details can be 'basic|extended|full' full gives diff
313 :param details: details can be 'basic|extended|full' full gives diff
314 info details like the diff itself, and number of changed files etc.
314 info details like the diff itself, and number of changed files etc.
315 :type details: Optional(str)
315 :type details: Optional(str)
316
316
317 """
317 """
318 repo = get_repo_or_error(repoid)
318 repo = get_repo_or_error(repoid)
319 if not has_superadmin_permission(apiuser):
319 if not has_superadmin_permission(apiuser):
320 _perms = (
320 _perms = (
321 'repository.admin', 'repository.write', 'repository.read',)
321 'repository.admin', 'repository.write', 'repository.read',)
322 validate_repo_permissions(apiuser, repoid, repo, _perms)
322 validate_repo_permissions(apiuser, repoid, repo, _perms)
323
323
324 changes_details = Optional.extract(details)
324 changes_details = Optional.extract(details)
325 _changes_details_types = ['basic', 'extended', 'full']
325 _changes_details_types = ['basic', 'extended', 'full']
326 if changes_details not in _changes_details_types:
326 if changes_details not in _changes_details_types:
327 raise JSONRPCError(
327 raise JSONRPCError(
328 'ret_type must be one of %s' % (
328 'ret_type must be one of %s' % (
329 ','.join(_changes_details_types)))
329 ','.join(_changes_details_types)))
330
330
331 pre_load = ['author', 'branch', 'date', 'message', 'parents',
331 pre_load = ['author', 'branch', 'date', 'message', 'parents',
332 'status', '_commit', '_file_paths']
332 'status', '_commit', '_file_paths']
333
333
334 try:
334 try:
335 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
335 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
336 except TypeError as e:
336 except TypeError as e:
337 raise JSONRPCError(e.message)
337 raise JSONRPCError(e.message)
338 _cs_json = cs.__json__()
338 _cs_json = cs.__json__()
339 _cs_json['diff'] = build_commit_data(cs, changes_details)
339 _cs_json['diff'] = build_commit_data(cs, changes_details)
340 if changes_details == 'full':
340 if changes_details == 'full':
341 _cs_json['refs'] = {
341 _cs_json['refs'] = {
342 'branches': [cs.branch],
342 'branches': [cs.branch],
343 'bookmarks': getattr(cs, 'bookmarks', []),
343 'bookmarks': getattr(cs, 'bookmarks', []),
344 'tags': cs.tags
344 'tags': cs.tags
345 }
345 }
346 return _cs_json
346 return _cs_json
347
347
348
348
349 @jsonrpc_method()
349 @jsonrpc_method()
350 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
350 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
351 details=Optional('basic')):
351 details=Optional('basic')):
352 """
352 """
353 Returns a set of commits limited by the number starting
353 Returns a set of commits limited by the number starting
354 from the `start_rev` option.
354 from the `start_rev` option.
355
355
356 Additional parameters define the amount of details returned by this
356 Additional parameters define the amount of details returned by this
357 function.
357 function.
358
358
359 This command can only be run using an |authtoken| with admin rights,
359 This command can only be run using an |authtoken| with admin rights,
360 or users with at least read rights to |repos|.
360 or users with at least read rights to |repos|.
361
361
362 :param apiuser: This is filled automatically from the |authtoken|.
362 :param apiuser: This is filled automatically from the |authtoken|.
363 :type apiuser: AuthUser
363 :type apiuser: AuthUser
364 :param repoid: The repository name or repository ID.
364 :param repoid: The repository name or repository ID.
365 :type repoid: str or int
365 :type repoid: str or int
366 :param start_rev: The starting revision from where to get changesets.
366 :param start_rev: The starting revision from where to get changesets.
367 :type start_rev: str
367 :type start_rev: str
368 :param limit: Limit the number of commits to this amount
368 :param limit: Limit the number of commits to this amount
369 :type limit: str or int
369 :type limit: str or int
370 :param details: Set the level of detail returned. Valid option are:
370 :param details: Set the level of detail returned. Valid option are:
371 ``basic``, ``extended`` and ``full``.
371 ``basic``, ``extended`` and ``full``.
372 :type details: Optional(str)
372 :type details: Optional(str)
373
373
374 .. note::
374 .. note::
375
375
376 Setting the parameter `details` to the value ``full`` is extensive
376 Setting the parameter `details` to the value ``full`` is extensive
377 and returns details like the diff itself, and the number
377 and returns details like the diff itself, and the number
378 of changed files.
378 of changed files.
379
379
380 """
380 """
381 repo = get_repo_or_error(repoid)
381 repo = get_repo_or_error(repoid)
382 if not has_superadmin_permission(apiuser):
382 if not has_superadmin_permission(apiuser):
383 _perms = (
383 _perms = (
384 'repository.admin', 'repository.write', 'repository.read',)
384 'repository.admin', 'repository.write', 'repository.read',)
385 validate_repo_permissions(apiuser, repoid, repo, _perms)
385 validate_repo_permissions(apiuser, repoid, repo, _perms)
386
386
387 changes_details = Optional.extract(details)
387 changes_details = Optional.extract(details)
388 _changes_details_types = ['basic', 'extended', 'full']
388 _changes_details_types = ['basic', 'extended', 'full']
389 if changes_details not in _changes_details_types:
389 if changes_details not in _changes_details_types:
390 raise JSONRPCError(
390 raise JSONRPCError(
391 'ret_type must be one of %s' % (
391 'ret_type must be one of %s' % (
392 ','.join(_changes_details_types)))
392 ','.join(_changes_details_types)))
393
393
394 limit = int(limit)
394 limit = int(limit)
395 pre_load = ['author', 'branch', 'date', 'message', 'parents',
395 pre_load = ['author', 'branch', 'date', 'message', 'parents',
396 'status', '_commit', '_file_paths']
396 'status', '_commit', '_file_paths']
397
397
398 vcs_repo = repo.scm_instance()
398 vcs_repo = repo.scm_instance()
399 # SVN needs a special case to distinguish its index and commit id
399 # SVN needs a special case to distinguish its index and commit id
400 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
400 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
401 start_rev = vcs_repo.commit_ids[0]
401 start_rev = vcs_repo.commit_ids[0]
402
402
403 try:
403 try:
404 commits = vcs_repo.get_commits(
404 commits = vcs_repo.get_commits(
405 start_id=start_rev, pre_load=pre_load)
405 start_id=start_rev, pre_load=pre_load)
406 except TypeError as e:
406 except TypeError as e:
407 raise JSONRPCError(e.message)
407 raise JSONRPCError(e.message)
408 except Exception:
408 except Exception:
409 log.exception('Fetching of commits failed')
409 log.exception('Fetching of commits failed')
410 raise JSONRPCError('Error occurred during commit fetching')
410 raise JSONRPCError('Error occurred during commit fetching')
411
411
412 ret = []
412 ret = []
413 for cnt, commit in enumerate(commits):
413 for cnt, commit in enumerate(commits):
414 if cnt >= limit != -1:
414 if cnt >= limit != -1:
415 break
415 break
416 _cs_json = commit.__json__()
416 _cs_json = commit.__json__()
417 _cs_json['diff'] = build_commit_data(commit, changes_details)
417 _cs_json['diff'] = build_commit_data(commit, changes_details)
418 if changes_details == 'full':
418 if changes_details == 'full':
419 _cs_json['refs'] = {
419 _cs_json['refs'] = {
420 'branches': [commit.branch],
420 'branches': [commit.branch],
421 'bookmarks': getattr(commit, 'bookmarks', []),
421 'bookmarks': getattr(commit, 'bookmarks', []),
422 'tags': commit.tags
422 'tags': commit.tags
423 }
423 }
424 ret.append(_cs_json)
424 ret.append(_cs_json)
425 return ret
425 return ret
426
426
427
427
428 @jsonrpc_method()
428 @jsonrpc_method()
429 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
429 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
430 ret_type=Optional('all'), details=Optional('basic'),
430 ret_type=Optional('all'), details=Optional('basic'),
431 max_file_bytes=Optional(None)):
431 max_file_bytes=Optional(None)):
432 """
432 """
433 Returns a list of nodes and children in a flat list for a given
433 Returns a list of nodes and children in a flat list for a given
434 path at given revision.
434 path at given revision.
435
435
436 It's possible to specify ret_type to show only `files` or `dirs`.
436 It's possible to specify ret_type to show only `files` or `dirs`.
437
437
438 This command can only be run using an |authtoken| with admin rights,
438 This command can only be run using an |authtoken| with admin rights,
439 or users with at least read rights to |repos|.
439 or users with at least read rights to |repos|.
440
440
441 :param apiuser: This is filled automatically from the |authtoken|.
441 :param apiuser: This is filled automatically from the |authtoken|.
442 :type apiuser: AuthUser
442 :type apiuser: AuthUser
443 :param repoid: The repository name or repository ID.
443 :param repoid: The repository name or repository ID.
444 :type repoid: str or int
444 :type repoid: str or int
445 :param revision: The revision for which listing should be done.
445 :param revision: The revision for which listing should be done.
446 :type revision: str
446 :type revision: str
447 :param root_path: The path from which to start displaying.
447 :param root_path: The path from which to start displaying.
448 :type root_path: str
448 :type root_path: str
449 :param ret_type: Set the return type. Valid options are
449 :param ret_type: Set the return type. Valid options are
450 ``all`` (default), ``files`` and ``dirs``.
450 ``all`` (default), ``files`` and ``dirs``.
451 :type ret_type: Optional(str)
451 :type ret_type: Optional(str)
452 :param details: Returns extended information about nodes, such as
452 :param details: Returns extended information about nodes, such as
453 md5, binary, and or content. The valid options are ``basic`` and
453 md5, binary, and or content. The valid options are ``basic`` and
454 ``full``.
454 ``full``.
455 :type details: Optional(str)
455 :type details: Optional(str)
456 :param max_file_bytes: Only return file content under this file size bytes
456 :param max_file_bytes: Only return file content under this file size bytes
457 :type details: Optional(int)
457 :type details: Optional(int)
458
458
459 Example output:
459 Example output:
460
460
461 .. code-block:: bash
461 .. code-block:: bash
462
462
463 id : <id_given_in_input>
463 id : <id_given_in_input>
464 result: [
464 result: [
465 {
465 {
466 "name" : "<name>"
466 "name" : "<name>"
467 "type" : "<type>",
467 "type" : "<type>",
468 "binary": "<true|false>" (only in extended mode)
468 "binary": "<true|false>" (only in extended mode)
469 "md5" : "<md5 of file content>" (only in extended mode)
469 "md5" : "<md5 of file content>" (only in extended mode)
470 },
470 },
471 ...
471 ...
472 ]
472 ]
473 error: null
473 error: null
474 """
474 """
475
475
476 repo = get_repo_or_error(repoid)
476 repo = get_repo_or_error(repoid)
477 if not has_superadmin_permission(apiuser):
477 if not has_superadmin_permission(apiuser):
478 _perms = (
478 _perms = (
479 'repository.admin', 'repository.write', 'repository.read',)
479 'repository.admin', 'repository.write', 'repository.read',)
480 validate_repo_permissions(apiuser, repoid, repo, _perms)
480 validate_repo_permissions(apiuser, repoid, repo, _perms)
481
481
482 ret_type = Optional.extract(ret_type)
482 ret_type = Optional.extract(ret_type)
483 details = Optional.extract(details)
483 details = Optional.extract(details)
484 _extended_types = ['basic', 'full']
484 _extended_types = ['basic', 'full']
485 if details not in _extended_types:
485 if details not in _extended_types:
486 raise JSONRPCError(
486 raise JSONRPCError(
487 'ret_type must be one of %s' % (','.join(_extended_types)))
487 'ret_type must be one of %s' % (','.join(_extended_types)))
488 extended_info = False
488 extended_info = False
489 content = False
489 content = False
490 if details == 'basic':
490 if details == 'basic':
491 extended_info = True
491 extended_info = True
492
492
493 if details == 'full':
493 if details == 'full':
494 extended_info = content = True
494 extended_info = content = True
495
495
496 _map = {}
496 _map = {}
497 try:
497 try:
498 # check if repo is not empty by any chance, skip quicker if it is.
498 # check if repo is not empty by any chance, skip quicker if it is.
499 _scm = repo.scm_instance()
499 _scm = repo.scm_instance()
500 if _scm.is_empty():
500 if _scm.is_empty():
501 return []
501 return []
502
502
503 _d, _f = ScmModel().get_nodes(
503 _d, _f = ScmModel().get_nodes(
504 repo, revision, root_path, flat=False,
504 repo, revision, root_path, flat=False,
505 extended_info=extended_info, content=content,
505 extended_info=extended_info, content=content,
506 max_file_bytes=max_file_bytes)
506 max_file_bytes=max_file_bytes)
507 _map = {
507 _map = {
508 'all': _d + _f,
508 'all': _d + _f,
509 'files': _f,
509 'files': _f,
510 'dirs': _d,
510 'dirs': _d,
511 }
511 }
512 return _map[ret_type]
512 return _map[ret_type]
513 except KeyError:
513 except KeyError:
514 raise JSONRPCError(
514 raise JSONRPCError(
515 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
515 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
516 except Exception:
516 except Exception:
517 log.exception("Exception occurred while trying to get repo nodes")
517 log.exception("Exception occurred while trying to get repo nodes")
518 raise JSONRPCError(
518 raise JSONRPCError(
519 'failed to get repo: `%s` nodes' % repo.repo_name
519 'failed to get repo: `%s` nodes' % repo.repo_name
520 )
520 )
521
521
522
522
523 @jsonrpc_method()
523 @jsonrpc_method()
524 def get_repo_refs(request, apiuser, repoid):
524 def get_repo_refs(request, apiuser, repoid):
525 """
525 """
526 Returns a dictionary of current references. It returns
526 Returns a dictionary of current references. It returns
527 bookmarks, branches, closed_branches, and tags for given repository
527 bookmarks, branches, closed_branches, and tags for given repository
528
528
529 It's possible to specify ret_type to show only `files` or `dirs`.
529 It's possible to specify ret_type to show only `files` or `dirs`.
530
530
531 This command can only be run using an |authtoken| with admin rights,
531 This command can only be run using an |authtoken| with admin rights,
532 or users with at least read rights to |repos|.
532 or users with at least read rights to |repos|.
533
533
534 :param apiuser: This is filled automatically from the |authtoken|.
534 :param apiuser: This is filled automatically from the |authtoken|.
535 :type apiuser: AuthUser
535 :type apiuser: AuthUser
536 :param repoid: The repository name or repository ID.
536 :param repoid: The repository name or repository ID.
537 :type repoid: str or int
537 :type repoid: str or int
538
538
539 Example output:
539 Example output:
540
540
541 .. code-block:: bash
541 .. code-block:: bash
542
542
543 id : <id_given_in_input>
543 id : <id_given_in_input>
544 "result": {
544 "result": {
545 "bookmarks": {
545 "bookmarks": {
546 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
546 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
547 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
547 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
548 },
548 },
549 "branches": {
549 "branches": {
550 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
550 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
551 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
551 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
552 },
552 },
553 "branches_closed": {},
553 "branches_closed": {},
554 "tags": {
554 "tags": {
555 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
555 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
556 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
556 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
557 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
557 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
558 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
558 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
559 }
559 }
560 }
560 }
561 error: null
561 error: null
562 """
562 """
563
563
564 repo = get_repo_or_error(repoid)
564 repo = get_repo_or_error(repoid)
565 if not has_superadmin_permission(apiuser):
565 if not has_superadmin_permission(apiuser):
566 _perms = ('repository.admin', 'repository.write', 'repository.read',)
566 _perms = ('repository.admin', 'repository.write', 'repository.read',)
567 validate_repo_permissions(apiuser, repoid, repo, _perms)
567 validate_repo_permissions(apiuser, repoid, repo, _perms)
568
568
569 try:
569 try:
570 # check if repo is not empty by any chance, skip quicker if it is.
570 # check if repo is not empty by any chance, skip quicker if it is.
571 vcs_instance = repo.scm_instance()
571 vcs_instance = repo.scm_instance()
572 refs = vcs_instance.refs()
572 refs = vcs_instance.refs()
573 return refs
573 return refs
574 except Exception:
574 except Exception:
575 log.exception("Exception occurred while trying to get repo refs")
575 log.exception("Exception occurred while trying to get repo refs")
576 raise JSONRPCError(
576 raise JSONRPCError(
577 'failed to get repo: `%s` references' % repo.repo_name
577 'failed to get repo: `%s` references' % repo.repo_name
578 )
578 )
579
579
580
580
581 @jsonrpc_method()
581 @jsonrpc_method()
582 def create_repo(
582 def create_repo(
583 request, apiuser, repo_name, repo_type,
583 request, apiuser, repo_name, repo_type,
584 owner=Optional(OAttr('apiuser')),
584 owner=Optional(OAttr('apiuser')),
585 description=Optional(''),
585 description=Optional(''),
586 private=Optional(False),
586 private=Optional(False),
587 clone_uri=Optional(None),
587 clone_uri=Optional(None),
588 landing_rev=Optional('rev:tip'),
588 landing_rev=Optional('rev:tip'),
589 enable_statistics=Optional(False),
589 enable_statistics=Optional(False),
590 enable_locking=Optional(False),
590 enable_locking=Optional(False),
591 enable_downloads=Optional(False),
591 enable_downloads=Optional(False),
592 copy_permissions=Optional(False)):
592 copy_permissions=Optional(False)):
593 """
593 """
594 Creates a repository.
594 Creates a repository.
595
595
596 * If the repository name contains "/", repository will be created inside
596 * If the repository name contains "/", repository will be created inside
597 a repository group or nested repository groups
597 a repository group or nested repository groups
598
598
599 For example "foo/bar/repo1" will create |repo| called "repo1" inside
599 For example "foo/bar/repo1" will create |repo| called "repo1" inside
600 group "foo/bar". You have to have permissions to access and write to
600 group "foo/bar". You have to have permissions to access and write to
601 the last repository group ("bar" in this example)
601 the last repository group ("bar" in this example)
602
602
603 This command can only be run using an |authtoken| with at least
603 This command can only be run using an |authtoken| with at least
604 permissions to create repositories, or write permissions to
604 permissions to create repositories, or write permissions to
605 parent repository groups.
605 parent repository groups.
606
606
607 :param apiuser: This is filled automatically from the |authtoken|.
607 :param apiuser: This is filled automatically from the |authtoken|.
608 :type apiuser: AuthUser
608 :type apiuser: AuthUser
609 :param repo_name: Set the repository name.
609 :param repo_name: Set the repository name.
610 :type repo_name: str
610 :type repo_name: str
611 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
611 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
612 :type repo_type: str
612 :type repo_type: str
613 :param owner: user_id or username
613 :param owner: user_id or username
614 :type owner: Optional(str)
614 :type owner: Optional(str)
615 :param description: Set the repository description.
615 :param description: Set the repository description.
616 :type description: Optional(str)
616 :type description: Optional(str)
617 :param private: set repository as private
617 :param private: set repository as private
618 :type private: bool
618 :type private: bool
619 :param clone_uri: set clone_uri
619 :param clone_uri: set clone_uri
620 :type clone_uri: str
620 :type clone_uri: str
621 :param landing_rev: <rev_type>:<rev>
621 :param landing_rev: <rev_type>:<rev>
622 :type landing_rev: str
622 :type landing_rev: str
623 :param enable_locking:
623 :param enable_locking:
624 :type enable_locking: bool
624 :type enable_locking: bool
625 :param enable_downloads:
625 :param enable_downloads:
626 :type enable_downloads: bool
626 :type enable_downloads: bool
627 :param enable_statistics:
627 :param enable_statistics:
628 :type enable_statistics: bool
628 :type enable_statistics: bool
629 :param copy_permissions: Copy permission from group in which the
629 :param copy_permissions: Copy permission from group in which the
630 repository is being created.
630 repository is being created.
631 :type copy_permissions: bool
631 :type copy_permissions: bool
632
632
633
633
634 Example output:
634 Example output:
635
635
636 .. code-block:: bash
636 .. code-block:: bash
637
637
638 id : <id_given_in_input>
638 id : <id_given_in_input>
639 result: {
639 result: {
640 "msg": "Created new repository `<reponame>`",
640 "msg": "Created new repository `<reponame>`",
641 "success": true,
641 "success": true,
642 "task": "<celery task id or None if done sync>"
642 "task": "<celery task id or None if done sync>"
643 }
643 }
644 error: null
644 error: null
645
645
646
646
647 Example error output:
647 Example error output:
648
648
649 .. code-block:: bash
649 .. code-block:: bash
650
650
651 id : <id_given_in_input>
651 id : <id_given_in_input>
652 result : null
652 result : null
653 error : {
653 error : {
654 'failed to create repository `<repo_name>`'
654 'failed to create repository `<repo_name>`'
655 }
655 }
656
656
657 """
657 """
658
658
659 owner = validate_set_owner_permissions(apiuser, owner)
659 owner = validate_set_owner_permissions(apiuser, owner)
660
660
661 description = Optional.extract(description)
661 description = Optional.extract(description)
662 copy_permissions = Optional.extract(copy_permissions)
662 copy_permissions = Optional.extract(copy_permissions)
663 clone_uri = Optional.extract(clone_uri)
663 clone_uri = Optional.extract(clone_uri)
664 landing_commit_ref = Optional.extract(landing_rev)
664 landing_commit_ref = Optional.extract(landing_rev)
665
665
666 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
666 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
667 if isinstance(private, Optional):
667 if isinstance(private, Optional):
668 private = defs.get('repo_private') or Optional.extract(private)
668 private = defs.get('repo_private') or Optional.extract(private)
669 if isinstance(repo_type, Optional):
669 if isinstance(repo_type, Optional):
670 repo_type = defs.get('repo_type')
670 repo_type = defs.get('repo_type')
671 if isinstance(enable_statistics, Optional):
671 if isinstance(enable_statistics, Optional):
672 enable_statistics = defs.get('repo_enable_statistics')
672 enable_statistics = defs.get('repo_enable_statistics')
673 if isinstance(enable_locking, Optional):
673 if isinstance(enable_locking, Optional):
674 enable_locking = defs.get('repo_enable_locking')
674 enable_locking = defs.get('repo_enable_locking')
675 if isinstance(enable_downloads, Optional):
675 if isinstance(enable_downloads, Optional):
676 enable_downloads = defs.get('repo_enable_downloads')
676 enable_downloads = defs.get('repo_enable_downloads')
677
677
678 schema = repo_schema.RepoSchema().bind(
678 schema = repo_schema.RepoSchema().bind(
679 repo_type_options=rhodecode.BACKENDS.keys(),
679 repo_type_options=rhodecode.BACKENDS.keys(),
680 # user caller
680 # user caller
681 user=apiuser)
681 user=apiuser)
682
682
683 try:
683 try:
684 schema_data = schema.deserialize(dict(
684 schema_data = schema.deserialize(dict(
685 repo_name=repo_name,
685 repo_name=repo_name,
686 repo_type=repo_type,
686 repo_type=repo_type,
687 repo_owner=owner.username,
687 repo_owner=owner.username,
688 repo_description=description,
688 repo_description=description,
689 repo_landing_commit_ref=landing_commit_ref,
689 repo_landing_commit_ref=landing_commit_ref,
690 repo_clone_uri=clone_uri,
690 repo_clone_uri=clone_uri,
691 repo_private=private,
691 repo_private=private,
692 repo_copy_permissions=copy_permissions,
692 repo_copy_permissions=copy_permissions,
693 repo_enable_statistics=enable_statistics,
693 repo_enable_statistics=enable_statistics,
694 repo_enable_downloads=enable_downloads,
694 repo_enable_downloads=enable_downloads,
695 repo_enable_locking=enable_locking))
695 repo_enable_locking=enable_locking))
696 except validation_schema.Invalid as err:
696 except validation_schema.Invalid as err:
697 raise JSONRPCValidationError(colander_exc=err)
697 raise JSONRPCValidationError(colander_exc=err)
698
698
699 try:
699 try:
700 data = {
700 data = {
701 'owner': owner,
701 'owner': owner,
702 'repo_name': schema_data['repo_group']['repo_name_without_group'],
702 'repo_name': schema_data['repo_group']['repo_name_without_group'],
703 'repo_name_full': schema_data['repo_name'],
703 'repo_name_full': schema_data['repo_name'],
704 'repo_group': schema_data['repo_group']['repo_group_id'],
704 'repo_group': schema_data['repo_group']['repo_group_id'],
705 'repo_type': schema_data['repo_type'],
705 'repo_type': schema_data['repo_type'],
706 'repo_description': schema_data['repo_description'],
706 'repo_description': schema_data['repo_description'],
707 'repo_private': schema_data['repo_private'],
707 'repo_private': schema_data['repo_private'],
708 'clone_uri': schema_data['repo_clone_uri'],
708 'clone_uri': schema_data['repo_clone_uri'],
709 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
709 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
710 'enable_statistics': schema_data['repo_enable_statistics'],
710 'enable_statistics': schema_data['repo_enable_statistics'],
711 'enable_locking': schema_data['repo_enable_locking'],
711 'enable_locking': schema_data['repo_enable_locking'],
712 'enable_downloads': schema_data['repo_enable_downloads'],
712 'enable_downloads': schema_data['repo_enable_downloads'],
713 'repo_copy_permissions': schema_data['repo_copy_permissions'],
713 'repo_copy_permissions': schema_data['repo_copy_permissions'],
714 }
714 }
715
715
716 task = RepoModel().create(form_data=data, cur_user=owner)
716 task = RepoModel().create(form_data=data, cur_user=owner)
717 from celery.result import BaseAsyncResult
717 from celery.result import BaseAsyncResult
718 task_id = None
718 task_id = None
719 if isinstance(task, BaseAsyncResult):
719 if isinstance(task, BaseAsyncResult):
720 task_id = task.task_id
720 task_id = task.task_id
721 # no commit, it's done in RepoModel, or async via celery
721 # no commit, it's done in RepoModel, or async via celery
722 return {
722 return {
723 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
723 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
724 'success': True, # cannot return the repo data here since fork
724 'success': True, # cannot return the repo data here since fork
725 # can be done async
725 # can be done async
726 'task': task_id
726 'task': task_id
727 }
727 }
728 except Exception:
728 except Exception:
729 log.exception(
729 log.exception(
730 u"Exception while trying to create the repository %s",
730 u"Exception while trying to create the repository %s",
731 schema_data['repo_name'])
731 schema_data['repo_name'])
732 raise JSONRPCError(
732 raise JSONRPCError(
733 'failed to create repository `%s`' % (schema_data['repo_name'],))
733 'failed to create repository `%s`' % (schema_data['repo_name'],))
734
734
735
735
736 @jsonrpc_method()
736 @jsonrpc_method()
737 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
737 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
738 description=Optional('')):
738 description=Optional('')):
739 """
739 """
740 Adds an extra field to a repository.
740 Adds an extra field to a repository.
741
741
742 This command can only be run using an |authtoken| with at least
742 This command can only be run using an |authtoken| with at least
743 write permissions to the |repo|.
743 write permissions to the |repo|.
744
744
745 :param apiuser: This is filled automatically from the |authtoken|.
745 :param apiuser: This is filled automatically from the |authtoken|.
746 :type apiuser: AuthUser
746 :type apiuser: AuthUser
747 :param repoid: Set the repository name or repository id.
747 :param repoid: Set the repository name or repository id.
748 :type repoid: str or int
748 :type repoid: str or int
749 :param key: Create a unique field key for this repository.
749 :param key: Create a unique field key for this repository.
750 :type key: str
750 :type key: str
751 :param label:
751 :param label:
752 :type label: Optional(str)
752 :type label: Optional(str)
753 :param description:
753 :param description:
754 :type description: Optional(str)
754 :type description: Optional(str)
755 """
755 """
756 repo = get_repo_or_error(repoid)
756 repo = get_repo_or_error(repoid)
757 if not has_superadmin_permission(apiuser):
757 if not has_superadmin_permission(apiuser):
758 _perms = ('repository.admin',)
758 _perms = ('repository.admin',)
759 validate_repo_permissions(apiuser, repoid, repo, _perms)
759 validate_repo_permissions(apiuser, repoid, repo, _perms)
760
760
761 label = Optional.extract(label) or key
761 label = Optional.extract(label) or key
762 description = Optional.extract(description)
762 description = Optional.extract(description)
763
763
764 field = RepositoryField.get_by_key_name(key, repo)
764 field = RepositoryField.get_by_key_name(key, repo)
765 if field:
765 if field:
766 raise JSONRPCError('Field with key '
766 raise JSONRPCError('Field with key '
767 '`%s` exists for repo `%s`' % (key, repoid))
767 '`%s` exists for repo `%s`' % (key, repoid))
768
768
769 try:
769 try:
770 RepoModel().add_repo_field(repo, key, field_label=label,
770 RepoModel().add_repo_field(repo, key, field_label=label,
771 field_desc=description)
771 field_desc=description)
772 Session().commit()
772 Session().commit()
773 return {
773 return {
774 'msg': "Added new repository field `%s`" % (key,),
774 'msg': "Added new repository field `%s`" % (key,),
775 'success': True,
775 'success': True,
776 }
776 }
777 except Exception:
777 except Exception:
778 log.exception("Exception occurred while trying to add field to repo")
778 log.exception("Exception occurred while trying to add field to repo")
779 raise JSONRPCError(
779 raise JSONRPCError(
780 'failed to create new field for repository `%s`' % (repoid,))
780 'failed to create new field for repository `%s`' % (repoid,))
781
781
782
782
783 @jsonrpc_method()
783 @jsonrpc_method()
784 def remove_field_from_repo(request, apiuser, repoid, key):
784 def remove_field_from_repo(request, apiuser, repoid, key):
785 """
785 """
786 Removes an extra field from a repository.
786 Removes an extra field from a repository.
787
787
788 This command can only be run using an |authtoken| with at least
788 This command can only be run using an |authtoken| with at least
789 write permissions to the |repo|.
789 write permissions to the |repo|.
790
790
791 :param apiuser: This is filled automatically from the |authtoken|.
791 :param apiuser: This is filled automatically from the |authtoken|.
792 :type apiuser: AuthUser
792 :type apiuser: AuthUser
793 :param repoid: Set the repository name or repository ID.
793 :param repoid: Set the repository name or repository ID.
794 :type repoid: str or int
794 :type repoid: str or int
795 :param key: Set the unique field key for this repository.
795 :param key: Set the unique field key for this repository.
796 :type key: str
796 :type key: str
797 """
797 """
798
798
799 repo = get_repo_or_error(repoid)
799 repo = get_repo_or_error(repoid)
800 if not has_superadmin_permission(apiuser):
800 if not has_superadmin_permission(apiuser):
801 _perms = ('repository.admin',)
801 _perms = ('repository.admin',)
802 validate_repo_permissions(apiuser, repoid, repo, _perms)
802 validate_repo_permissions(apiuser, repoid, repo, _perms)
803
803
804 field = RepositoryField.get_by_key_name(key, repo)
804 field = RepositoryField.get_by_key_name(key, repo)
805 if not field:
805 if not field:
806 raise JSONRPCError('Field with key `%s` does not '
806 raise JSONRPCError('Field with key `%s` does not '
807 'exists for repo `%s`' % (key, repoid))
807 'exists for repo `%s`' % (key, repoid))
808
808
809 try:
809 try:
810 RepoModel().delete_repo_field(repo, field_key=key)
810 RepoModel().delete_repo_field(repo, field_key=key)
811 Session().commit()
811 Session().commit()
812 return {
812 return {
813 'msg': "Deleted repository field `%s`" % (key,),
813 'msg': "Deleted repository field `%s`" % (key,),
814 'success': True,
814 'success': True,
815 }
815 }
816 except Exception:
816 except Exception:
817 log.exception(
817 log.exception(
818 "Exception occurred while trying to delete field from repo")
818 "Exception occurred while trying to delete field from repo")
819 raise JSONRPCError(
819 raise JSONRPCError(
820 'failed to delete field for repository `%s`' % (repoid,))
820 'failed to delete field for repository `%s`' % (repoid,))
821
821
822
822
823 @jsonrpc_method()
823 @jsonrpc_method()
824 def update_repo(
824 def update_repo(
825 request, apiuser, repoid, repo_name=Optional(None),
825 request, apiuser, repoid, repo_name=Optional(None),
826 owner=Optional(OAttr('apiuser')), description=Optional(''),
826 owner=Optional(OAttr('apiuser')), description=Optional(''),
827 private=Optional(False), clone_uri=Optional(None),
827 private=Optional(False), clone_uri=Optional(None),
828 landing_rev=Optional('rev:tip'), fork_of=Optional(None),
828 landing_rev=Optional('rev:tip'), fork_of=Optional(None),
829 enable_statistics=Optional(False),
829 enable_statistics=Optional(False),
830 enable_locking=Optional(False),
830 enable_locking=Optional(False),
831 enable_downloads=Optional(False), fields=Optional('')):
831 enable_downloads=Optional(False), fields=Optional('')):
832 """
832 """
833 Updates a repository with the given information.
833 Updates a repository with the given information.
834
834
835 This command can only be run using an |authtoken| with at least
835 This command can only be run using an |authtoken| with at least
836 admin permissions to the |repo|.
836 admin permissions to the |repo|.
837
837
838 * If the repository name contains "/", repository will be updated
838 * If the repository name contains "/", repository will be updated
839 accordingly with a repository group or nested repository groups
839 accordingly with a repository group or nested repository groups
840
840
841 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
841 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
842 called "repo-test" and place it inside group "foo/bar".
842 called "repo-test" and place it inside group "foo/bar".
843 You have to have permissions to access and write to the last repository
843 You have to have permissions to access and write to the last repository
844 group ("bar" in this example)
844 group ("bar" in this example)
845
845
846 :param apiuser: This is filled automatically from the |authtoken|.
846 :param apiuser: This is filled automatically from the |authtoken|.
847 :type apiuser: AuthUser
847 :type apiuser: AuthUser
848 :param repoid: repository name or repository ID.
848 :param repoid: repository name or repository ID.
849 :type repoid: str or int
849 :type repoid: str or int
850 :param repo_name: Update the |repo| name, including the
850 :param repo_name: Update the |repo| name, including the
851 repository group it's in.
851 repository group it's in.
852 :type repo_name: str
852 :type repo_name: str
853 :param owner: Set the |repo| owner.
853 :param owner: Set the |repo| owner.
854 :type owner: str
854 :type owner: str
855 :param fork_of: Set the |repo| as fork of another |repo|.
855 :param fork_of: Set the |repo| as fork of another |repo|.
856 :type fork_of: str
856 :type fork_of: str
857 :param description: Update the |repo| description.
857 :param description: Update the |repo| description.
858 :type description: str
858 :type description: str
859 :param private: Set the |repo| as private. (True | False)
859 :param private: Set the |repo| as private. (True | False)
860 :type private: bool
860 :type private: bool
861 :param clone_uri: Update the |repo| clone URI.
861 :param clone_uri: Update the |repo| clone URI.
862 :type clone_uri: str
862 :type clone_uri: str
863 :param landing_rev: Set the |repo| landing revision. Default is ``rev:tip``.
863 :param landing_rev: Set the |repo| landing revision. Default is ``rev:tip``.
864 :type landing_rev: str
864 :type landing_rev: str
865 :param enable_statistics: Enable statistics on the |repo|, (True | False).
865 :param enable_statistics: Enable statistics on the |repo|, (True | False).
866 :type enable_statistics: bool
866 :type enable_statistics: bool
867 :param enable_locking: Enable |repo| locking.
867 :param enable_locking: Enable |repo| locking.
868 :type enable_locking: bool
868 :type enable_locking: bool
869 :param enable_downloads: Enable downloads from the |repo|, (True | False).
869 :param enable_downloads: Enable downloads from the |repo|, (True | False).
870 :type enable_downloads: bool
870 :type enable_downloads: bool
871 :param fields: Add extra fields to the |repo|. Use the following
871 :param fields: Add extra fields to the |repo|. Use the following
872 example format: ``field_key=field_val,field_key2=fieldval2``.
872 example format: ``field_key=field_val,field_key2=fieldval2``.
873 Escape ', ' with \,
873 Escape ', ' with \,
874 :type fields: str
874 :type fields: str
875 """
875 """
876
876
877 repo = get_repo_or_error(repoid)
877 repo = get_repo_or_error(repoid)
878
878
879 include_secrets = False
879 include_secrets = False
880 if not has_superadmin_permission(apiuser):
880 if not has_superadmin_permission(apiuser):
881 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
881 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
882 else:
882 else:
883 include_secrets = True
883 include_secrets = True
884
884
885 updates = dict(
885 updates = dict(
886 repo_name=repo_name
886 repo_name=repo_name
887 if not isinstance(repo_name, Optional) else repo.repo_name,
887 if not isinstance(repo_name, Optional) else repo.repo_name,
888
888
889 fork_id=fork_of
889 fork_id=fork_of
890 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
890 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
891
891
892 user=owner
892 user=owner
893 if not isinstance(owner, Optional) else repo.user.username,
893 if not isinstance(owner, Optional) else repo.user.username,
894
894
895 repo_description=description
895 repo_description=description
896 if not isinstance(description, Optional) else repo.description,
896 if not isinstance(description, Optional) else repo.description,
897
897
898 repo_private=private
898 repo_private=private
899 if not isinstance(private, Optional) else repo.private,
899 if not isinstance(private, Optional) else repo.private,
900
900
901 clone_uri=clone_uri
901 clone_uri=clone_uri
902 if not isinstance(clone_uri, Optional) else repo.clone_uri,
902 if not isinstance(clone_uri, Optional) else repo.clone_uri,
903
903
904 repo_landing_rev=landing_rev
904 repo_landing_rev=landing_rev
905 if not isinstance(landing_rev, Optional) else repo._landing_revision,
905 if not isinstance(landing_rev, Optional) else repo._landing_revision,
906
906
907 repo_enable_statistics=enable_statistics
907 repo_enable_statistics=enable_statistics
908 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
908 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
909
909
910 repo_enable_locking=enable_locking
910 repo_enable_locking=enable_locking
911 if not isinstance(enable_locking, Optional) else repo.enable_locking,
911 if not isinstance(enable_locking, Optional) else repo.enable_locking,
912
912
913 repo_enable_downloads=enable_downloads
913 repo_enable_downloads=enable_downloads
914 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
914 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
915
915
916 ref_choices, _labels = ScmModel().get_repo_landing_revs(repo=repo)
916 ref_choices, _labels = ScmModel().get_repo_landing_revs(repo=repo)
917
917
918 schema = repo_schema.RepoSchema().bind(
918 schema = repo_schema.RepoSchema().bind(
919 repo_type_options=rhodecode.BACKENDS.keys(),
919 repo_type_options=rhodecode.BACKENDS.keys(),
920 repo_ref_options=ref_choices,
920 repo_ref_options=ref_choices,
921 # user caller
921 # user caller
922 user=apiuser,
922 user=apiuser,
923 old_values=repo.get_api_data())
923 old_values=repo.get_api_data())
924 try:
924 try:
925 schema_data = schema.deserialize(dict(
925 schema_data = schema.deserialize(dict(
926 # we save old value, users cannot change type
926 # we save old value, users cannot change type
927 repo_type=repo.repo_type,
927 repo_type=repo.repo_type,
928
928
929 repo_name=updates['repo_name'],
929 repo_name=updates['repo_name'],
930 repo_owner=updates['user'],
930 repo_owner=updates['user'],
931 repo_description=updates['repo_description'],
931 repo_description=updates['repo_description'],
932 repo_clone_uri=updates['clone_uri'],
932 repo_clone_uri=updates['clone_uri'],
933 repo_fork_of=updates['fork_id'],
933 repo_fork_of=updates['fork_id'],
934 repo_private=updates['repo_private'],
934 repo_private=updates['repo_private'],
935 repo_landing_commit_ref=updates['repo_landing_rev'],
935 repo_landing_commit_ref=updates['repo_landing_rev'],
936 repo_enable_statistics=updates['repo_enable_statistics'],
936 repo_enable_statistics=updates['repo_enable_statistics'],
937 repo_enable_downloads=updates['repo_enable_downloads'],
937 repo_enable_downloads=updates['repo_enable_downloads'],
938 repo_enable_locking=updates['repo_enable_locking']))
938 repo_enable_locking=updates['repo_enable_locking']))
939 except validation_schema.Invalid as err:
939 except validation_schema.Invalid as err:
940 raise JSONRPCValidationError(colander_exc=err)
940 raise JSONRPCValidationError(colander_exc=err)
941
941
942 # save validated data back into the updates dict
942 # save validated data back into the updates dict
943 validated_updates = dict(
943 validated_updates = dict(
944 repo_name=schema_data['repo_group']['repo_name_without_group'],
944 repo_name=schema_data['repo_group']['repo_name_without_group'],
945 repo_group=schema_data['repo_group']['repo_group_id'],
945 repo_group=schema_data['repo_group']['repo_group_id'],
946
946
947 user=schema_data['repo_owner'],
947 user=schema_data['repo_owner'],
948 repo_description=schema_data['repo_description'],
948 repo_description=schema_data['repo_description'],
949 repo_private=schema_data['repo_private'],
949 repo_private=schema_data['repo_private'],
950 clone_uri=schema_data['repo_clone_uri'],
950 clone_uri=schema_data['repo_clone_uri'],
951 repo_landing_rev=schema_data['repo_landing_commit_ref'],
951 repo_landing_rev=schema_data['repo_landing_commit_ref'],
952 repo_enable_statistics=schema_data['repo_enable_statistics'],
952 repo_enable_statistics=schema_data['repo_enable_statistics'],
953 repo_enable_locking=schema_data['repo_enable_locking'],
953 repo_enable_locking=schema_data['repo_enable_locking'],
954 repo_enable_downloads=schema_data['repo_enable_downloads'],
954 repo_enable_downloads=schema_data['repo_enable_downloads'],
955 )
955 )
956
956
957 if schema_data['repo_fork_of']:
957 if schema_data['repo_fork_of']:
958 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
958 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
959 validated_updates['fork_id'] = fork_repo.repo_id
959 validated_updates['fork_id'] = fork_repo.repo_id
960
960
961 # extra fields
961 # extra fields
962 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
962 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
963 if fields:
963 if fields:
964 validated_updates.update(fields)
964 validated_updates.update(fields)
965
965
966 try:
966 try:
967 RepoModel().update(repo, **validated_updates)
967 RepoModel().update(repo, **validated_updates)
968 Session().commit()
968 Session().commit()
969 return {
969 return {
970 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
970 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
971 'repository': repo.get_api_data(include_secrets=include_secrets)
971 'repository': repo.get_api_data(include_secrets=include_secrets)
972 }
972 }
973 except Exception:
973 except Exception:
974 log.exception(
974 log.exception(
975 u"Exception while trying to update the repository %s",
975 u"Exception while trying to update the repository %s",
976 repoid)
976 repoid)
977 raise JSONRPCError('failed to update repo `%s`' % repoid)
977 raise JSONRPCError('failed to update repo `%s`' % repoid)
978
978
979
979
980 @jsonrpc_method()
980 @jsonrpc_method()
981 def fork_repo(request, apiuser, repoid, fork_name,
981 def fork_repo(request, apiuser, repoid, fork_name,
982 owner=Optional(OAttr('apiuser')),
982 owner=Optional(OAttr('apiuser')),
983 description=Optional(''),
983 description=Optional(''),
984 private=Optional(False),
984 private=Optional(False),
985 clone_uri=Optional(None),
985 clone_uri=Optional(None),
986 landing_rev=Optional('rev:tip'),
986 landing_rev=Optional('rev:tip'),
987 copy_permissions=Optional(False)):
987 copy_permissions=Optional(False)):
988 """
988 """
989 Creates a fork of the specified |repo|.
989 Creates a fork of the specified |repo|.
990
990
991 * If the fork_name contains "/", fork will be created inside
991 * If the fork_name contains "/", fork will be created inside
992 a repository group or nested repository groups
992 a repository group or nested repository groups
993
993
994 For example "foo/bar/fork-repo" will create fork called "fork-repo"
994 For example "foo/bar/fork-repo" will create fork called "fork-repo"
995 inside group "foo/bar". You have to have permissions to access and
995 inside group "foo/bar". You have to have permissions to access and
996 write to the last repository group ("bar" in this example)
996 write to the last repository group ("bar" in this example)
997
997
998 This command can only be run using an |authtoken| with minimum
998 This command can only be run using an |authtoken| with minimum
999 read permissions of the forked repo, create fork permissions for an user.
999 read permissions of the forked repo, create fork permissions for an user.
1000
1000
1001 :param apiuser: This is filled automatically from the |authtoken|.
1001 :param apiuser: This is filled automatically from the |authtoken|.
1002 :type apiuser: AuthUser
1002 :type apiuser: AuthUser
1003 :param repoid: Set repository name or repository ID.
1003 :param repoid: Set repository name or repository ID.
1004 :type repoid: str or int
1004 :type repoid: str or int
1005 :param fork_name: Set the fork name, including it's repository group membership.
1005 :param fork_name: Set the fork name, including it's repository group membership.
1006 :type fork_name: str
1006 :type fork_name: str
1007 :param owner: Set the fork owner.
1007 :param owner: Set the fork owner.
1008 :type owner: str
1008 :type owner: str
1009 :param description: Set the fork description.
1009 :param description: Set the fork description.
1010 :type description: str
1010 :type description: str
1011 :param copy_permissions: Copy permissions from parent |repo|. The
1011 :param copy_permissions: Copy permissions from parent |repo|. The
1012 default is False.
1012 default is False.
1013 :type copy_permissions: bool
1013 :type copy_permissions: bool
1014 :param private: Make the fork private. The default is False.
1014 :param private: Make the fork private. The default is False.
1015 :type private: bool
1015 :type private: bool
1016 :param landing_rev: Set the landing revision. The default is tip.
1016 :param landing_rev: Set the landing revision. The default is tip.
1017
1017
1018 Example output:
1018 Example output:
1019
1019
1020 .. code-block:: bash
1020 .. code-block:: bash
1021
1021
1022 id : <id_for_response>
1022 id : <id_for_response>
1023 api_key : "<api_key>"
1023 api_key : "<api_key>"
1024 args: {
1024 args: {
1025 "repoid" : "<reponame or repo_id>",
1025 "repoid" : "<reponame or repo_id>",
1026 "fork_name": "<forkname>",
1026 "fork_name": "<forkname>",
1027 "owner": "<username or user_id = Optional(=apiuser)>",
1027 "owner": "<username or user_id = Optional(=apiuser)>",
1028 "description": "<description>",
1028 "description": "<description>",
1029 "copy_permissions": "<bool>",
1029 "copy_permissions": "<bool>",
1030 "private": "<bool>",
1030 "private": "<bool>",
1031 "landing_rev": "<landing_rev>"
1031 "landing_rev": "<landing_rev>"
1032 }
1032 }
1033
1033
1034 Example error output:
1034 Example error output:
1035
1035
1036 .. code-block:: bash
1036 .. code-block:: bash
1037
1037
1038 id : <id_given_in_input>
1038 id : <id_given_in_input>
1039 result: {
1039 result: {
1040 "msg": "Created fork of `<reponame>` as `<forkname>`",
1040 "msg": "Created fork of `<reponame>` as `<forkname>`",
1041 "success": true,
1041 "success": true,
1042 "task": "<celery task id or None if done sync>"
1042 "task": "<celery task id or None if done sync>"
1043 }
1043 }
1044 error: null
1044 error: null
1045
1045
1046 """
1046 """
1047
1047
1048 repo = get_repo_or_error(repoid)
1048 repo = get_repo_or_error(repoid)
1049 repo_name = repo.repo_name
1049 repo_name = repo.repo_name
1050
1050
1051 if not has_superadmin_permission(apiuser):
1051 if not has_superadmin_permission(apiuser):
1052 # check if we have at least read permission for
1052 # check if we have at least read permission for
1053 # this repo that we fork !
1053 # this repo that we fork !
1054 _perms = (
1054 _perms = (
1055 'repository.admin', 'repository.write', 'repository.read')
1055 'repository.admin', 'repository.write', 'repository.read')
1056 validate_repo_permissions(apiuser, repoid, repo, _perms)
1056 validate_repo_permissions(apiuser, repoid, repo, _perms)
1057
1057
1058 # check if the regular user has at least fork permissions as well
1058 # check if the regular user has at least fork permissions as well
1059 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1059 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1060 raise JSONRPCForbidden()
1060 raise JSONRPCForbidden()
1061
1061
1062 # check if user can set owner parameter
1062 # check if user can set owner parameter
1063 owner = validate_set_owner_permissions(apiuser, owner)
1063 owner = validate_set_owner_permissions(apiuser, owner)
1064
1064
1065 description = Optional.extract(description)
1065 description = Optional.extract(description)
1066 copy_permissions = Optional.extract(copy_permissions)
1066 copy_permissions = Optional.extract(copy_permissions)
1067 clone_uri = Optional.extract(clone_uri)
1067 clone_uri = Optional.extract(clone_uri)
1068 landing_commit_ref = Optional.extract(landing_rev)
1068 landing_commit_ref = Optional.extract(landing_rev)
1069 private = Optional.extract(private)
1069 private = Optional.extract(private)
1070
1070
1071 schema = repo_schema.RepoSchema().bind(
1071 schema = repo_schema.RepoSchema().bind(
1072 repo_type_options=rhodecode.BACKENDS.keys(),
1072 repo_type_options=rhodecode.BACKENDS.keys(),
1073 # user caller
1073 # user caller
1074 user=apiuser)
1074 user=apiuser)
1075
1075
1076 try:
1076 try:
1077 schema_data = schema.deserialize(dict(
1077 schema_data = schema.deserialize(dict(
1078 repo_name=fork_name,
1078 repo_name=fork_name,
1079 repo_type=repo.repo_type,
1079 repo_type=repo.repo_type,
1080 repo_owner=owner.username,
1080 repo_owner=owner.username,
1081 repo_description=description,
1081 repo_description=description,
1082 repo_landing_commit_ref=landing_commit_ref,
1082 repo_landing_commit_ref=landing_commit_ref,
1083 repo_clone_uri=clone_uri,
1083 repo_clone_uri=clone_uri,
1084 repo_private=private,
1084 repo_private=private,
1085 repo_copy_permissions=copy_permissions))
1085 repo_copy_permissions=copy_permissions))
1086 except validation_schema.Invalid as err:
1086 except validation_schema.Invalid as err:
1087 raise JSONRPCValidationError(colander_exc=err)
1087 raise JSONRPCValidationError(colander_exc=err)
1088
1088
1089 try:
1089 try:
1090 data = {
1090 data = {
1091 'fork_parent_id': repo.repo_id,
1091 'fork_parent_id': repo.repo_id,
1092
1092
1093 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1093 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1094 'repo_name_full': schema_data['repo_name'],
1094 'repo_name_full': schema_data['repo_name'],
1095 'repo_group': schema_data['repo_group']['repo_group_id'],
1095 'repo_group': schema_data['repo_group']['repo_group_id'],
1096 'repo_type': schema_data['repo_type'],
1096 'repo_type': schema_data['repo_type'],
1097 'description': schema_data['repo_description'],
1097 'description': schema_data['repo_description'],
1098 'private': schema_data['repo_private'],
1098 'private': schema_data['repo_private'],
1099 'copy_permissions': schema_data['repo_copy_permissions'],
1099 'copy_permissions': schema_data['repo_copy_permissions'],
1100 'landing_rev': schema_data['repo_landing_commit_ref'],
1100 'landing_rev': schema_data['repo_landing_commit_ref'],
1101 }
1101 }
1102
1102
1103 task = RepoModel().create_fork(data, cur_user=owner)
1103 task = RepoModel().create_fork(data, cur_user=owner)
1104 # no commit, it's done in RepoModel, or async via celery
1104 # no commit, it's done in RepoModel, or async via celery
1105 from celery.result import BaseAsyncResult
1105 from celery.result import BaseAsyncResult
1106 task_id = None
1106 task_id = None
1107 if isinstance(task, BaseAsyncResult):
1107 if isinstance(task, BaseAsyncResult):
1108 task_id = task.task_id
1108 task_id = task.task_id
1109 return {
1109 return {
1110 'msg': 'Created fork of `%s` as `%s`' % (
1110 'msg': 'Created fork of `%s` as `%s`' % (
1111 repo.repo_name, schema_data['repo_name']),
1111 repo.repo_name, schema_data['repo_name']),
1112 'success': True, # cannot return the repo data here since fork
1112 'success': True, # cannot return the repo data here since fork
1113 # can be done async
1113 # can be done async
1114 'task': task_id
1114 'task': task_id
1115 }
1115 }
1116 except Exception:
1116 except Exception:
1117 log.exception(
1117 log.exception(
1118 u"Exception while trying to create fork %s",
1118 u"Exception while trying to create fork %s",
1119 schema_data['repo_name'])
1119 schema_data['repo_name'])
1120 raise JSONRPCError(
1120 raise JSONRPCError(
1121 'failed to fork repository `%s` as `%s`' % (
1121 'failed to fork repository `%s` as `%s`' % (
1122 repo_name, schema_data['repo_name']))
1122 repo_name, schema_data['repo_name']))
1123
1123
1124
1124
1125 @jsonrpc_method()
1125 @jsonrpc_method()
1126 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1126 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1127 """
1127 """
1128 Deletes a repository.
1128 Deletes a repository.
1129
1129
1130 * When the `forks` parameter is set it's possible to detach or delete
1130 * When the `forks` parameter is set it's possible to detach or delete
1131 forks of deleted repository.
1131 forks of deleted repository.
1132
1132
1133 This command can only be run using an |authtoken| with admin
1133 This command can only be run using an |authtoken| with admin
1134 permissions on the |repo|.
1134 permissions on the |repo|.
1135
1135
1136 :param apiuser: This is filled automatically from the |authtoken|.
1136 :param apiuser: This is filled automatically from the |authtoken|.
1137 :type apiuser: AuthUser
1137 :type apiuser: AuthUser
1138 :param repoid: Set the repository name or repository ID.
1138 :param repoid: Set the repository name or repository ID.
1139 :type repoid: str or int
1139 :type repoid: str or int
1140 :param forks: Set to `detach` or `delete` forks from the |repo|.
1140 :param forks: Set to `detach` or `delete` forks from the |repo|.
1141 :type forks: Optional(str)
1141 :type forks: Optional(str)
1142
1142
1143 Example error output:
1143 Example error output:
1144
1144
1145 .. code-block:: bash
1145 .. code-block:: bash
1146
1146
1147 id : <id_given_in_input>
1147 id : <id_given_in_input>
1148 result: {
1148 result: {
1149 "msg": "Deleted repository `<reponame>`",
1149 "msg": "Deleted repository `<reponame>`",
1150 "success": true
1150 "success": true
1151 }
1151 }
1152 error: null
1152 error: null
1153 """
1153 """
1154
1154
1155 repo = get_repo_or_error(repoid)
1155 repo = get_repo_or_error(repoid)
1156 if not has_superadmin_permission(apiuser):
1156 if not has_superadmin_permission(apiuser):
1157 _perms = ('repository.admin',)
1157 _perms = ('repository.admin',)
1158 validate_repo_permissions(apiuser, repoid, repo, _perms)
1158 validate_repo_permissions(apiuser, repoid, repo, _perms)
1159
1159
1160 try:
1160 try:
1161 handle_forks = Optional.extract(forks)
1161 handle_forks = Optional.extract(forks)
1162 _forks_msg = ''
1162 _forks_msg = ''
1163 _forks = [f for f in repo.forks]
1163 _forks = [f for f in repo.forks]
1164 if handle_forks == 'detach':
1164 if handle_forks == 'detach':
1165 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1165 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1166 elif handle_forks == 'delete':
1166 elif handle_forks == 'delete':
1167 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1167 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1168 elif _forks:
1168 elif _forks:
1169 raise JSONRPCError(
1169 raise JSONRPCError(
1170 'Cannot delete `%s` it still contains attached forks' %
1170 'Cannot delete `%s` it still contains attached forks' %
1171 (repo.repo_name,)
1171 (repo.repo_name,)
1172 )
1172 )
1173
1173
1174 RepoModel().delete(repo, forks=forks)
1174 RepoModel().delete(repo, forks=forks)
1175 Session().commit()
1175 Session().commit()
1176 return {
1176 return {
1177 'msg': 'Deleted repository `%s`%s' % (
1177 'msg': 'Deleted repository `%s`%s' % (
1178 repo.repo_name, _forks_msg),
1178 repo.repo_name, _forks_msg),
1179 'success': True
1179 'success': True
1180 }
1180 }
1181 except Exception:
1181 except Exception:
1182 log.exception("Exception occurred while trying to delete repo")
1182 log.exception("Exception occurred while trying to delete repo")
1183 raise JSONRPCError(
1183 raise JSONRPCError(
1184 'failed to delete repository `%s`' % (repo.repo_name,)
1184 'failed to delete repository `%s`' % (repo.repo_name,)
1185 )
1185 )
1186
1186
1187
1187
1188 #TODO: marcink, change name ?
1188 #TODO: marcink, change name ?
1189 @jsonrpc_method()
1189 @jsonrpc_method()
1190 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1190 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1191 """
1191 """
1192 Invalidates the cache for the specified repository.
1192 Invalidates the cache for the specified repository.
1193
1193
1194 This command can only be run using an |authtoken| with admin rights to
1194 This command can only be run using an |authtoken| with admin rights to
1195 the specified repository.
1195 the specified repository.
1196
1196
1197 This command takes the following options:
1197 This command takes the following options:
1198
1198
1199 :param apiuser: This is filled automatically from |authtoken|.
1199 :param apiuser: This is filled automatically from |authtoken|.
1200 :type apiuser: AuthUser
1200 :type apiuser: AuthUser
1201 :param repoid: Sets the repository name or repository ID.
1201 :param repoid: Sets the repository name or repository ID.
1202 :type repoid: str or int
1202 :type repoid: str or int
1203 :param delete_keys: This deletes the invalidated keys instead of
1203 :param delete_keys: This deletes the invalidated keys instead of
1204 just flagging them.
1204 just flagging them.
1205 :type delete_keys: Optional(``True`` | ``False``)
1205 :type delete_keys: Optional(``True`` | ``False``)
1206
1206
1207 Example output:
1207 Example output:
1208
1208
1209 .. code-block:: bash
1209 .. code-block:: bash
1210
1210
1211 id : <id_given_in_input>
1211 id : <id_given_in_input>
1212 result : {
1212 result : {
1213 'msg': Cache for repository `<repository name>` was invalidated,
1213 'msg': Cache for repository `<repository name>` was invalidated,
1214 'repository': <repository name>
1214 'repository': <repository name>
1215 }
1215 }
1216 error : null
1216 error : null
1217
1217
1218 Example error output:
1218 Example error output:
1219
1219
1220 .. code-block:: bash
1220 .. code-block:: bash
1221
1221
1222 id : <id_given_in_input>
1222 id : <id_given_in_input>
1223 result : null
1223 result : null
1224 error : {
1224 error : {
1225 'Error occurred during cache invalidation action'
1225 'Error occurred during cache invalidation action'
1226 }
1226 }
1227
1227
1228 """
1228 """
1229
1229
1230 repo = get_repo_or_error(repoid)
1230 repo = get_repo_or_error(repoid)
1231 if not has_superadmin_permission(apiuser):
1231 if not has_superadmin_permission(apiuser):
1232 _perms = ('repository.admin', 'repository.write',)
1232 _perms = ('repository.admin', 'repository.write',)
1233 validate_repo_permissions(apiuser, repoid, repo, _perms)
1233 validate_repo_permissions(apiuser, repoid, repo, _perms)
1234
1234
1235 delete = Optional.extract(delete_keys)
1235 delete = Optional.extract(delete_keys)
1236 try:
1236 try:
1237 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1237 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1238 return {
1238 return {
1239 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1239 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1240 'repository': repo.repo_name
1240 'repository': repo.repo_name
1241 }
1241 }
1242 except Exception:
1242 except Exception:
1243 log.exception(
1243 log.exception(
1244 "Exception occurred while trying to invalidate repo cache")
1244 "Exception occurred while trying to invalidate repo cache")
1245 raise JSONRPCError(
1245 raise JSONRPCError(
1246 'Error occurred during cache invalidation action'
1246 'Error occurred during cache invalidation action'
1247 )
1247 )
1248
1248
1249
1249
1250 #TODO: marcink, change name ?
1250 #TODO: marcink, change name ?
1251 @jsonrpc_method()
1251 @jsonrpc_method()
1252 def lock(request, apiuser, repoid, locked=Optional(None),
1252 def lock(request, apiuser, repoid, locked=Optional(None),
1253 userid=Optional(OAttr('apiuser'))):
1253 userid=Optional(OAttr('apiuser'))):
1254 """
1254 """
1255 Sets the lock state of the specified |repo| by the given user.
1255 Sets the lock state of the specified |repo| by the given user.
1256 From more information, see :ref:`repo-locking`.
1256 From more information, see :ref:`repo-locking`.
1257
1257
1258 * If the ``userid`` option is not set, the repository is locked to the
1258 * If the ``userid`` option is not set, the repository is locked to the
1259 user who called the method.
1259 user who called the method.
1260 * If the ``locked`` parameter is not set, the current lock state of the
1260 * If the ``locked`` parameter is not set, the current lock state of the
1261 repository is displayed.
1261 repository is displayed.
1262
1262
1263 This command can only be run using an |authtoken| with admin rights to
1263 This command can only be run using an |authtoken| with admin rights to
1264 the specified repository.
1264 the specified repository.
1265
1265
1266 This command takes the following options:
1266 This command takes the following options:
1267
1267
1268 :param apiuser: This is filled automatically from the |authtoken|.
1268 :param apiuser: This is filled automatically from the |authtoken|.
1269 :type apiuser: AuthUser
1269 :type apiuser: AuthUser
1270 :param repoid: Sets the repository name or repository ID.
1270 :param repoid: Sets the repository name or repository ID.
1271 :type repoid: str or int
1271 :type repoid: str or int
1272 :param locked: Sets the lock state.
1272 :param locked: Sets the lock state.
1273 :type locked: Optional(``True`` | ``False``)
1273 :type locked: Optional(``True`` | ``False``)
1274 :param userid: Set the repository lock to this user.
1274 :param userid: Set the repository lock to this user.
1275 :type userid: Optional(str or int)
1275 :type userid: Optional(str or int)
1276
1276
1277 Example error output:
1277 Example error output:
1278
1278
1279 .. code-block:: bash
1279 .. code-block:: bash
1280
1280
1281 id : <id_given_in_input>
1281 id : <id_given_in_input>
1282 result : {
1282 result : {
1283 'repo': '<reponame>',
1283 'repo': '<reponame>',
1284 'locked': <bool: lock state>,
1284 'locked': <bool: lock state>,
1285 'locked_since': <int: lock timestamp>,
1285 'locked_since': <int: lock timestamp>,
1286 'locked_by': <username of person who made the lock>,
1286 'locked_by': <username of person who made the lock>,
1287 'lock_reason': <str: reason for locking>,
1287 'lock_reason': <str: reason for locking>,
1288 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1288 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1289 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1289 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1290 or
1290 or
1291 'msg': 'Repo `<repository name>` not locked.'
1291 'msg': 'Repo `<repository name>` not locked.'
1292 or
1292 or
1293 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1293 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1294 }
1294 }
1295 error : null
1295 error : null
1296
1296
1297 Example error output:
1297 Example error output:
1298
1298
1299 .. code-block:: bash
1299 .. code-block:: bash
1300
1300
1301 id : <id_given_in_input>
1301 id : <id_given_in_input>
1302 result : null
1302 result : null
1303 error : {
1303 error : {
1304 'Error occurred locking repository `<reponame>`'
1304 'Error occurred locking repository `<reponame>`'
1305 }
1305 }
1306 """
1306 """
1307
1307
1308 repo = get_repo_or_error(repoid)
1308 repo = get_repo_or_error(repoid)
1309 if not has_superadmin_permission(apiuser):
1309 if not has_superadmin_permission(apiuser):
1310 # check if we have at least write permission for this repo !
1310 # check if we have at least write permission for this repo !
1311 _perms = ('repository.admin', 'repository.write',)
1311 _perms = ('repository.admin', 'repository.write',)
1312 validate_repo_permissions(apiuser, repoid, repo, _perms)
1312 validate_repo_permissions(apiuser, repoid, repo, _perms)
1313
1313
1314 # make sure normal user does not pass someone else userid,
1314 # make sure normal user does not pass someone else userid,
1315 # he is not allowed to do that
1315 # he is not allowed to do that
1316 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1316 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1317 raise JSONRPCError('userid is not the same as your user')
1317 raise JSONRPCError('userid is not the same as your user')
1318
1318
1319 if isinstance(userid, Optional):
1319 if isinstance(userid, Optional):
1320 userid = apiuser.user_id
1320 userid = apiuser.user_id
1321
1321
1322 user = get_user_or_error(userid)
1322 user = get_user_or_error(userid)
1323
1323
1324 if isinstance(locked, Optional):
1324 if isinstance(locked, Optional):
1325 lockobj = repo.locked
1325 lockobj = repo.locked
1326
1326
1327 if lockobj[0] is None:
1327 if lockobj[0] is None:
1328 _d = {
1328 _d = {
1329 'repo': repo.repo_name,
1329 'repo': repo.repo_name,
1330 'locked': False,
1330 'locked': False,
1331 'locked_since': None,
1331 'locked_since': None,
1332 'locked_by': None,
1332 'locked_by': None,
1333 'lock_reason': None,
1333 'lock_reason': None,
1334 'lock_state_changed': False,
1334 'lock_state_changed': False,
1335 'msg': 'Repo `%s` not locked.' % repo.repo_name
1335 'msg': 'Repo `%s` not locked.' % repo.repo_name
1336 }
1336 }
1337 return _d
1337 return _d
1338 else:
1338 else:
1339 _user_id, _time, _reason = lockobj
1339 _user_id, _time, _reason = lockobj
1340 lock_user = get_user_or_error(userid)
1340 lock_user = get_user_or_error(userid)
1341 _d = {
1341 _d = {
1342 'repo': repo.repo_name,
1342 'repo': repo.repo_name,
1343 'locked': True,
1343 'locked': True,
1344 'locked_since': _time,
1344 'locked_since': _time,
1345 'locked_by': lock_user.username,
1345 'locked_by': lock_user.username,
1346 'lock_reason': _reason,
1346 'lock_reason': _reason,
1347 'lock_state_changed': False,
1347 'lock_state_changed': False,
1348 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1348 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1349 % (repo.repo_name, lock_user.username,
1349 % (repo.repo_name, lock_user.username,
1350 json.dumps(time_to_datetime(_time))))
1350 json.dumps(time_to_datetime(_time))))
1351 }
1351 }
1352 return _d
1352 return _d
1353
1353
1354 # force locked state through a flag
1354 # force locked state through a flag
1355 else:
1355 else:
1356 locked = str2bool(locked)
1356 locked = str2bool(locked)
1357 lock_reason = Repository.LOCK_API
1357 lock_reason = Repository.LOCK_API
1358 try:
1358 try:
1359 if locked:
1359 if locked:
1360 lock_time = time.time()
1360 lock_time = time.time()
1361 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1361 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1362 else:
1362 else:
1363 lock_time = None
1363 lock_time = None
1364 Repository.unlock(repo)
1364 Repository.unlock(repo)
1365 _d = {
1365 _d = {
1366 'repo': repo.repo_name,
1366 'repo': repo.repo_name,
1367 'locked': locked,
1367 'locked': locked,
1368 'locked_since': lock_time,
1368 'locked_since': lock_time,
1369 'locked_by': user.username,
1369 'locked_by': user.username,
1370 'lock_reason': lock_reason,
1370 'lock_reason': lock_reason,
1371 'lock_state_changed': True,
1371 'lock_state_changed': True,
1372 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1372 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1373 % (user.username, repo.repo_name, locked))
1373 % (user.username, repo.repo_name, locked))
1374 }
1374 }
1375 return _d
1375 return _d
1376 except Exception:
1376 except Exception:
1377 log.exception(
1377 log.exception(
1378 "Exception occurred while trying to lock repository")
1378 "Exception occurred while trying to lock repository")
1379 raise JSONRPCError(
1379 raise JSONRPCError(
1380 'Error occurred locking repository `%s`' % repo.repo_name
1380 'Error occurred locking repository `%s`' % repo.repo_name
1381 )
1381 )
1382
1382
1383
1383
1384 @jsonrpc_method()
1384 @jsonrpc_method()
1385 def comment_commit(
1385 def comment_commit(
1386 request, apiuser, repoid, commit_id, message, status=Optional(None),
1386 request, apiuser, repoid, commit_id, message, status=Optional(None),
1387 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1387 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1388 resolves_comment_id=Optional(None),
1388 resolves_comment_id=Optional(None),
1389 userid=Optional(OAttr('apiuser'))):
1389 userid=Optional(OAttr('apiuser'))):
1390 """
1390 """
1391 Set a commit comment, and optionally change the status of the commit.
1391 Set a commit comment, and optionally change the status of the commit.
1392
1392
1393 :param apiuser: This is filled automatically from the |authtoken|.
1393 :param apiuser: This is filled automatically from the |authtoken|.
1394 :type apiuser: AuthUser
1394 :type apiuser: AuthUser
1395 :param repoid: Set the repository name or repository ID.
1395 :param repoid: Set the repository name or repository ID.
1396 :type repoid: str or int
1396 :type repoid: str or int
1397 :param commit_id: Specify the commit_id for which to set a comment.
1397 :param commit_id: Specify the commit_id for which to set a comment.
1398 :type commit_id: str
1398 :type commit_id: str
1399 :param message: The comment text.
1399 :param message: The comment text.
1400 :type message: str
1400 :type message: str
1401 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1401 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1402 'approved', 'rejected', 'under_review'
1402 'approved', 'rejected', 'under_review'
1403 :type status: str
1403 :type status: str
1404 :param comment_type: Comment type, one of: 'note', 'todo'
1404 :param comment_type: Comment type, one of: 'note', 'todo'
1405 :type comment_type: Optional(str), default: 'note'
1405 :type comment_type: Optional(str), default: 'note'
1406 :param userid: Set the user name of the comment creator.
1406 :param userid: Set the user name of the comment creator.
1407 :type userid: Optional(str or int)
1407 :type userid: Optional(str or int)
1408
1408
1409 Example error output:
1409 Example error output:
1410
1410
1411 .. code-block:: bash
1411 .. code-block:: bash
1412
1412
1413 {
1413 {
1414 "id" : <id_given_in_input>,
1414 "id" : <id_given_in_input>,
1415 "result" : {
1415 "result" : {
1416 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1416 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1417 "status_change": null or <status>,
1417 "status_change": null or <status>,
1418 "success": true
1418 "success": true
1419 },
1419 },
1420 "error" : null
1420 "error" : null
1421 }
1421 }
1422
1422
1423 """
1423 """
1424 repo = get_repo_or_error(repoid)
1424 repo = get_repo_or_error(repoid)
1425 if not has_superadmin_permission(apiuser):
1425 if not has_superadmin_permission(apiuser):
1426 _perms = ('repository.read', 'repository.write', 'repository.admin')
1426 _perms = ('repository.read', 'repository.write', 'repository.admin')
1427 validate_repo_permissions(apiuser, repoid, repo, _perms)
1427 validate_repo_permissions(apiuser, repoid, repo, _perms)
1428
1428
1429 try:
1429 try:
1430 commit_id = repo.scm_instance().get_commit(commit_id=commit_id).raw_id
1430 commit_id = repo.scm_instance().get_commit(commit_id=commit_id).raw_id
1431 except Exception as e:
1431 except Exception as e:
1432 log.exception('Failed to fetch commit')
1432 log.exception('Failed to fetch commit')
1433 raise JSONRPCError(e.message)
1433 raise JSONRPCError(e.message)
1434
1434
1435 if isinstance(userid, Optional):
1435 if isinstance(userid, Optional):
1436 userid = apiuser.user_id
1436 userid = apiuser.user_id
1437
1437
1438 user = get_user_or_error(userid)
1438 user = get_user_or_error(userid)
1439 status = Optional.extract(status)
1439 status = Optional.extract(status)
1440 comment_type = Optional.extract(comment_type)
1440 comment_type = Optional.extract(comment_type)
1441 resolves_comment_id = Optional.extract(resolves_comment_id)
1441 resolves_comment_id = Optional.extract(resolves_comment_id)
1442
1442
1443 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1443 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1444 if status and status not in allowed_statuses:
1444 if status and status not in allowed_statuses:
1445 raise JSONRPCError('Bad status, must be on '
1445 raise JSONRPCError('Bad status, must be on '
1446 'of %s got %s' % (allowed_statuses, status,))
1446 'of %s got %s' % (allowed_statuses, status,))
1447
1447
1448 if resolves_comment_id:
1448 if resolves_comment_id:
1449 comment = ChangesetComment.get(resolves_comment_id)
1449 comment = ChangesetComment.get(resolves_comment_id)
1450 if not comment:
1450 if not comment:
1451 raise JSONRPCError(
1451 raise JSONRPCError(
1452 'Invalid resolves_comment_id `%s` for this commit.'
1452 'Invalid resolves_comment_id `%s` for this commit.'
1453 % resolves_comment_id)
1453 % resolves_comment_id)
1454 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1454 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1455 raise JSONRPCError(
1455 raise JSONRPCError(
1456 'Comment `%s` is wrong type for setting status to resolved.'
1456 'Comment `%s` is wrong type for setting status to resolved.'
1457 % resolves_comment_id)
1457 % resolves_comment_id)
1458
1458
1459 try:
1459 try:
1460 rc_config = SettingsModel().get_all_settings()
1460 rc_config = SettingsModel().get_all_settings()
1461 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1461 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1462 status_change_label = ChangesetStatus.get_status_lbl(status)
1462 status_change_label = ChangesetStatus.get_status_lbl(status)
1463 comm = CommentsModel().create(
1463 comm = CommentsModel().create(
1464 message, repo, user, commit_id=commit_id,
1464 message, repo, user, commit_id=commit_id,
1465 status_change=status_change_label,
1465 status_change=status_change_label,
1466 status_change_type=status,
1466 status_change_type=status,
1467 renderer=renderer,
1467 renderer=renderer,
1468 comment_type=comment_type,
1468 comment_type=comment_type,
1469 resolves_comment_id=resolves_comment_id
1469 resolves_comment_id=resolves_comment_id
1470 )
1470 )
1471 if status:
1471 if status:
1472 # also do a status change
1472 # also do a status change
1473 try:
1473 try:
1474 ChangesetStatusModel().set_status(
1474 ChangesetStatusModel().set_status(
1475 repo, status, user, comm, revision=commit_id,
1475 repo, status, user, comm, revision=commit_id,
1476 dont_allow_on_closed_pull_request=True
1476 dont_allow_on_closed_pull_request=True
1477 )
1477 )
1478 except StatusChangeOnClosedPullRequestError:
1478 except StatusChangeOnClosedPullRequestError:
1479 log.exception(
1479 log.exception(
1480 "Exception occurred while trying to change repo commit status")
1480 "Exception occurred while trying to change repo commit status")
1481 msg = ('Changing status on a changeset associated with '
1481 msg = ('Changing status on a changeset associated with '
1482 'a closed pull request is not allowed')
1482 'a closed pull request is not allowed')
1483 raise JSONRPCError(msg)
1483 raise JSONRPCError(msg)
1484
1484
1485 Session().commit()
1485 Session().commit()
1486 return {
1486 return {
1487 'msg': (
1487 'msg': (
1488 'Commented on commit `%s` for repository `%s`' % (
1488 'Commented on commit `%s` for repository `%s`' % (
1489 comm.revision, repo.repo_name)),
1489 comm.revision, repo.repo_name)),
1490 'status_change': status,
1490 'status_change': status,
1491 'success': True,
1491 'success': True,
1492 }
1492 }
1493 except JSONRPCError:
1493 except JSONRPCError:
1494 # catch any inside errors, and re-raise them to prevent from
1494 # catch any inside errors, and re-raise them to prevent from
1495 # below global catch to silence them
1495 # below global catch to silence them
1496 raise
1496 raise
1497 except Exception:
1497 except Exception:
1498 log.exception("Exception occurred while trying to comment on commit")
1498 log.exception("Exception occurred while trying to comment on commit")
1499 raise JSONRPCError(
1499 raise JSONRPCError(
1500 'failed to set comment on repository `%s`' % (repo.repo_name,)
1500 'failed to set comment on repository `%s`' % (repo.repo_name,)
1501 )
1501 )
1502
1502
1503
1503
1504 @jsonrpc_method()
1504 @jsonrpc_method()
1505 def grant_user_permission(request, apiuser, repoid, userid, perm):
1505 def grant_user_permission(request, apiuser, repoid, userid, perm):
1506 """
1506 """
1507 Grant permissions for the specified user on the given repository,
1507 Grant permissions for the specified user on the given repository,
1508 or update existing permissions if found.
1508 or update existing permissions if found.
1509
1509
1510 This command can only be run using an |authtoken| with admin
1510 This command can only be run using an |authtoken| with admin
1511 permissions on the |repo|.
1511 permissions on the |repo|.
1512
1512
1513 :param apiuser: This is filled automatically from the |authtoken|.
1513 :param apiuser: This is filled automatically from the |authtoken|.
1514 :type apiuser: AuthUser
1514 :type apiuser: AuthUser
1515 :param repoid: Set the repository name or repository ID.
1515 :param repoid: Set the repository name or repository ID.
1516 :type repoid: str or int
1516 :type repoid: str or int
1517 :param userid: Set the user name.
1517 :param userid: Set the user name.
1518 :type userid: str
1518 :type userid: str
1519 :param perm: Set the user permissions, using the following format
1519 :param perm: Set the user permissions, using the following format
1520 ``(repository.(none|read|write|admin))``
1520 ``(repository.(none|read|write|admin))``
1521 :type perm: str
1521 :type perm: str
1522
1522
1523 Example output:
1523 Example output:
1524
1524
1525 .. code-block:: bash
1525 .. code-block:: bash
1526
1526
1527 id : <id_given_in_input>
1527 id : <id_given_in_input>
1528 result: {
1528 result: {
1529 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1529 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1530 "success": true
1530 "success": true
1531 }
1531 }
1532 error: null
1532 error: null
1533 """
1533 """
1534
1534
1535 repo = get_repo_or_error(repoid)
1535 repo = get_repo_or_error(repoid)
1536 user = get_user_or_error(userid)
1536 user = get_user_or_error(userid)
1537 perm = get_perm_or_error(perm)
1537 perm = get_perm_or_error(perm)
1538 if not has_superadmin_permission(apiuser):
1538 if not has_superadmin_permission(apiuser):
1539 _perms = ('repository.admin',)
1539 _perms = ('repository.admin',)
1540 validate_repo_permissions(apiuser, repoid, repo, _perms)
1540 validate_repo_permissions(apiuser, repoid, repo, _perms)
1541
1541
1542 try:
1542 try:
1543
1543
1544 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
1544 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
1545
1545
1546 Session().commit()
1546 Session().commit()
1547 return {
1547 return {
1548 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1548 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1549 perm.permission_name, user.username, repo.repo_name
1549 perm.permission_name, user.username, repo.repo_name
1550 ),
1550 ),
1551 'success': True
1551 'success': True
1552 }
1552 }
1553 except Exception:
1553 except Exception:
1554 log.exception(
1554 log.exception(
1555 "Exception occurred while trying edit permissions for repo")
1555 "Exception occurred while trying edit permissions for repo")
1556 raise JSONRPCError(
1556 raise JSONRPCError(
1557 'failed to edit permission for user: `%s` in repo: `%s`' % (
1557 'failed to edit permission for user: `%s` in repo: `%s`' % (
1558 userid, repoid
1558 userid, repoid
1559 )
1559 )
1560 )
1560 )
1561
1561
1562
1562
1563 @jsonrpc_method()
1563 @jsonrpc_method()
1564 def revoke_user_permission(request, apiuser, repoid, userid):
1564 def revoke_user_permission(request, apiuser, repoid, userid):
1565 """
1565 """
1566 Revoke permission for a user on the specified repository.
1566 Revoke permission for a user on the specified repository.
1567
1567
1568 This command can only be run using an |authtoken| with admin
1568 This command can only be run using an |authtoken| with admin
1569 permissions on the |repo|.
1569 permissions on the |repo|.
1570
1570
1571 :param apiuser: This is filled automatically from the |authtoken|.
1571 :param apiuser: This is filled automatically from the |authtoken|.
1572 :type apiuser: AuthUser
1572 :type apiuser: AuthUser
1573 :param repoid: Set the repository name or repository ID.
1573 :param repoid: Set the repository name or repository ID.
1574 :type repoid: str or int
1574 :type repoid: str or int
1575 :param userid: Set the user name of revoked user.
1575 :param userid: Set the user name of revoked user.
1576 :type userid: str or int
1576 :type userid: str or int
1577
1577
1578 Example error output:
1578 Example error output:
1579
1579
1580 .. code-block:: bash
1580 .. code-block:: bash
1581
1581
1582 id : <id_given_in_input>
1582 id : <id_given_in_input>
1583 result: {
1583 result: {
1584 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1584 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1585 "success": true
1585 "success": true
1586 }
1586 }
1587 error: null
1587 error: null
1588 """
1588 """
1589
1589
1590 repo = get_repo_or_error(repoid)
1590 repo = get_repo_or_error(repoid)
1591 user = get_user_or_error(userid)
1591 user = get_user_or_error(userid)
1592 if not has_superadmin_permission(apiuser):
1592 if not has_superadmin_permission(apiuser):
1593 _perms = ('repository.admin',)
1593 _perms = ('repository.admin',)
1594 validate_repo_permissions(apiuser, repoid, repo, _perms)
1594 validate_repo_permissions(apiuser, repoid, repo, _perms)
1595
1595
1596 try:
1596 try:
1597 RepoModel().revoke_user_permission(repo=repo, user=user)
1597 RepoModel().revoke_user_permission(repo=repo, user=user)
1598 Session().commit()
1598 Session().commit()
1599 return {
1599 return {
1600 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1600 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1601 user.username, repo.repo_name
1601 user.username, repo.repo_name
1602 ),
1602 ),
1603 'success': True
1603 'success': True
1604 }
1604 }
1605 except Exception:
1605 except Exception:
1606 log.exception(
1606 log.exception(
1607 "Exception occurred while trying revoke permissions to repo")
1607 "Exception occurred while trying revoke permissions to repo")
1608 raise JSONRPCError(
1608 raise JSONRPCError(
1609 'failed to edit permission for user: `%s` in repo: `%s`' % (
1609 'failed to edit permission for user: `%s` in repo: `%s`' % (
1610 userid, repoid
1610 userid, repoid
1611 )
1611 )
1612 )
1612 )
1613
1613
1614
1614
1615 @jsonrpc_method()
1615 @jsonrpc_method()
1616 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1616 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1617 """
1617 """
1618 Grant permission for a user group on the specified repository,
1618 Grant permission for a user group on the specified repository,
1619 or update existing permissions.
1619 or update existing permissions.
1620
1620
1621 This command can only be run using an |authtoken| with admin
1621 This command can only be run using an |authtoken| with admin
1622 permissions on the |repo|.
1622 permissions on the |repo|.
1623
1623
1624 :param apiuser: This is filled automatically from the |authtoken|.
1624 :param apiuser: This is filled automatically from the |authtoken|.
1625 :type apiuser: AuthUser
1625 :type apiuser: AuthUser
1626 :param repoid: Set the repository name or repository ID.
1626 :param repoid: Set the repository name or repository ID.
1627 :type repoid: str or int
1627 :type repoid: str or int
1628 :param usergroupid: Specify the ID of the user group.
1628 :param usergroupid: Specify the ID of the user group.
1629 :type usergroupid: str or int
1629 :type usergroupid: str or int
1630 :param perm: Set the user group permissions using the following
1630 :param perm: Set the user group permissions using the following
1631 format: (repository.(none|read|write|admin))
1631 format: (repository.(none|read|write|admin))
1632 :type perm: str
1632 :type perm: str
1633
1633
1634 Example output:
1634 Example output:
1635
1635
1636 .. code-block:: bash
1636 .. code-block:: bash
1637
1637
1638 id : <id_given_in_input>
1638 id : <id_given_in_input>
1639 result : {
1639 result : {
1640 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1640 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1641 "success": true
1641 "success": true
1642
1642
1643 }
1643 }
1644 error : null
1644 error : null
1645
1645
1646 Example error output:
1646 Example error output:
1647
1647
1648 .. code-block:: bash
1648 .. code-block:: bash
1649
1649
1650 id : <id_given_in_input>
1650 id : <id_given_in_input>
1651 result : null
1651 result : null
1652 error : {
1652 error : {
1653 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1653 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1654 }
1654 }
1655
1655
1656 """
1656 """
1657
1657
1658 repo = get_repo_or_error(repoid)
1658 repo = get_repo_or_error(repoid)
1659 perm = get_perm_or_error(perm)
1659 perm = get_perm_or_error(perm)
1660 if not has_superadmin_permission(apiuser):
1660 if not has_superadmin_permission(apiuser):
1661 _perms = ('repository.admin',)
1661 _perms = ('repository.admin',)
1662 validate_repo_permissions(apiuser, repoid, repo, _perms)
1662 validate_repo_permissions(apiuser, repoid, repo, _perms)
1663
1663
1664 user_group = get_user_group_or_error(usergroupid)
1664 user_group = get_user_group_or_error(usergroupid)
1665 if not has_superadmin_permission(apiuser):
1665 if not has_superadmin_permission(apiuser):
1666 # check if we have at least read permission for this user group !
1666 # check if we have at least read permission for this user group !
1667 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1667 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1668 if not HasUserGroupPermissionAnyApi(*_perms)(
1668 if not HasUserGroupPermissionAnyApi(*_perms)(
1669 user=apiuser, user_group_name=user_group.users_group_name):
1669 user=apiuser, user_group_name=user_group.users_group_name):
1670 raise JSONRPCError(
1670 raise JSONRPCError(
1671 'user group `%s` does not exist' % (usergroupid,))
1671 'user group `%s` does not exist' % (usergroupid,))
1672
1672
1673 try:
1673 try:
1674 RepoModel().grant_user_group_permission(
1674 RepoModel().grant_user_group_permission(
1675 repo=repo, group_name=user_group, perm=perm)
1675 repo=repo, group_name=user_group, perm=perm)
1676
1676
1677 Session().commit()
1677 Session().commit()
1678 return {
1678 return {
1679 'msg': 'Granted perm: `%s` for user group: `%s` in '
1679 'msg': 'Granted perm: `%s` for user group: `%s` in '
1680 'repo: `%s`' % (
1680 'repo: `%s`' % (
1681 perm.permission_name, user_group.users_group_name,
1681 perm.permission_name, user_group.users_group_name,
1682 repo.repo_name
1682 repo.repo_name
1683 ),
1683 ),
1684 'success': True
1684 'success': True
1685 }
1685 }
1686 except Exception:
1686 except Exception:
1687 log.exception(
1687 log.exception(
1688 "Exception occurred while trying change permission on repo")
1688 "Exception occurred while trying change permission on repo")
1689 raise JSONRPCError(
1689 raise JSONRPCError(
1690 'failed to edit permission for user group: `%s` in '
1690 'failed to edit permission for user group: `%s` in '
1691 'repo: `%s`' % (
1691 'repo: `%s`' % (
1692 usergroupid, repo.repo_name
1692 usergroupid, repo.repo_name
1693 )
1693 )
1694 )
1694 )
1695
1695
1696
1696
1697 @jsonrpc_method()
1697 @jsonrpc_method()
1698 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1698 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1699 """
1699 """
1700 Revoke the permissions of a user group on a given repository.
1700 Revoke the permissions of a user group on a given repository.
1701
1701
1702 This command can only be run using an |authtoken| with admin
1702 This command can only be run using an |authtoken| with admin
1703 permissions on the |repo|.
1703 permissions on the |repo|.
1704
1704
1705 :param apiuser: This is filled automatically from the |authtoken|.
1705 :param apiuser: This is filled automatically from the |authtoken|.
1706 :type apiuser: AuthUser
1706 :type apiuser: AuthUser
1707 :param repoid: Set the repository name or repository ID.
1707 :param repoid: Set the repository name or repository ID.
1708 :type repoid: str or int
1708 :type repoid: str or int
1709 :param usergroupid: Specify the user group ID.
1709 :param usergroupid: Specify the user group ID.
1710 :type usergroupid: str or int
1710 :type usergroupid: str or int
1711
1711
1712 Example output:
1712 Example output:
1713
1713
1714 .. code-block:: bash
1714 .. code-block:: bash
1715
1715
1716 id : <id_given_in_input>
1716 id : <id_given_in_input>
1717 result: {
1717 result: {
1718 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1718 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1719 "success": true
1719 "success": true
1720 }
1720 }
1721 error: null
1721 error: null
1722 """
1722 """
1723
1723
1724 repo = get_repo_or_error(repoid)
1724 repo = get_repo_or_error(repoid)
1725 if not has_superadmin_permission(apiuser):
1725 if not has_superadmin_permission(apiuser):
1726 _perms = ('repository.admin',)
1726 _perms = ('repository.admin',)
1727 validate_repo_permissions(apiuser, repoid, repo, _perms)
1727 validate_repo_permissions(apiuser, repoid, repo, _perms)
1728
1728
1729 user_group = get_user_group_or_error(usergroupid)
1729 user_group = get_user_group_or_error(usergroupid)
1730 if not has_superadmin_permission(apiuser):
1730 if not has_superadmin_permission(apiuser):
1731 # check if we have at least read permission for this user group !
1731 # check if we have at least read permission for this user group !
1732 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1732 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1733 if not HasUserGroupPermissionAnyApi(*_perms)(
1733 if not HasUserGroupPermissionAnyApi(*_perms)(
1734 user=apiuser, user_group_name=user_group.users_group_name):
1734 user=apiuser, user_group_name=user_group.users_group_name):
1735 raise JSONRPCError(
1735 raise JSONRPCError(
1736 'user group `%s` does not exist' % (usergroupid,))
1736 'user group `%s` does not exist' % (usergroupid,))
1737
1737
1738 try:
1738 try:
1739 RepoModel().revoke_user_group_permission(
1739 RepoModel().revoke_user_group_permission(
1740 repo=repo, group_name=user_group)
1740 repo=repo, group_name=user_group)
1741
1741
1742 Session().commit()
1742 Session().commit()
1743 return {
1743 return {
1744 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1744 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1745 user_group.users_group_name, repo.repo_name
1745 user_group.users_group_name, repo.repo_name
1746 ),
1746 ),
1747 'success': True
1747 'success': True
1748 }
1748 }
1749 except Exception:
1749 except Exception:
1750 log.exception("Exception occurred while trying revoke "
1750 log.exception("Exception occurred while trying revoke "
1751 "user group permission on repo")
1751 "user group permission on repo")
1752 raise JSONRPCError(
1752 raise JSONRPCError(
1753 'failed to edit permission for user group: `%s` in '
1753 'failed to edit permission for user group: `%s` in '
1754 'repo: `%s`' % (
1754 'repo: `%s`' % (
1755 user_group.users_group_name, repo.repo_name
1755 user_group.users_group_name, repo.repo_name
1756 )
1756 )
1757 )
1757 )
1758
1758
1759
1759
1760 @jsonrpc_method()
1760 @jsonrpc_method()
1761 def pull(request, apiuser, repoid):
1761 def pull(request, apiuser, repoid):
1762 """
1762 """
1763 Triggers a pull on the given repository from a remote location. You
1763 Triggers a pull on the given repository from a remote location. You
1764 can use this to keep remote repositories up-to-date.
1764 can use this to keep remote repositories up-to-date.
1765
1765
1766 This command can only be run using an |authtoken| with admin
1766 This command can only be run using an |authtoken| with admin
1767 rights to the specified repository. For more information,
1767 rights to the specified repository. For more information,
1768 see :ref:`config-token-ref`.
1768 see :ref:`config-token-ref`.
1769
1769
1770 This command takes the following options:
1770 This command takes the following options:
1771
1771
1772 :param apiuser: This is filled automatically from the |authtoken|.
1772 :param apiuser: This is filled automatically from the |authtoken|.
1773 :type apiuser: AuthUser
1773 :type apiuser: AuthUser
1774 :param repoid: The repository name or repository ID.
1774 :param repoid: The repository name or repository ID.
1775 :type repoid: str or int
1775 :type repoid: str or int
1776
1776
1777 Example output:
1777 Example output:
1778
1778
1779 .. code-block:: bash
1779 .. code-block:: bash
1780
1780
1781 id : <id_given_in_input>
1781 id : <id_given_in_input>
1782 result : {
1782 result : {
1783 "msg": "Pulled from `<repository name>`"
1783 "msg": "Pulled from `<repository name>`"
1784 "repository": "<repository name>"
1784 "repository": "<repository name>"
1785 }
1785 }
1786 error : null
1786 error : null
1787
1787
1788 Example error output:
1788 Example error output:
1789
1789
1790 .. code-block:: bash
1790 .. code-block:: bash
1791
1791
1792 id : <id_given_in_input>
1792 id : <id_given_in_input>
1793 result : null
1793 result : null
1794 error : {
1794 error : {
1795 "Unable to pull changes from `<reponame>`"
1795 "Unable to pull changes from `<reponame>`"
1796 }
1796 }
1797
1797
1798 """
1798 """
1799
1799
1800 repo = get_repo_or_error(repoid)
1800 repo = get_repo_or_error(repoid)
1801 if not has_superadmin_permission(apiuser):
1801 if not has_superadmin_permission(apiuser):
1802 _perms = ('repository.admin',)
1802 _perms = ('repository.admin',)
1803 validate_repo_permissions(apiuser, repoid, repo, _perms)
1803 validate_repo_permissions(apiuser, repoid, repo, _perms)
1804
1804
1805 try:
1805 try:
1806 ScmModel().pull_changes(repo.repo_name, apiuser.username)
1806 ScmModel().pull_changes(repo.repo_name, apiuser.username)
1807 return {
1807 return {
1808 'msg': 'Pulled from `%s`' % repo.repo_name,
1808 'msg': 'Pulled from `%s`' % repo.repo_name,
1809 'repository': repo.repo_name
1809 'repository': repo.repo_name
1810 }
1810 }
1811 except Exception:
1811 except Exception:
1812 log.exception("Exception occurred while trying to "
1812 log.exception("Exception occurred while trying to "
1813 "pull changes from remote location")
1813 "pull changes from remote location")
1814 raise JSONRPCError(
1814 raise JSONRPCError(
1815 'Unable to pull changes from `%s`' % repo.repo_name
1815 'Unable to pull changes from `%s`' % repo.repo_name
1816 )
1816 )
1817
1817
1818
1818
1819 @jsonrpc_method()
1819 @jsonrpc_method()
1820 def strip(request, apiuser, repoid, revision, branch):
1820 def strip(request, apiuser, repoid, revision, branch):
1821 """
1821 """
1822 Strips the given revision from the specified repository.
1822 Strips the given revision from the specified repository.
1823
1823
1824 * This will remove the revision and all of its decendants.
1824 * This will remove the revision and all of its decendants.
1825
1825
1826 This command can only be run using an |authtoken| with admin rights to
1826 This command can only be run using an |authtoken| with admin rights to
1827 the specified repository.
1827 the specified repository.
1828
1828
1829 This command takes the following options:
1829 This command takes the following options:
1830
1830
1831 :param apiuser: This is filled automatically from the |authtoken|.
1831 :param apiuser: This is filled automatically from the |authtoken|.
1832 :type apiuser: AuthUser
1832 :type apiuser: AuthUser
1833 :param repoid: The repository name or repository ID.
1833 :param repoid: The repository name or repository ID.
1834 :type repoid: str or int
1834 :type repoid: str or int
1835 :param revision: The revision you wish to strip.
1835 :param revision: The revision you wish to strip.
1836 :type revision: str
1836 :type revision: str
1837 :param branch: The branch from which to strip the revision.
1837 :param branch: The branch from which to strip the revision.
1838 :type branch: str
1838 :type branch: str
1839
1839
1840 Example output:
1840 Example output:
1841
1841
1842 .. code-block:: bash
1842 .. code-block:: bash
1843
1843
1844 id : <id_given_in_input>
1844 id : <id_given_in_input>
1845 result : {
1845 result : {
1846 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
1846 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
1847 "repository": "<repository name>"
1847 "repository": "<repository name>"
1848 }
1848 }
1849 error : null
1849 error : null
1850
1850
1851 Example error output:
1851 Example error output:
1852
1852
1853 .. code-block:: bash
1853 .. code-block:: bash
1854
1854
1855 id : <id_given_in_input>
1855 id : <id_given_in_input>
1856 result : null
1856 result : null
1857 error : {
1857 error : {
1858 "Unable to strip commit <commit_hash> from repo `<repository name>`"
1858 "Unable to strip commit <commit_hash> from repo `<repository name>`"
1859 }
1859 }
1860
1860
1861 """
1861 """
1862
1862
1863 repo = get_repo_or_error(repoid)
1863 repo = get_repo_or_error(repoid)
1864 if not has_superadmin_permission(apiuser):
1864 if not has_superadmin_permission(apiuser):
1865 _perms = ('repository.admin',)
1865 _perms = ('repository.admin',)
1866 validate_repo_permissions(apiuser, repoid, repo, _perms)
1866 validate_repo_permissions(apiuser, repoid, repo, _perms)
1867
1867
1868 try:
1868 try:
1869 ScmModel().strip(repo, revision, branch)
1869 ScmModel().strip(repo, revision, branch)
1870 return {
1870 return {
1871 'msg': 'Stripped commit %s from repo `%s`' % (
1871 'msg': 'Stripped commit %s from repo `%s`' % (
1872 revision, repo.repo_name),
1872 revision, repo.repo_name),
1873 'repository': repo.repo_name
1873 'repository': repo.repo_name
1874 }
1874 }
1875 except Exception:
1875 except Exception:
1876 log.exception("Exception while trying to strip")
1876 log.exception("Exception while trying to strip")
1877 raise JSONRPCError(
1877 raise JSONRPCError(
1878 'Unable to strip commit %s from repo `%s`' % (
1878 'Unable to strip commit %s from repo `%s`' % (
1879 revision, repo.repo_name)
1879 revision, repo.repo_name)
1880 )
1880 )
1881
1881
1882
1882
1883 @jsonrpc_method()
1883 @jsonrpc_method()
1884 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
1884 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
1885 """
1885 """
1886 Returns all settings for a repository. If key is given it only returns the
1886 Returns all settings for a repository. If key is given it only returns the
1887 setting identified by the key or null.
1887 setting identified by the key or null.
1888
1888
1889 :param apiuser: This is filled automatically from the |authtoken|.
1889 :param apiuser: This is filled automatically from the |authtoken|.
1890 :type apiuser: AuthUser
1890 :type apiuser: AuthUser
1891 :param repoid: The repository name or repository id.
1891 :param repoid: The repository name or repository id.
1892 :type repoid: str or int
1892 :type repoid: str or int
1893 :param key: Key of the setting to return.
1893 :param key: Key of the setting to return.
1894 :type: key: Optional(str)
1894 :type: key: Optional(str)
1895
1895
1896 Example output:
1896 Example output:
1897
1897
1898 .. code-block:: bash
1898 .. code-block:: bash
1899
1899
1900 {
1900 {
1901 "error": null,
1901 "error": null,
1902 "id": 237,
1902 "id": 237,
1903 "result": {
1903 "result": {
1904 "extensions_largefiles": true,
1904 "extensions_largefiles": true,
1905 "extensions_evolve": true,
1905 "hooks_changegroup_push_logger": true,
1906 "hooks_changegroup_push_logger": true,
1906 "hooks_changegroup_repo_size": false,
1907 "hooks_changegroup_repo_size": false,
1907 "hooks_outgoing_pull_logger": true,
1908 "hooks_outgoing_pull_logger": true,
1908 "phases_publish": "True",
1909 "phases_publish": "True",
1909 "rhodecode_hg_use_rebase_for_merging": true,
1910 "rhodecode_hg_use_rebase_for_merging": true,
1910 "rhodecode_pr_merge_enabled": true,
1911 "rhodecode_pr_merge_enabled": true,
1911 "rhodecode_use_outdated_comments": true
1912 "rhodecode_use_outdated_comments": true
1912 }
1913 }
1913 }
1914 }
1914 """
1915 """
1915
1916
1916 # Restrict access to this api method to admins only.
1917 # Restrict access to this api method to admins only.
1917 if not has_superadmin_permission(apiuser):
1918 if not has_superadmin_permission(apiuser):
1918 raise JSONRPCForbidden()
1919 raise JSONRPCForbidden()
1919
1920
1920 try:
1921 try:
1921 repo = get_repo_or_error(repoid)
1922 repo = get_repo_or_error(repoid)
1922 settings_model = VcsSettingsModel(repo=repo)
1923 settings_model = VcsSettingsModel(repo=repo)
1923 settings = settings_model.get_global_settings()
1924 settings = settings_model.get_global_settings()
1924 settings.update(settings_model.get_repo_settings())
1925 settings.update(settings_model.get_repo_settings())
1925
1926
1926 # If only a single setting is requested fetch it from all settings.
1927 # If only a single setting is requested fetch it from all settings.
1927 key = Optional.extract(key)
1928 key = Optional.extract(key)
1928 if key is not None:
1929 if key is not None:
1929 settings = settings.get(key, None)
1930 settings = settings.get(key, None)
1930 except Exception:
1931 except Exception:
1931 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
1932 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
1932 log.exception(msg)
1933 log.exception(msg)
1933 raise JSONRPCError(msg)
1934 raise JSONRPCError(msg)
1934
1935
1935 return settings
1936 return settings
1936
1937
1937
1938
1938 @jsonrpc_method()
1939 @jsonrpc_method()
1939 def set_repo_settings(request, apiuser, repoid, settings):
1940 def set_repo_settings(request, apiuser, repoid, settings):
1940 """
1941 """
1941 Update repository settings. Returns true on success.
1942 Update repository settings. Returns true on success.
1942
1943
1943 :param apiuser: This is filled automatically from the |authtoken|.
1944 :param apiuser: This is filled automatically from the |authtoken|.
1944 :type apiuser: AuthUser
1945 :type apiuser: AuthUser
1945 :param repoid: The repository name or repository id.
1946 :param repoid: The repository name or repository id.
1946 :type repoid: str or int
1947 :type repoid: str or int
1947 :param settings: The new settings for the repository.
1948 :param settings: The new settings for the repository.
1948 :type: settings: dict
1949 :type: settings: dict
1949
1950
1950 Example output:
1951 Example output:
1951
1952
1952 .. code-block:: bash
1953 .. code-block:: bash
1953
1954
1954 {
1955 {
1955 "error": null,
1956 "error": null,
1956 "id": 237,
1957 "id": 237,
1957 "result": true
1958 "result": true
1958 }
1959 }
1959 """
1960 """
1960 # Restrict access to this api method to admins only.
1961 # Restrict access to this api method to admins only.
1961 if not has_superadmin_permission(apiuser):
1962 if not has_superadmin_permission(apiuser):
1962 raise JSONRPCForbidden()
1963 raise JSONRPCForbidden()
1963
1964
1964 if type(settings) is not dict:
1965 if type(settings) is not dict:
1965 raise JSONRPCError('Settings have to be a JSON Object.')
1966 raise JSONRPCError('Settings have to be a JSON Object.')
1966
1967
1967 try:
1968 try:
1968 settings_model = VcsSettingsModel(repo=repoid)
1969 settings_model = VcsSettingsModel(repo=repoid)
1969
1970
1970 # Merge global, repo and incoming settings.
1971 # Merge global, repo and incoming settings.
1971 new_settings = settings_model.get_global_settings()
1972 new_settings = settings_model.get_global_settings()
1972 new_settings.update(settings_model.get_repo_settings())
1973 new_settings.update(settings_model.get_repo_settings())
1973 new_settings.update(settings)
1974 new_settings.update(settings)
1974
1975
1975 # Update the settings.
1976 # Update the settings.
1976 inherit_global_settings = new_settings.get(
1977 inherit_global_settings = new_settings.get(
1977 'inherit_global_settings', False)
1978 'inherit_global_settings', False)
1978 settings_model.create_or_update_repo_settings(
1979 settings_model.create_or_update_repo_settings(
1979 new_settings, inherit_global_settings=inherit_global_settings)
1980 new_settings, inherit_global_settings=inherit_global_settings)
1980 Session().commit()
1981 Session().commit()
1981 except Exception:
1982 except Exception:
1982 msg = 'Failed to update settings for repository `{}`'.format(repoid)
1983 msg = 'Failed to update settings for repository `{}`'.format(repoid)
1983 log.exception(msg)
1984 log.exception(msg)
1984 raise JSONRPCError(msg)
1985 raise JSONRPCError(msg)
1985
1986
1986 # Indicate success.
1987 # Indicate success.
1987 return True
1988 return True
@@ -1,611 +1,619 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Database creation, and setup module for RhodeCode Enterprise. Used for creation
22 Database creation, and setup module for RhodeCode Enterprise. Used for creation
23 of database as well as for migration operations
23 of database as well as for migration operations
24 """
24 """
25
25
26 import os
26 import os
27 import sys
27 import sys
28 import time
28 import time
29 import uuid
29 import uuid
30 import logging
30 import logging
31 import getpass
31 import getpass
32 from os.path import dirname as dn, join as jn
32 from os.path import dirname as dn, join as jn
33
33
34 from sqlalchemy.engine import create_engine
34 from sqlalchemy.engine import create_engine
35
35
36 from rhodecode import __dbversion__
36 from rhodecode import __dbversion__
37 from rhodecode.model import init_model
37 from rhodecode.model import init_model
38 from rhodecode.model.user import UserModel
38 from rhodecode.model.user import UserModel
39 from rhodecode.model.db import (
39 from rhodecode.model.db import (
40 User, Permission, RhodeCodeUi, RhodeCodeSetting, UserToPerm,
40 User, Permission, RhodeCodeUi, RhodeCodeSetting, UserToPerm,
41 DbMigrateVersion, RepoGroup, UserRepoGroupToPerm, CacheKey, Repository)
41 DbMigrateVersion, RepoGroup, UserRepoGroupToPerm, CacheKey, Repository)
42 from rhodecode.model.meta import Session, Base
42 from rhodecode.model.meta import Session, Base
43 from rhodecode.model.permission import PermissionModel
43 from rhodecode.model.permission import PermissionModel
44 from rhodecode.model.repo import RepoModel
44 from rhodecode.model.repo import RepoModel
45 from rhodecode.model.repo_group import RepoGroupModel
45 from rhodecode.model.repo_group import RepoGroupModel
46 from rhodecode.model.settings import SettingsModel
46 from rhodecode.model.settings import SettingsModel
47
47
48
48
49 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
50
50
51
51
52 def notify(msg):
52 def notify(msg):
53 """
53 """
54 Notification for migrations messages
54 Notification for migrations messages
55 """
55 """
56 ml = len(msg) + (4 * 2)
56 ml = len(msg) + (4 * 2)
57 print('\n%s\n*** %s ***\n%s' % ('*' * ml, msg, '*' * ml)).upper()
57 print('\n%s\n*** %s ***\n%s' % ('*' * ml, msg, '*' * ml)).upper()
58
58
59
59
60 class DbManage(object):
60 class DbManage(object):
61
61
62 def __init__(self, log_sql, dbconf, root, tests=False,
62 def __init__(self, log_sql, dbconf, root, tests=False,
63 SESSION=None, cli_args={}):
63 SESSION=None, cli_args={}):
64 self.dbname = dbconf.split('/')[-1]
64 self.dbname = dbconf.split('/')[-1]
65 self.tests = tests
65 self.tests = tests
66 self.root = root
66 self.root = root
67 self.dburi = dbconf
67 self.dburi = dbconf
68 self.log_sql = log_sql
68 self.log_sql = log_sql
69 self.db_exists = False
69 self.db_exists = False
70 self.cli_args = cli_args
70 self.cli_args = cli_args
71 self.init_db(SESSION=SESSION)
71 self.init_db(SESSION=SESSION)
72 self.ask_ok = self.get_ask_ok_func(self.cli_args.get('force_ask'))
72 self.ask_ok = self.get_ask_ok_func(self.cli_args.get('force_ask'))
73
73
74 def get_ask_ok_func(self, param):
74 def get_ask_ok_func(self, param):
75 if param not in [None]:
75 if param not in [None]:
76 # return a function lambda that has a default set to param
76 # return a function lambda that has a default set to param
77 return lambda *args, **kwargs: param
77 return lambda *args, **kwargs: param
78 else:
78 else:
79 from rhodecode.lib.utils import ask_ok
79 from rhodecode.lib.utils import ask_ok
80 return ask_ok
80 return ask_ok
81
81
82 def init_db(self, SESSION=None):
82 def init_db(self, SESSION=None):
83 if SESSION:
83 if SESSION:
84 self.sa = SESSION
84 self.sa = SESSION
85 else:
85 else:
86 # init new sessions
86 # init new sessions
87 engine = create_engine(self.dburi, echo=self.log_sql)
87 engine = create_engine(self.dburi, echo=self.log_sql)
88 init_model(engine)
88 init_model(engine)
89 self.sa = Session()
89 self.sa = Session()
90
90
91 def create_tables(self, override=False):
91 def create_tables(self, override=False):
92 """
92 """
93 Create a auth database
93 Create a auth database
94 """
94 """
95
95
96 log.info("Existing database with the same name is going to be destroyed.")
96 log.info("Existing database with the same name is going to be destroyed.")
97 log.info("Setup command will run DROP ALL command on that database.")
97 log.info("Setup command will run DROP ALL command on that database.")
98 if self.tests:
98 if self.tests:
99 destroy = True
99 destroy = True
100 else:
100 else:
101 destroy = self.ask_ok('Are you sure that you want to destroy the old database? [y/n]')
101 destroy = self.ask_ok('Are you sure that you want to destroy the old database? [y/n]')
102 if not destroy:
102 if not destroy:
103 log.info('Nothing done.')
103 log.info('Nothing done.')
104 sys.exit(0)
104 sys.exit(0)
105 if destroy:
105 if destroy:
106 Base.metadata.drop_all()
106 Base.metadata.drop_all()
107
107
108 checkfirst = not override
108 checkfirst = not override
109 Base.metadata.create_all(checkfirst=checkfirst)
109 Base.metadata.create_all(checkfirst=checkfirst)
110 log.info('Created tables for %s' % self.dbname)
110 log.info('Created tables for %s' % self.dbname)
111
111
112 def set_db_version(self):
112 def set_db_version(self):
113 ver = DbMigrateVersion()
113 ver = DbMigrateVersion()
114 ver.version = __dbversion__
114 ver.version = __dbversion__
115 ver.repository_id = 'rhodecode_db_migrations'
115 ver.repository_id = 'rhodecode_db_migrations'
116 ver.repository_path = 'versions'
116 ver.repository_path = 'versions'
117 self.sa.add(ver)
117 self.sa.add(ver)
118 log.info('db version set to: %s' % __dbversion__)
118 log.info('db version set to: %s' % __dbversion__)
119
119
120 def run_pre_migration_tasks(self):
120 def run_pre_migration_tasks(self):
121 """
121 """
122 Run various tasks before actually doing migrations
122 Run various tasks before actually doing migrations
123 """
123 """
124 # delete cache keys on each upgrade
124 # delete cache keys on each upgrade
125 total = CacheKey.query().count()
125 total = CacheKey.query().count()
126 log.info("Deleting (%s) cache keys now...", total)
126 log.info("Deleting (%s) cache keys now...", total)
127 CacheKey.delete_all_cache()
127 CacheKey.delete_all_cache()
128
128
129 def upgrade(self):
129 def upgrade(self):
130 """
130 """
131 Upgrades given database schema to given revision following
131 Upgrades given database schema to given revision following
132 all needed steps, to perform the upgrade
132 all needed steps, to perform the upgrade
133
133
134 """
134 """
135
135
136 from rhodecode.lib.dbmigrate.migrate.versioning import api
136 from rhodecode.lib.dbmigrate.migrate.versioning import api
137 from rhodecode.lib.dbmigrate.migrate.exceptions import \
137 from rhodecode.lib.dbmigrate.migrate.exceptions import \
138 DatabaseNotControlledError
138 DatabaseNotControlledError
139
139
140 if 'sqlite' in self.dburi:
140 if 'sqlite' in self.dburi:
141 print (
141 print (
142 '********************** WARNING **********************\n'
142 '********************** WARNING **********************\n'
143 'Make sure your version of sqlite is at least 3.7.X. \n'
143 'Make sure your version of sqlite is at least 3.7.X. \n'
144 'Earlier versions are known to fail on some migrations\n'
144 'Earlier versions are known to fail on some migrations\n'
145 '*****************************************************\n')
145 '*****************************************************\n')
146
146
147 upgrade = self.ask_ok(
147 upgrade = self.ask_ok(
148 'You are about to perform a database upgrade. Make '
148 'You are about to perform a database upgrade. Make '
149 'sure you have backed up your database. '
149 'sure you have backed up your database. '
150 'Continue ? [y/n]')
150 'Continue ? [y/n]')
151 if not upgrade:
151 if not upgrade:
152 log.info('No upgrade performed')
152 log.info('No upgrade performed')
153 sys.exit(0)
153 sys.exit(0)
154
154
155 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
155 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
156 'rhodecode/lib/dbmigrate')
156 'rhodecode/lib/dbmigrate')
157 db_uri = self.dburi
157 db_uri = self.dburi
158
158
159 try:
159 try:
160 curr_version = api.db_version(db_uri, repository_path)
160 curr_version = api.db_version(db_uri, repository_path)
161 msg = ('Found current database under version '
161 msg = ('Found current database under version '
162 'control with version %s' % curr_version)
162 'control with version %s' % curr_version)
163
163
164 except (RuntimeError, DatabaseNotControlledError):
164 except (RuntimeError, DatabaseNotControlledError):
165 curr_version = 1
165 curr_version = 1
166 msg = ('Current database is not under version control. Setting '
166 msg = ('Current database is not under version control. Setting '
167 'as version %s' % curr_version)
167 'as version %s' % curr_version)
168 api.version_control(db_uri, repository_path, curr_version)
168 api.version_control(db_uri, repository_path, curr_version)
169
169
170 notify(msg)
170 notify(msg)
171
171
172 self.run_pre_migration_tasks()
172 self.run_pre_migration_tasks()
173
173
174 if curr_version == __dbversion__:
174 if curr_version == __dbversion__:
175 log.info('This database is already at the newest version')
175 log.info('This database is already at the newest version')
176 sys.exit(0)
176 sys.exit(0)
177
177
178 upgrade_steps = range(curr_version + 1, __dbversion__ + 1)
178 upgrade_steps = range(curr_version + 1, __dbversion__ + 1)
179 notify('attempting to upgrade database from '
179 notify('attempting to upgrade database from '
180 'version %s to version %s' % (curr_version, __dbversion__))
180 'version %s to version %s' % (curr_version, __dbversion__))
181
181
182 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
182 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
183 _step = None
183 _step = None
184 for step in upgrade_steps:
184 for step in upgrade_steps:
185 notify('performing upgrade step %s' % step)
185 notify('performing upgrade step %s' % step)
186 time.sleep(0.5)
186 time.sleep(0.5)
187
187
188 api.upgrade(db_uri, repository_path, step)
188 api.upgrade(db_uri, repository_path, step)
189 self.sa.rollback()
189 self.sa.rollback()
190 notify('schema upgrade for step %s completed' % (step,))
190 notify('schema upgrade for step %s completed' % (step,))
191
191
192 _step = step
192 _step = step
193
193
194 notify('upgrade to version %s successful' % _step)
194 notify('upgrade to version %s successful' % _step)
195
195
196 def fix_repo_paths(self):
196 def fix_repo_paths(self):
197 """
197 """
198 Fixes an old RhodeCode version path into new one without a '*'
198 Fixes an old RhodeCode version path into new one without a '*'
199 """
199 """
200
200
201 paths = self.sa.query(RhodeCodeUi)\
201 paths = self.sa.query(RhodeCodeUi)\
202 .filter(RhodeCodeUi.ui_key == '/')\
202 .filter(RhodeCodeUi.ui_key == '/')\
203 .scalar()
203 .scalar()
204
204
205 paths.ui_value = paths.ui_value.replace('*', '')
205 paths.ui_value = paths.ui_value.replace('*', '')
206
206
207 try:
207 try:
208 self.sa.add(paths)
208 self.sa.add(paths)
209 self.sa.commit()
209 self.sa.commit()
210 except Exception:
210 except Exception:
211 self.sa.rollback()
211 self.sa.rollback()
212 raise
212 raise
213
213
214 def fix_default_user(self):
214 def fix_default_user(self):
215 """
215 """
216 Fixes an old default user with some 'nicer' default values,
216 Fixes an old default user with some 'nicer' default values,
217 used mostly for anonymous access
217 used mostly for anonymous access
218 """
218 """
219 def_user = self.sa.query(User)\
219 def_user = self.sa.query(User)\
220 .filter(User.username == User.DEFAULT_USER)\
220 .filter(User.username == User.DEFAULT_USER)\
221 .one()
221 .one()
222
222
223 def_user.name = 'Anonymous'
223 def_user.name = 'Anonymous'
224 def_user.lastname = 'User'
224 def_user.lastname = 'User'
225 def_user.email = User.DEFAULT_USER_EMAIL
225 def_user.email = User.DEFAULT_USER_EMAIL
226
226
227 try:
227 try:
228 self.sa.add(def_user)
228 self.sa.add(def_user)
229 self.sa.commit()
229 self.sa.commit()
230 except Exception:
230 except Exception:
231 self.sa.rollback()
231 self.sa.rollback()
232 raise
232 raise
233
233
234 def fix_settings(self):
234 def fix_settings(self):
235 """
235 """
236 Fixes rhodecode settings and adds ga_code key for google analytics
236 Fixes rhodecode settings and adds ga_code key for google analytics
237 """
237 """
238
238
239 hgsettings3 = RhodeCodeSetting('ga_code', '')
239 hgsettings3 = RhodeCodeSetting('ga_code', '')
240
240
241 try:
241 try:
242 self.sa.add(hgsettings3)
242 self.sa.add(hgsettings3)
243 self.sa.commit()
243 self.sa.commit()
244 except Exception:
244 except Exception:
245 self.sa.rollback()
245 self.sa.rollback()
246 raise
246 raise
247
247
248 def create_admin_and_prompt(self):
248 def create_admin_and_prompt(self):
249
249
250 # defaults
250 # defaults
251 defaults = self.cli_args
251 defaults = self.cli_args
252 username = defaults.get('username')
252 username = defaults.get('username')
253 password = defaults.get('password')
253 password = defaults.get('password')
254 email = defaults.get('email')
254 email = defaults.get('email')
255
255
256 if username is None:
256 if username is None:
257 username = raw_input('Specify admin username:')
257 username = raw_input('Specify admin username:')
258 if password is None:
258 if password is None:
259 password = self._get_admin_password()
259 password = self._get_admin_password()
260 if not password:
260 if not password:
261 # second try
261 # second try
262 password = self._get_admin_password()
262 password = self._get_admin_password()
263 if not password:
263 if not password:
264 sys.exit()
264 sys.exit()
265 if email is None:
265 if email is None:
266 email = raw_input('Specify admin email:')
266 email = raw_input('Specify admin email:')
267 api_key = self.cli_args.get('api_key')
267 api_key = self.cli_args.get('api_key')
268 self.create_user(username, password, email, True,
268 self.create_user(username, password, email, True,
269 strict_creation_check=False,
269 strict_creation_check=False,
270 api_key=api_key)
270 api_key=api_key)
271
271
272 def _get_admin_password(self):
272 def _get_admin_password(self):
273 password = getpass.getpass('Specify admin password '
273 password = getpass.getpass('Specify admin password '
274 '(min 6 chars):')
274 '(min 6 chars):')
275 confirm = getpass.getpass('Confirm password:')
275 confirm = getpass.getpass('Confirm password:')
276
276
277 if password != confirm:
277 if password != confirm:
278 log.error('passwords mismatch')
278 log.error('passwords mismatch')
279 return False
279 return False
280 if len(password) < 6:
280 if len(password) < 6:
281 log.error('password is too short - use at least 6 characters')
281 log.error('password is too short - use at least 6 characters')
282 return False
282 return False
283
283
284 return password
284 return password
285
285
286 def create_test_admin_and_users(self):
286 def create_test_admin_and_users(self):
287 log.info('creating admin and regular test users')
287 log.info('creating admin and regular test users')
288 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
288 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
289 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
289 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
290 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
290 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
291 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
291 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
292 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
292 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
293
293
294 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
294 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
295 TEST_USER_ADMIN_EMAIL, True, api_key=True)
295 TEST_USER_ADMIN_EMAIL, True, api_key=True)
296
296
297 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
297 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
298 TEST_USER_REGULAR_EMAIL, False, api_key=True)
298 TEST_USER_REGULAR_EMAIL, False, api_key=True)
299
299
300 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
300 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
301 TEST_USER_REGULAR2_EMAIL, False, api_key=True)
301 TEST_USER_REGULAR2_EMAIL, False, api_key=True)
302
302
303 def create_ui_settings(self, repo_store_path):
303 def create_ui_settings(self, repo_store_path):
304 """
304 """
305 Creates ui settings, fills out hooks
305 Creates ui settings, fills out hooks
306 and disables dotencode
306 and disables dotencode
307 """
307 """
308 settings_model = SettingsModel(sa=self.sa)
308 settings_model = SettingsModel(sa=self.sa)
309 from rhodecode.lib.vcs.backends.hg import largefiles_store
309 from rhodecode.lib.vcs.backends.hg import largefiles_store
310 from rhodecode.lib.vcs.backends.git import lfs_store
310 from rhodecode.lib.vcs.backends.git import lfs_store
311
311
312 # Build HOOKS
312 # Build HOOKS
313 hooks = [
313 hooks = [
314 (RhodeCodeUi.HOOK_REPO_SIZE, 'python:vcsserver.hooks.repo_size'),
314 (RhodeCodeUi.HOOK_REPO_SIZE, 'python:vcsserver.hooks.repo_size'),
315
315
316 # HG
316 # HG
317 (RhodeCodeUi.HOOK_PRE_PULL, 'python:vcsserver.hooks.pre_pull'),
317 (RhodeCodeUi.HOOK_PRE_PULL, 'python:vcsserver.hooks.pre_pull'),
318 (RhodeCodeUi.HOOK_PULL, 'python:vcsserver.hooks.log_pull_action'),
318 (RhodeCodeUi.HOOK_PULL, 'python:vcsserver.hooks.log_pull_action'),
319 (RhodeCodeUi.HOOK_PRE_PUSH, 'python:vcsserver.hooks.pre_push'),
319 (RhodeCodeUi.HOOK_PRE_PUSH, 'python:vcsserver.hooks.pre_push'),
320 (RhodeCodeUi.HOOK_PRETX_PUSH, 'python:vcsserver.hooks.pre_push'),
320 (RhodeCodeUi.HOOK_PRETX_PUSH, 'python:vcsserver.hooks.pre_push'),
321 (RhodeCodeUi.HOOK_PUSH, 'python:vcsserver.hooks.log_push_action'),
321 (RhodeCodeUi.HOOK_PUSH, 'python:vcsserver.hooks.log_push_action'),
322
322
323 ]
323 ]
324
324
325 for key, value in hooks:
325 for key, value in hooks:
326 hook_obj = settings_model.get_ui_by_key(key)
326 hook_obj = settings_model.get_ui_by_key(key)
327 hooks2 = hook_obj if hook_obj else RhodeCodeUi()
327 hooks2 = hook_obj if hook_obj else RhodeCodeUi()
328 hooks2.ui_section = 'hooks'
328 hooks2.ui_section = 'hooks'
329 hooks2.ui_key = key
329 hooks2.ui_key = key
330 hooks2.ui_value = value
330 hooks2.ui_value = value
331 self.sa.add(hooks2)
331 self.sa.add(hooks2)
332
332
333 # enable largefiles
333 # enable largefiles
334 largefiles = RhodeCodeUi()
334 largefiles = RhodeCodeUi()
335 largefiles.ui_section = 'extensions'
335 largefiles.ui_section = 'extensions'
336 largefiles.ui_key = 'largefiles'
336 largefiles.ui_key = 'largefiles'
337 largefiles.ui_value = ''
337 largefiles.ui_value = ''
338 self.sa.add(largefiles)
338 self.sa.add(largefiles)
339
339
340 # set default largefiles cache dir, defaults to
340 # set default largefiles cache dir, defaults to
341 # /repo_store_location/.cache/largefiles
341 # /repo_store_location/.cache/largefiles
342 largefiles = RhodeCodeUi()
342 largefiles = RhodeCodeUi()
343 largefiles.ui_section = 'largefiles'
343 largefiles.ui_section = 'largefiles'
344 largefiles.ui_key = 'usercache'
344 largefiles.ui_key = 'usercache'
345 largefiles.ui_value = largefiles_store(repo_store_path)
345 largefiles.ui_value = largefiles_store(repo_store_path)
346
346
347 self.sa.add(largefiles)
347 self.sa.add(largefiles)
348
348
349 # set default lfs cache dir, defaults to
349 # set default lfs cache dir, defaults to
350 # /repo_store_location/.cache/lfs_store
350 # /repo_store_location/.cache/lfs_store
351 lfsstore = RhodeCodeUi()
351 lfsstore = RhodeCodeUi()
352 lfsstore.ui_section = 'vcs_git_lfs'
352 lfsstore.ui_section = 'vcs_git_lfs'
353 lfsstore.ui_key = 'store_location'
353 lfsstore.ui_key = 'store_location'
354 lfsstore.ui_value = lfs_store(repo_store_path)
354 lfsstore.ui_value = lfs_store(repo_store_path)
355
355
356 self.sa.add(lfsstore)
356 self.sa.add(lfsstore)
357
357
358 # enable hgsubversion disabled by default
358 # enable hgsubversion disabled by default
359 hgsubversion = RhodeCodeUi()
359 hgsubversion = RhodeCodeUi()
360 hgsubversion.ui_section = 'extensions'
360 hgsubversion.ui_section = 'extensions'
361 hgsubversion.ui_key = 'hgsubversion'
361 hgsubversion.ui_key = 'hgsubversion'
362 hgsubversion.ui_value = ''
362 hgsubversion.ui_value = ''
363 hgsubversion.ui_active = False
363 hgsubversion.ui_active = False
364 self.sa.add(hgsubversion)
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 # enable hggit disabled by default
374 # enable hggit disabled by default
367 hggit = RhodeCodeUi()
375 hggit = RhodeCodeUi()
368 hggit.ui_section = 'extensions'
376 hggit.ui_section = 'extensions'
369 hggit.ui_key = 'hggit'
377 hggit.ui_key = 'hggit'
370 hggit.ui_value = ''
378 hggit.ui_value = ''
371 hggit.ui_active = False
379 hggit.ui_active = False
372 self.sa.add(hggit)
380 self.sa.add(hggit)
373
381
374 # set svn branch defaults
382 # set svn branch defaults
375 branches = ["/branches/*", "/trunk"]
383 branches = ["/branches/*", "/trunk"]
376 tags = ["/tags/*"]
384 tags = ["/tags/*"]
377
385
378 for branch in branches:
386 for branch in branches:
379 settings_model.create_ui_section_value(
387 settings_model.create_ui_section_value(
380 RhodeCodeUi.SVN_BRANCH_ID, branch)
388 RhodeCodeUi.SVN_BRANCH_ID, branch)
381
389
382 for tag in tags:
390 for tag in tags:
383 settings_model.create_ui_section_value(RhodeCodeUi.SVN_TAG_ID, tag)
391 settings_model.create_ui_section_value(RhodeCodeUi.SVN_TAG_ID, tag)
384
392
385 def create_auth_plugin_options(self, skip_existing=False):
393 def create_auth_plugin_options(self, skip_existing=False):
386 """
394 """
387 Create default auth plugin settings, and make it active
395 Create default auth plugin settings, and make it active
388
396
389 :param skip_existing:
397 :param skip_existing:
390 """
398 """
391
399
392 for k, v, t in [('auth_plugins', 'egg:rhodecode-enterprise-ce#rhodecode', 'list'),
400 for k, v, t in [('auth_plugins', 'egg:rhodecode-enterprise-ce#rhodecode', 'list'),
393 ('auth_rhodecode_enabled', 'True', 'bool')]:
401 ('auth_rhodecode_enabled', 'True', 'bool')]:
394 if (skip_existing and
402 if (skip_existing and
395 SettingsModel().get_setting_by_name(k) is not None):
403 SettingsModel().get_setting_by_name(k) is not None):
396 log.debug('Skipping option %s' % k)
404 log.debug('Skipping option %s' % k)
397 continue
405 continue
398 setting = RhodeCodeSetting(k, v, t)
406 setting = RhodeCodeSetting(k, v, t)
399 self.sa.add(setting)
407 self.sa.add(setting)
400
408
401 def create_default_options(self, skip_existing=False):
409 def create_default_options(self, skip_existing=False):
402 """Creates default settings"""
410 """Creates default settings"""
403
411
404 for k, v, t in [
412 for k, v, t in [
405 ('default_repo_enable_locking', False, 'bool'),
413 ('default_repo_enable_locking', False, 'bool'),
406 ('default_repo_enable_downloads', False, 'bool'),
414 ('default_repo_enable_downloads', False, 'bool'),
407 ('default_repo_enable_statistics', False, 'bool'),
415 ('default_repo_enable_statistics', False, 'bool'),
408 ('default_repo_private', False, 'bool'),
416 ('default_repo_private', False, 'bool'),
409 ('default_repo_type', 'hg', 'unicode')]:
417 ('default_repo_type', 'hg', 'unicode')]:
410
418
411 if (skip_existing and
419 if (skip_existing and
412 SettingsModel().get_setting_by_name(k) is not None):
420 SettingsModel().get_setting_by_name(k) is not None):
413 log.debug('Skipping option %s' % k)
421 log.debug('Skipping option %s' % k)
414 continue
422 continue
415 setting = RhodeCodeSetting(k, v, t)
423 setting = RhodeCodeSetting(k, v, t)
416 self.sa.add(setting)
424 self.sa.add(setting)
417
425
418 def fixup_groups(self):
426 def fixup_groups(self):
419 def_usr = User.get_default_user()
427 def_usr = User.get_default_user()
420 for g in RepoGroup.query().all():
428 for g in RepoGroup.query().all():
421 g.group_name = g.get_new_name(g.name)
429 g.group_name = g.get_new_name(g.name)
422 self.sa.add(g)
430 self.sa.add(g)
423 # get default perm
431 # get default perm
424 default = UserRepoGroupToPerm.query()\
432 default = UserRepoGroupToPerm.query()\
425 .filter(UserRepoGroupToPerm.group == g)\
433 .filter(UserRepoGroupToPerm.group == g)\
426 .filter(UserRepoGroupToPerm.user == def_usr)\
434 .filter(UserRepoGroupToPerm.user == def_usr)\
427 .scalar()
435 .scalar()
428
436
429 if default is None:
437 if default is None:
430 log.debug('missing default permission for group %s adding' % g)
438 log.debug('missing default permission for group %s adding' % g)
431 perm_obj = RepoGroupModel()._create_default_perms(g)
439 perm_obj = RepoGroupModel()._create_default_perms(g)
432 self.sa.add(perm_obj)
440 self.sa.add(perm_obj)
433
441
434 def reset_permissions(self, username):
442 def reset_permissions(self, username):
435 """
443 """
436 Resets permissions to default state, useful when old systems had
444 Resets permissions to default state, useful when old systems had
437 bad permissions, we must clean them up
445 bad permissions, we must clean them up
438
446
439 :param username:
447 :param username:
440 """
448 """
441 default_user = User.get_by_username(username)
449 default_user = User.get_by_username(username)
442 if not default_user:
450 if not default_user:
443 return
451 return
444
452
445 u2p = UserToPerm.query()\
453 u2p = UserToPerm.query()\
446 .filter(UserToPerm.user == default_user).all()
454 .filter(UserToPerm.user == default_user).all()
447 fixed = False
455 fixed = False
448 if len(u2p) != len(Permission.DEFAULT_USER_PERMISSIONS):
456 if len(u2p) != len(Permission.DEFAULT_USER_PERMISSIONS):
449 for p in u2p:
457 for p in u2p:
450 Session().delete(p)
458 Session().delete(p)
451 fixed = True
459 fixed = True
452 self.populate_default_permissions()
460 self.populate_default_permissions()
453 return fixed
461 return fixed
454
462
455 def update_repo_info(self):
463 def update_repo_info(self):
456 RepoModel.update_repoinfo()
464 RepoModel.update_repoinfo()
457
465
458 def config_prompt(self, test_repo_path='', retries=3):
466 def config_prompt(self, test_repo_path='', retries=3):
459 defaults = self.cli_args
467 defaults = self.cli_args
460 _path = defaults.get('repos_location')
468 _path = defaults.get('repos_location')
461 if retries == 3:
469 if retries == 3:
462 log.info('Setting up repositories config')
470 log.info('Setting up repositories config')
463
471
464 if _path is not None:
472 if _path is not None:
465 path = _path
473 path = _path
466 elif not self.tests and not test_repo_path:
474 elif not self.tests and not test_repo_path:
467 path = raw_input(
475 path = raw_input(
468 'Enter a valid absolute path to store repositories. '
476 'Enter a valid absolute path to store repositories. '
469 'All repositories in that path will be added automatically:'
477 'All repositories in that path will be added automatically:'
470 )
478 )
471 else:
479 else:
472 path = test_repo_path
480 path = test_repo_path
473 path_ok = True
481 path_ok = True
474
482
475 # check proper dir
483 # check proper dir
476 if not os.path.isdir(path):
484 if not os.path.isdir(path):
477 path_ok = False
485 path_ok = False
478 log.error('Given path %s is not a valid directory' % (path,))
486 log.error('Given path %s is not a valid directory' % (path,))
479
487
480 elif not os.path.isabs(path):
488 elif not os.path.isabs(path):
481 path_ok = False
489 path_ok = False
482 log.error('Given path %s is not an absolute path' % (path,))
490 log.error('Given path %s is not an absolute path' % (path,))
483
491
484 # check if path is at least readable.
492 # check if path is at least readable.
485 if not os.access(path, os.R_OK):
493 if not os.access(path, os.R_OK):
486 path_ok = False
494 path_ok = False
487 log.error('Given path %s is not readable' % (path,))
495 log.error('Given path %s is not readable' % (path,))
488
496
489 # check write access, warn user about non writeable paths
497 # check write access, warn user about non writeable paths
490 elif not os.access(path, os.W_OK) and path_ok:
498 elif not os.access(path, os.W_OK) and path_ok:
491 log.warning('No write permission to given path %s' % (path,))
499 log.warning('No write permission to given path %s' % (path,))
492
500
493 q = ('Given path %s is not writeable, do you want to '
501 q = ('Given path %s is not writeable, do you want to '
494 'continue with read only mode ? [y/n]' % (path,))
502 'continue with read only mode ? [y/n]' % (path,))
495 if not self.ask_ok(q):
503 if not self.ask_ok(q):
496 log.error('Canceled by user')
504 log.error('Canceled by user')
497 sys.exit(-1)
505 sys.exit(-1)
498
506
499 if retries == 0:
507 if retries == 0:
500 sys.exit('max retries reached')
508 sys.exit('max retries reached')
501 if not path_ok:
509 if not path_ok:
502 retries -= 1
510 retries -= 1
503 return self.config_prompt(test_repo_path, retries)
511 return self.config_prompt(test_repo_path, retries)
504
512
505 real_path = os.path.normpath(os.path.realpath(path))
513 real_path = os.path.normpath(os.path.realpath(path))
506
514
507 if real_path != os.path.normpath(path):
515 if real_path != os.path.normpath(path):
508 q = ('Path looks like a symlink, RhodeCode Enterprise will store '
516 q = ('Path looks like a symlink, RhodeCode Enterprise will store '
509 'given path as %s ? [y/n]') % (real_path,)
517 'given path as %s ? [y/n]') % (real_path,)
510 if not self.ask_ok(q):
518 if not self.ask_ok(q):
511 log.error('Canceled by user')
519 log.error('Canceled by user')
512 sys.exit(-1)
520 sys.exit(-1)
513
521
514 return real_path
522 return real_path
515
523
516 def create_settings(self, path):
524 def create_settings(self, path):
517
525
518 self.create_ui_settings(path)
526 self.create_ui_settings(path)
519
527
520 ui_config = [
528 ui_config = [
521 ('web', 'push_ssl', 'False'),
529 ('web', 'push_ssl', 'False'),
522 ('web', 'allow_archive', 'gz zip bz2'),
530 ('web', 'allow_archive', 'gz zip bz2'),
523 ('web', 'allow_push', '*'),
531 ('web', 'allow_push', '*'),
524 ('web', 'baseurl', '/'),
532 ('web', 'baseurl', '/'),
525 ('paths', '/', path),
533 ('paths', '/', path),
526 ('phases', 'publish', 'True')
534 ('phases', 'publish', 'True')
527 ]
535 ]
528 for section, key, value in ui_config:
536 for section, key, value in ui_config:
529 ui_conf = RhodeCodeUi()
537 ui_conf = RhodeCodeUi()
530 setattr(ui_conf, 'ui_section', section)
538 setattr(ui_conf, 'ui_section', section)
531 setattr(ui_conf, 'ui_key', key)
539 setattr(ui_conf, 'ui_key', key)
532 setattr(ui_conf, 'ui_value', value)
540 setattr(ui_conf, 'ui_value', value)
533 self.sa.add(ui_conf)
541 self.sa.add(ui_conf)
534
542
535 # rhodecode app settings
543 # rhodecode app settings
536 settings = [
544 settings = [
537 ('realm', 'RhodeCode', 'unicode'),
545 ('realm', 'RhodeCode', 'unicode'),
538 ('title', '', 'unicode'),
546 ('title', '', 'unicode'),
539 ('pre_code', '', 'unicode'),
547 ('pre_code', '', 'unicode'),
540 ('post_code', '', 'unicode'),
548 ('post_code', '', 'unicode'),
541 ('show_public_icon', True, 'bool'),
549 ('show_public_icon', True, 'bool'),
542 ('show_private_icon', True, 'bool'),
550 ('show_private_icon', True, 'bool'),
543 ('stylify_metatags', False, 'bool'),
551 ('stylify_metatags', False, 'bool'),
544 ('dashboard_items', 100, 'int'),
552 ('dashboard_items', 100, 'int'),
545 ('admin_grid_items', 25, 'int'),
553 ('admin_grid_items', 25, 'int'),
546 ('show_version', True, 'bool'),
554 ('show_version', True, 'bool'),
547 ('use_gravatar', False, 'bool'),
555 ('use_gravatar', False, 'bool'),
548 ('gravatar_url', User.DEFAULT_GRAVATAR_URL, 'unicode'),
556 ('gravatar_url', User.DEFAULT_GRAVATAR_URL, 'unicode'),
549 ('clone_uri_tmpl', Repository.DEFAULT_CLONE_URI, 'unicode'),
557 ('clone_uri_tmpl', Repository.DEFAULT_CLONE_URI, 'unicode'),
550 ('support_url', '', 'unicode'),
558 ('support_url', '', 'unicode'),
551 ('update_url', RhodeCodeSetting.DEFAULT_UPDATE_URL, 'unicode'),
559 ('update_url', RhodeCodeSetting.DEFAULT_UPDATE_URL, 'unicode'),
552 ('show_revision_number', True, 'bool'),
560 ('show_revision_number', True, 'bool'),
553 ('show_sha_length', 12, 'int'),
561 ('show_sha_length', 12, 'int'),
554 ]
562 ]
555
563
556 for key, val, type_ in settings:
564 for key, val, type_ in settings:
557 sett = RhodeCodeSetting(key, val, type_)
565 sett = RhodeCodeSetting(key, val, type_)
558 self.sa.add(sett)
566 self.sa.add(sett)
559
567
560 self.create_auth_plugin_options()
568 self.create_auth_plugin_options()
561 self.create_default_options()
569 self.create_default_options()
562
570
563 log.info('created ui config')
571 log.info('created ui config')
564
572
565 def create_user(self, username, password, email='', admin=False,
573 def create_user(self, username, password, email='', admin=False,
566 strict_creation_check=True, api_key=None):
574 strict_creation_check=True, api_key=None):
567 log.info('creating user %s' % username)
575 log.info('creating user %s' % username)
568 user = UserModel().create_or_update(
576 user = UserModel().create_or_update(
569 username, password, email, firstname='RhodeCode', lastname='Admin',
577 username, password, email, firstname='RhodeCode', lastname='Admin',
570 active=True, admin=admin, extern_type="rhodecode",
578 active=True, admin=admin, extern_type="rhodecode",
571 strict_creation_check=strict_creation_check)
579 strict_creation_check=strict_creation_check)
572
580
573 if api_key:
581 if api_key:
574 log.info('setting a provided api key for the user %s', username)
582 log.info('setting a provided api key for the user %s', username)
575 from rhodecode.model.auth_token import AuthTokenModel
583 from rhodecode.model.auth_token import AuthTokenModel
576 AuthTokenModel().create(
584 AuthTokenModel().create(
577 user=user, description='BUILTIN TOKEN')
585 user=user, description='BUILTIN TOKEN')
578
586
579 def create_default_user(self):
587 def create_default_user(self):
580 log.info('creating default user')
588 log.info('creating default user')
581 # create default user for handling default permissions.
589 # create default user for handling default permissions.
582 user = UserModel().create_or_update(username=User.DEFAULT_USER,
590 user = UserModel().create_or_update(username=User.DEFAULT_USER,
583 password=str(uuid.uuid1())[:20],
591 password=str(uuid.uuid1())[:20],
584 email=User.DEFAULT_USER_EMAIL,
592 email=User.DEFAULT_USER_EMAIL,
585 firstname='Anonymous',
593 firstname='Anonymous',
586 lastname='User',
594 lastname='User',
587 strict_creation_check=False)
595 strict_creation_check=False)
588 # based on configuration options activate/deactive this user which
596 # based on configuration options activate/deactive this user which
589 # controlls anonymous access
597 # controlls anonymous access
590 if self.cli_args.get('public_access') is False:
598 if self.cli_args.get('public_access') is False:
591 log.info('Public access disabled')
599 log.info('Public access disabled')
592 user.active = False
600 user.active = False
593 Session().add(user)
601 Session().add(user)
594 Session().commit()
602 Session().commit()
595
603
596 def create_permissions(self):
604 def create_permissions(self):
597 """
605 """
598 Creates all permissions defined in the system
606 Creates all permissions defined in the system
599 """
607 """
600 # module.(access|create|change|delete)_[name]
608 # module.(access|create|change|delete)_[name]
601 # module.(none|read|write|admin)
609 # module.(none|read|write|admin)
602 log.info('creating permissions')
610 log.info('creating permissions')
603 PermissionModel(self.sa).create_permissions()
611 PermissionModel(self.sa).create_permissions()
604
612
605 def populate_default_permissions(self):
613 def populate_default_permissions(self):
606 """
614 """
607 Populate default permissions. It will create only the default
615 Populate default permissions. It will create only the default
608 permissions that are missing, and not alter already defined ones
616 permissions that are missing, and not alter already defined ones
609 """
617 """
610 log.info('creating default user permissions')
618 log.info('creating default user permissions')
611 PermissionModel(self.sa).create_default_user_permissions(user=User.DEFAULT_USER)
619 PermissionModel(self.sa).create_default_user_permissions(user=User.DEFAULT_USER)
@@ -1,373 +1,383 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2014-2017 RhodeCode GmbH
3 # Copyright (C) 2014-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 HG commit module
22 HG commit module
23 """
23 """
24
24
25 import os
25 import os
26
26
27 from zope.cachedescriptors.property import Lazy as LazyProperty
27 from zope.cachedescriptors.property import Lazy as LazyProperty
28
28
29 from rhodecode.lib.datelib import utcdate_fromtimestamp
29 from rhodecode.lib.datelib import utcdate_fromtimestamp
30 from rhodecode.lib.utils import safe_str, safe_unicode
30 from rhodecode.lib.utils import safe_str, safe_unicode
31 from rhodecode.lib.vcs import path as vcspath
31 from rhodecode.lib.vcs import path as vcspath
32 from rhodecode.lib.vcs.backends import base
32 from rhodecode.lib.vcs.backends import base
33 from rhodecode.lib.vcs.backends.hg.diff import MercurialDiff
33 from rhodecode.lib.vcs.backends.hg.diff import MercurialDiff
34 from rhodecode.lib.vcs.exceptions import CommitError
34 from rhodecode.lib.vcs.exceptions import CommitError
35 from rhodecode.lib.vcs.nodes import (
35 from rhodecode.lib.vcs.nodes import (
36 AddedFileNodesGenerator, ChangedFileNodesGenerator, DirNode, FileNode,
36 AddedFileNodesGenerator, ChangedFileNodesGenerator, DirNode, FileNode,
37 NodeKind, RemovedFileNodesGenerator, RootNode, SubModuleNode,
37 NodeKind, RemovedFileNodesGenerator, RootNode, SubModuleNode,
38 LargeFileNode, LARGEFILE_PREFIX)
38 LargeFileNode, LARGEFILE_PREFIX)
39 from rhodecode.lib.vcs.utils.paths import get_dirs_for_path
39 from rhodecode.lib.vcs.utils.paths import get_dirs_for_path
40
40
41
41
42 class MercurialCommit(base.BaseCommit):
42 class MercurialCommit(base.BaseCommit):
43 """
43 """
44 Represents state of the repository at the single commit.
44 Represents state of the repository at the single commit.
45 """
45 """
46
46
47 _filter_pre_load = [
47 _filter_pre_load = [
48 # git specific property not supported here
48 # git specific property not supported here
49 "_commit",
49 "_commit",
50 ]
50 ]
51
51
52 def __init__(self, repository, raw_id, idx, pre_load=None):
52 def __init__(self, repository, raw_id, idx, pre_load=None):
53 raw_id = safe_str(raw_id)
53 raw_id = safe_str(raw_id)
54
54
55 self.repository = repository
55 self.repository = repository
56 self._remote = repository._remote
56 self._remote = repository._remote
57
57
58 self.raw_id = raw_id
58 self.raw_id = raw_id
59 self.idx = repository._sanitize_commit_idx(idx)
59 self.idx = repository._sanitize_commit_idx(idx)
60
60
61 self._set_bulk_properties(pre_load)
61 self._set_bulk_properties(pre_load)
62
62
63 # caches
63 # caches
64 self.nodes = {}
64 self.nodes = {}
65
65
66 def _set_bulk_properties(self, pre_load):
66 def _set_bulk_properties(self, pre_load):
67 if not pre_load:
67 if not pre_load:
68 return
68 return
69 pre_load = [entry for entry in pre_load
69 pre_load = [entry for entry in pre_load
70 if entry not in self._filter_pre_load]
70 if entry not in self._filter_pre_load]
71 if not pre_load:
71 if not pre_load:
72 return
72 return
73
73
74 result = self._remote.bulk_request(self.idx, pre_load)
74 result = self._remote.bulk_request(self.idx, pre_load)
75 for attr, value in result.items():
75 for attr, value in result.items():
76 if attr in ["author", "branch", "message"]:
76 if attr in ["author", "branch", "message"]:
77 value = safe_unicode(value)
77 value = safe_unicode(value)
78 elif attr == "affected_files":
78 elif attr == "affected_files":
79 value = map(safe_unicode, value)
79 value = map(safe_unicode, value)
80 elif attr == "date":
80 elif attr == "date":
81 value = utcdate_fromtimestamp(*value)
81 value = utcdate_fromtimestamp(*value)
82 elif attr in ["children", "parents"]:
82 elif attr in ["children", "parents"]:
83 value = self._make_commits(value)
83 value = self._make_commits(value)
84 self.__dict__[attr] = value
84 self.__dict__[attr] = value
85
85
86 @LazyProperty
86 @LazyProperty
87 def tags(self):
87 def tags(self):
88 tags = [name for name, commit_id in self.repository.tags.iteritems()
88 tags = [name for name, commit_id in self.repository.tags.iteritems()
89 if commit_id == self.raw_id]
89 if commit_id == self.raw_id]
90 return tags
90 return tags
91
91
92 @LazyProperty
92 @LazyProperty
93 def branch(self):
93 def branch(self):
94 return safe_unicode(self._remote.ctx_branch(self.idx))
94 return safe_unicode(self._remote.ctx_branch(self.idx))
95
95
96 @LazyProperty
96 @LazyProperty
97 def bookmarks(self):
97 def bookmarks(self):
98 bookmarks = [
98 bookmarks = [
99 name for name, commit_id in self.repository.bookmarks.iteritems()
99 name for name, commit_id in self.repository.bookmarks.iteritems()
100 if commit_id == self.raw_id]
100 if commit_id == self.raw_id]
101 return bookmarks
101 return bookmarks
102
102
103 @LazyProperty
103 @LazyProperty
104 def message(self):
104 def message(self):
105 return safe_unicode(self._remote.ctx_description(self.idx))
105 return safe_unicode(self._remote.ctx_description(self.idx))
106
106
107 @LazyProperty
107 @LazyProperty
108 def committer(self):
108 def committer(self):
109 return safe_unicode(self.author)
109 return safe_unicode(self.author)
110
110
111 @LazyProperty
111 @LazyProperty
112 def author(self):
112 def author(self):
113 return safe_unicode(self._remote.ctx_user(self.idx))
113 return safe_unicode(self._remote.ctx_user(self.idx))
114
114
115 @LazyProperty
115 @LazyProperty
116 def date(self):
116 def date(self):
117 return utcdate_fromtimestamp(*self._remote.ctx_date(self.idx))
117 return utcdate_fromtimestamp(*self._remote.ctx_date(self.idx))
118
118
119 @LazyProperty
119 @LazyProperty
120 def status(self):
120 def status(self):
121 """
121 """
122 Returns modified, added, removed, deleted files for current commit
122 Returns modified, added, removed, deleted files for current commit
123 """
123 """
124 return self._remote.ctx_status(self.idx)
124 return self._remote.ctx_status(self.idx)
125
125
126 @LazyProperty
126 @LazyProperty
127 def _file_paths(self):
127 def _file_paths(self):
128 return self._remote.ctx_list(self.idx)
128 return self._remote.ctx_list(self.idx)
129
129
130 @LazyProperty
130 @LazyProperty
131 def _dir_paths(self):
131 def _dir_paths(self):
132 p = list(set(get_dirs_for_path(*self._file_paths)))
132 p = list(set(get_dirs_for_path(*self._file_paths)))
133 p.insert(0, '')
133 p.insert(0, '')
134 return p
134 return p
135
135
136 @LazyProperty
136 @LazyProperty
137 def _paths(self):
137 def _paths(self):
138 return self._dir_paths + self._file_paths
138 return self._dir_paths + self._file_paths
139
139
140 @LazyProperty
140 @LazyProperty
141 def id(self):
141 def id(self):
142 if self.last:
142 if self.last:
143 return u'tip'
143 return u'tip'
144 return self.short_id
144 return self.short_id
145
145
146 @LazyProperty
146 @LazyProperty
147 def short_id(self):
147 def short_id(self):
148 return self.raw_id[:12]
148 return self.raw_id[:12]
149
149
150 def _make_commits(self, indexes):
150 def _make_commits(self, indexes):
151 return [self.repository.get_commit(commit_idx=idx)
151 return [self.repository.get_commit(commit_idx=idx)
152 for idx in indexes if idx >= 0]
152 for idx in indexes if idx >= 0]
153
153
154 @LazyProperty
154 @LazyProperty
155 def parents(self):
155 def parents(self):
156 """
156 """
157 Returns list of parent commits.
157 Returns list of parent commits.
158 """
158 """
159 parents = self._remote.ctx_parents(self.idx)
159 parents = self._remote.ctx_parents(self.idx)
160 return self._make_commits(parents)
160 return self._make_commits(parents)
161
161
162 @LazyProperty
162 @LazyProperty
163 def phase(self):
163 def phase(self):
164 phase_id = self._remote.ctx_phase(self.idx)
164 phase_id = self._remote.ctx_phase(self.idx)
165 phase_text = {
165 phase_text = {
166 0: 'public',
166 0: 'public',
167 1: 'draft',
167 1: 'draft',
168 2: 'secret',
168 2: 'secret',
169 }.get(phase_id) or ''
169 }.get(phase_id) or ''
170
170
171 return safe_unicode(phase_text)
171 return safe_unicode(phase_text)
172
172
173 @LazyProperty
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 def children(self):
184 def children(self):
175 """
185 """
176 Returns list of child commits.
186 Returns list of child commits.
177 """
187 """
178 children = self._remote.ctx_children(self.idx)
188 children = self._remote.ctx_children(self.idx)
179 return self._make_commits(children)
189 return self._make_commits(children)
180
190
181 def diff(self, ignore_whitespace=True, context=3):
191 def diff(self, ignore_whitespace=True, context=3):
182 result = self._remote.ctx_diff(
192 result = self._remote.ctx_diff(
183 self.idx,
193 self.idx,
184 git=True, ignore_whitespace=ignore_whitespace, context=context)
194 git=True, ignore_whitespace=ignore_whitespace, context=context)
185 diff = ''.join(result)
195 diff = ''.join(result)
186 return MercurialDiff(diff)
196 return MercurialDiff(diff)
187
197
188 def _fix_path(self, path):
198 def _fix_path(self, path):
189 """
199 """
190 Mercurial keeps filenodes as str so we need to encode from unicode
200 Mercurial keeps filenodes as str so we need to encode from unicode
191 to str.
201 to str.
192 """
202 """
193 return safe_str(super(MercurialCommit, self)._fix_path(path))
203 return safe_str(super(MercurialCommit, self)._fix_path(path))
194
204
195 def _get_kind(self, path):
205 def _get_kind(self, path):
196 path = self._fix_path(path)
206 path = self._fix_path(path)
197 if path in self._file_paths:
207 if path in self._file_paths:
198 return NodeKind.FILE
208 return NodeKind.FILE
199 elif path in self._dir_paths:
209 elif path in self._dir_paths:
200 return NodeKind.DIR
210 return NodeKind.DIR
201 else:
211 else:
202 raise CommitError(
212 raise CommitError(
203 "Node does not exist at the given path '%s'" % (path, ))
213 "Node does not exist at the given path '%s'" % (path, ))
204
214
205 def _get_filectx(self, path):
215 def _get_filectx(self, path):
206 path = self._fix_path(path)
216 path = self._fix_path(path)
207 if self._get_kind(path) != NodeKind.FILE:
217 if self._get_kind(path) != NodeKind.FILE:
208 raise CommitError(
218 raise CommitError(
209 "File does not exist for idx %s at '%s'" % (self.raw_id, path))
219 "File does not exist for idx %s at '%s'" % (self.raw_id, path))
210 return path
220 return path
211
221
212 def get_file_mode(self, path):
222 def get_file_mode(self, path):
213 """
223 """
214 Returns stat mode of the file at the given ``path``.
224 Returns stat mode of the file at the given ``path``.
215 """
225 """
216 path = self._get_filectx(path)
226 path = self._get_filectx(path)
217 if 'x' in self._remote.fctx_flags(self.idx, path):
227 if 'x' in self._remote.fctx_flags(self.idx, path):
218 return base.FILEMODE_EXECUTABLE
228 return base.FILEMODE_EXECUTABLE
219 else:
229 else:
220 return base.FILEMODE_DEFAULT
230 return base.FILEMODE_DEFAULT
221
231
222 def is_link(self, path):
232 def is_link(self, path):
223 path = self._get_filectx(path)
233 path = self._get_filectx(path)
224 return 'l' in self._remote.fctx_flags(self.idx, path)
234 return 'l' in self._remote.fctx_flags(self.idx, path)
225
235
226 def get_file_content(self, path):
236 def get_file_content(self, path):
227 """
237 """
228 Returns content of the file at given ``path``.
238 Returns content of the file at given ``path``.
229 """
239 """
230 path = self._get_filectx(path)
240 path = self._get_filectx(path)
231 return self._remote.fctx_data(self.idx, path)
241 return self._remote.fctx_data(self.idx, path)
232
242
233 def get_file_size(self, path):
243 def get_file_size(self, path):
234 """
244 """
235 Returns size of the file at given ``path``.
245 Returns size of the file at given ``path``.
236 """
246 """
237 path = self._get_filectx(path)
247 path = self._get_filectx(path)
238 return self._remote.fctx_size(self.idx, path)
248 return self._remote.fctx_size(self.idx, path)
239
249
240 def get_file_history(self, path, limit=None, pre_load=None):
250 def get_file_history(self, path, limit=None, pre_load=None):
241 """
251 """
242 Returns history of file as reversed list of `MercurialCommit` objects
252 Returns history of file as reversed list of `MercurialCommit` objects
243 for which file at given ``path`` has been modified.
253 for which file at given ``path`` has been modified.
244 """
254 """
245 path = self._get_filectx(path)
255 path = self._get_filectx(path)
246 hist = self._remote.file_history(self.idx, path, limit)
256 hist = self._remote.file_history(self.idx, path, limit)
247 return [
257 return [
248 self.repository.get_commit(commit_id=commit_id, pre_load=pre_load)
258 self.repository.get_commit(commit_id=commit_id, pre_load=pre_load)
249 for commit_id in hist]
259 for commit_id in hist]
250
260
251 def get_file_annotate(self, path, pre_load=None):
261 def get_file_annotate(self, path, pre_load=None):
252 """
262 """
253 Returns a generator of four element tuples with
263 Returns a generator of four element tuples with
254 lineno, commit_id, commit lazy loader and line
264 lineno, commit_id, commit lazy loader and line
255 """
265 """
256 result = self._remote.fctx_annotate(self.idx, path)
266 result = self._remote.fctx_annotate(self.idx, path)
257
267
258 for ln_no, commit_id, content in result:
268 for ln_no, commit_id, content in result:
259 yield (
269 yield (
260 ln_no, commit_id,
270 ln_no, commit_id,
261 lambda: self.repository.get_commit(commit_id=commit_id,
271 lambda: self.repository.get_commit(commit_id=commit_id,
262 pre_load=pre_load),
272 pre_load=pre_load),
263 content)
273 content)
264
274
265 def get_nodes(self, path):
275 def get_nodes(self, path):
266 """
276 """
267 Returns combined ``DirNode`` and ``FileNode`` objects list representing
277 Returns combined ``DirNode`` and ``FileNode`` objects list representing
268 state of commit at the given ``path``. If node at the given ``path``
278 state of commit at the given ``path``. If node at the given ``path``
269 is not instance of ``DirNode``, CommitError would be raised.
279 is not instance of ``DirNode``, CommitError would be raised.
270 """
280 """
271
281
272 if self._get_kind(path) != NodeKind.DIR:
282 if self._get_kind(path) != NodeKind.DIR:
273 raise CommitError(
283 raise CommitError(
274 "Directory does not exist for idx %s at '%s'" %
284 "Directory does not exist for idx %s at '%s'" %
275 (self.idx, path))
285 (self.idx, path))
276 path = self._fix_path(path)
286 path = self._fix_path(path)
277
287
278 filenodes = [
288 filenodes = [
279 FileNode(f, commit=self) for f in self._file_paths
289 FileNode(f, commit=self) for f in self._file_paths
280 if os.path.dirname(f) == path]
290 if os.path.dirname(f) == path]
281 # TODO: johbo: Check if this can be done in a more obvious way
291 # TODO: johbo: Check if this can be done in a more obvious way
282 dirs = path == '' and '' or [
292 dirs = path == '' and '' or [
283 d for d in self._dir_paths
293 d for d in self._dir_paths
284 if d and vcspath.dirname(d) == path]
294 if d and vcspath.dirname(d) == path]
285 dirnodes = [
295 dirnodes = [
286 DirNode(d, commit=self) for d in dirs
296 DirNode(d, commit=self) for d in dirs
287 if os.path.dirname(d) == path]
297 if os.path.dirname(d) == path]
288
298
289 alias = self.repository.alias
299 alias = self.repository.alias
290 for k, vals in self._submodules.iteritems():
300 for k, vals in self._submodules.iteritems():
291 loc = vals[0]
301 loc = vals[0]
292 commit = vals[1]
302 commit = vals[1]
293 dirnodes.append(
303 dirnodes.append(
294 SubModuleNode(k, url=loc, commit=commit, alias=alias))
304 SubModuleNode(k, url=loc, commit=commit, alias=alias))
295 nodes = dirnodes + filenodes
305 nodes = dirnodes + filenodes
296 # cache nodes
306 # cache nodes
297 for node in nodes:
307 for node in nodes:
298 self.nodes[node.path] = node
308 self.nodes[node.path] = node
299 nodes.sort()
309 nodes.sort()
300
310
301 return nodes
311 return nodes
302
312
303 def get_node(self, path, pre_load=None):
313 def get_node(self, path, pre_load=None):
304 """
314 """
305 Returns `Node` object from the given `path`. If there is no node at
315 Returns `Node` object from the given `path`. If there is no node at
306 the given `path`, `NodeDoesNotExistError` would be raised.
316 the given `path`, `NodeDoesNotExistError` would be raised.
307 """
317 """
308 path = self._fix_path(path)
318 path = self._fix_path(path)
309
319
310 if path not in self.nodes:
320 if path not in self.nodes:
311 if path in self._file_paths:
321 if path in self._file_paths:
312 node = FileNode(path, commit=self, pre_load=pre_load)
322 node = FileNode(path, commit=self, pre_load=pre_load)
313 elif path in self._dir_paths:
323 elif path in self._dir_paths:
314 if path == '':
324 if path == '':
315 node = RootNode(commit=self)
325 node = RootNode(commit=self)
316 else:
326 else:
317 node = DirNode(path, commit=self)
327 node = DirNode(path, commit=self)
318 else:
328 else:
319 raise self.no_node_at_path(path)
329 raise self.no_node_at_path(path)
320
330
321 # cache node
331 # cache node
322 self.nodes[path] = node
332 self.nodes[path] = node
323 return self.nodes[path]
333 return self.nodes[path]
324
334
325 def get_largefile_node(self, path):
335 def get_largefile_node(self, path):
326
336
327 if self._remote.is_large_file(path):
337 if self._remote.is_large_file(path):
328 # content of that file regular FileNode is the hash of largefile
338 # content of that file regular FileNode is the hash of largefile
329 file_id = self.get_file_content(path).strip()
339 file_id = self.get_file_content(path).strip()
330
340
331 if self._remote.in_largefiles_store(file_id):
341 if self._remote.in_largefiles_store(file_id):
332 lf_path = self._remote.store_path(file_id)
342 lf_path = self._remote.store_path(file_id)
333 return LargeFileNode(lf_path, commit=self, org_path=path)
343 return LargeFileNode(lf_path, commit=self, org_path=path)
334 elif self._remote.in_user_cache(file_id):
344 elif self._remote.in_user_cache(file_id):
335 lf_path = self._remote.store_path(file_id)
345 lf_path = self._remote.store_path(file_id)
336 self._remote.link(file_id, path)
346 self._remote.link(file_id, path)
337 return LargeFileNode(lf_path, commit=self, org_path=path)
347 return LargeFileNode(lf_path, commit=self, org_path=path)
338
348
339 @LazyProperty
349 @LazyProperty
340 def _submodules(self):
350 def _submodules(self):
341 """
351 """
342 Returns a dictionary with submodule information from substate file
352 Returns a dictionary with submodule information from substate file
343 of hg repository.
353 of hg repository.
344 """
354 """
345 return self._remote.ctx_substate(self.idx)
355 return self._remote.ctx_substate(self.idx)
346
356
347 @LazyProperty
357 @LazyProperty
348 def affected_files(self):
358 def affected_files(self):
349 """
359 """
350 Gets a fast accessible file changes for given commit
360 Gets a fast accessible file changes for given commit
351 """
361 """
352 return self._remote.ctx_files(self.idx)
362 return self._remote.ctx_files(self.idx)
353
363
354 @property
364 @property
355 def added(self):
365 def added(self):
356 """
366 """
357 Returns list of added ``FileNode`` objects.
367 Returns list of added ``FileNode`` objects.
358 """
368 """
359 return AddedFileNodesGenerator([n for n in self.status[1]], self)
369 return AddedFileNodesGenerator([n for n in self.status[1]], self)
360
370
361 @property
371 @property
362 def changed(self):
372 def changed(self):
363 """
373 """
364 Returns list of modified ``FileNode`` objects.
374 Returns list of modified ``FileNode`` objects.
365 """
375 """
366 return ChangedFileNodesGenerator([n for n in self.status[0]], self)
376 return ChangedFileNodesGenerator([n for n in self.status[0]], self)
367
377
368 @property
378 @property
369 def removed(self):
379 def removed(self):
370 """
380 """
371 Returns list of removed ``FileNode`` objects.
381 Returns list of removed ``FileNode`` objects.
372 """
382 """
373 return RemovedFileNodesGenerator([n for n in self.status[2]], self)
383 return RemovedFileNodesGenerator([n for n in self.status[2]], self)
@@ -1,561 +1,562 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 this is forms validation classes
22 this is forms validation classes
23 http://formencode.org/module-formencode.validators.html
23 http://formencode.org/module-formencode.validators.html
24 for list off all availible validators
24 for list off all availible validators
25
25
26 we can create our own validators
26 we can create our own validators
27
27
28 The table below outlines the options which can be used in a schema in addition to the validators themselves
28 The table below outlines the options which can be used in a schema in addition to the validators themselves
29 pre_validators [] These validators will be applied before the schema
29 pre_validators [] These validators will be applied before the schema
30 chained_validators [] These validators will be applied after the schema
30 chained_validators [] These validators will be applied after the schema
31 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
31 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
32 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
32 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
33 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
33 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
34 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
34 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
35
35
36
36
37 <name> = formencode.validators.<name of validator>
37 <name> = formencode.validators.<name of validator>
38 <name> must equal form name
38 <name> must equal form name
39 list=[1,2,3,4,5]
39 list=[1,2,3,4,5]
40 for SELECT use formencode.All(OneOf(list), Int())
40 for SELECT use formencode.All(OneOf(list), Int())
41
41
42 """
42 """
43
43
44 import deform
44 import deform
45 import logging
45 import logging
46 import formencode
46 import formencode
47
47
48 from pkg_resources import resource_filename
48 from pkg_resources import resource_filename
49 from formencode import All, Pipe
49 from formencode import All, Pipe
50
50
51 from pylons.i18n.translation import _
51 from pylons.i18n.translation import _
52
52
53 from rhodecode import BACKENDS
53 from rhodecode import BACKENDS
54 from rhodecode.lib import helpers
54 from rhodecode.lib import helpers
55 from rhodecode.model import validators as v
55 from rhodecode.model import validators as v
56
56
57 log = logging.getLogger(__name__)
57 log = logging.getLogger(__name__)
58
58
59
59
60 deform_templates = resource_filename('deform', 'templates')
60 deform_templates = resource_filename('deform', 'templates')
61 rhodecode_templates = resource_filename('rhodecode', 'templates/forms')
61 rhodecode_templates = resource_filename('rhodecode', 'templates/forms')
62 search_path = (rhodecode_templates, deform_templates)
62 search_path = (rhodecode_templates, deform_templates)
63
63
64
64
65 class RhodecodeFormZPTRendererFactory(deform.ZPTRendererFactory):
65 class RhodecodeFormZPTRendererFactory(deform.ZPTRendererFactory):
66 """ Subclass of ZPTRendererFactory to add rhodecode context variables """
66 """ Subclass of ZPTRendererFactory to add rhodecode context variables """
67 def __call__(self, template_name, **kw):
67 def __call__(self, template_name, **kw):
68 kw['h'] = helpers
68 kw['h'] = helpers
69 return self.load(template_name)(**kw)
69 return self.load(template_name)(**kw)
70
70
71
71
72 form_renderer = RhodecodeFormZPTRendererFactory(search_path)
72 form_renderer = RhodecodeFormZPTRendererFactory(search_path)
73 deform.Form.set_default_renderer(form_renderer)
73 deform.Form.set_default_renderer(form_renderer)
74
74
75
75
76 def LoginForm():
76 def LoginForm():
77 class _LoginForm(formencode.Schema):
77 class _LoginForm(formencode.Schema):
78 allow_extra_fields = True
78 allow_extra_fields = True
79 filter_extra_fields = True
79 filter_extra_fields = True
80 username = v.UnicodeString(
80 username = v.UnicodeString(
81 strip=True,
81 strip=True,
82 min=1,
82 min=1,
83 not_empty=True,
83 not_empty=True,
84 messages={
84 messages={
85 'empty': _(u'Please enter a login'),
85 'empty': _(u'Please enter a login'),
86 'tooShort': _(u'Enter a value %(min)i characters long or more')
86 'tooShort': _(u'Enter a value %(min)i characters long or more')
87 }
87 }
88 )
88 )
89
89
90 password = v.UnicodeString(
90 password = v.UnicodeString(
91 strip=False,
91 strip=False,
92 min=3,
92 min=3,
93 not_empty=True,
93 not_empty=True,
94 messages={
94 messages={
95 'empty': _(u'Please enter a password'),
95 'empty': _(u'Please enter a password'),
96 'tooShort': _(u'Enter %(min)i characters or more')}
96 'tooShort': _(u'Enter %(min)i characters or more')}
97 )
97 )
98
98
99 remember = v.StringBoolean(if_missing=False)
99 remember = v.StringBoolean(if_missing=False)
100
100
101 chained_validators = [v.ValidAuth()]
101 chained_validators = [v.ValidAuth()]
102 return _LoginForm
102 return _LoginForm
103
103
104
104
105 def UserForm(edit=False, available_languages=[], old_data={}):
105 def UserForm(edit=False, available_languages=[], old_data={}):
106 class _UserForm(formencode.Schema):
106 class _UserForm(formencode.Schema):
107 allow_extra_fields = True
107 allow_extra_fields = True
108 filter_extra_fields = True
108 filter_extra_fields = True
109 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
109 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
110 v.ValidUsername(edit, old_data))
110 v.ValidUsername(edit, old_data))
111 if edit:
111 if edit:
112 new_password = All(
112 new_password = All(
113 v.ValidPassword(),
113 v.ValidPassword(),
114 v.UnicodeString(strip=False, min=6, not_empty=False)
114 v.UnicodeString(strip=False, min=6, not_empty=False)
115 )
115 )
116 password_confirmation = All(
116 password_confirmation = All(
117 v.ValidPassword(),
117 v.ValidPassword(),
118 v.UnicodeString(strip=False, min=6, not_empty=False),
118 v.UnicodeString(strip=False, min=6, not_empty=False),
119 )
119 )
120 admin = v.StringBoolean(if_missing=False)
120 admin = v.StringBoolean(if_missing=False)
121 else:
121 else:
122 password = All(
122 password = All(
123 v.ValidPassword(),
123 v.ValidPassword(),
124 v.UnicodeString(strip=False, min=6, not_empty=True)
124 v.UnicodeString(strip=False, min=6, not_empty=True)
125 )
125 )
126 password_confirmation = All(
126 password_confirmation = All(
127 v.ValidPassword(),
127 v.ValidPassword(),
128 v.UnicodeString(strip=False, min=6, not_empty=False)
128 v.UnicodeString(strip=False, min=6, not_empty=False)
129 )
129 )
130
130
131 password_change = v.StringBoolean(if_missing=False)
131 password_change = v.StringBoolean(if_missing=False)
132 create_repo_group = v.StringBoolean(if_missing=False)
132 create_repo_group = v.StringBoolean(if_missing=False)
133
133
134 active = v.StringBoolean(if_missing=False)
134 active = v.StringBoolean(if_missing=False)
135 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
135 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
136 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
136 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
137 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
137 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
138 extern_name = v.UnicodeString(strip=True)
138 extern_name = v.UnicodeString(strip=True)
139 extern_type = v.UnicodeString(strip=True)
139 extern_type = v.UnicodeString(strip=True)
140 language = v.OneOf(available_languages, hideList=False,
140 language = v.OneOf(available_languages, hideList=False,
141 testValueList=True, if_missing=None)
141 testValueList=True, if_missing=None)
142 chained_validators = [v.ValidPasswordsMatch()]
142 chained_validators = [v.ValidPasswordsMatch()]
143 return _UserForm
143 return _UserForm
144
144
145
145
146 def UserGroupForm(edit=False, old_data=None, allow_disabled=False):
146 def UserGroupForm(edit=False, old_data=None, allow_disabled=False):
147 old_data = old_data or {}
147 old_data = old_data or {}
148
148
149 class _UserGroupForm(formencode.Schema):
149 class _UserGroupForm(formencode.Schema):
150 allow_extra_fields = True
150 allow_extra_fields = True
151 filter_extra_fields = True
151 filter_extra_fields = True
152
152
153 users_group_name = All(
153 users_group_name = All(
154 v.UnicodeString(strip=True, min=1, not_empty=True),
154 v.UnicodeString(strip=True, min=1, not_empty=True),
155 v.ValidUserGroup(edit, old_data)
155 v.ValidUserGroup(edit, old_data)
156 )
156 )
157 user_group_description = v.UnicodeString(strip=True, min=1,
157 user_group_description = v.UnicodeString(strip=True, min=1,
158 not_empty=False)
158 not_empty=False)
159
159
160 users_group_active = v.StringBoolean(if_missing=False)
160 users_group_active = v.StringBoolean(if_missing=False)
161
161
162 if edit:
162 if edit:
163 # this is user group owner
163 # this is user group owner
164 user = All(
164 user = All(
165 v.UnicodeString(not_empty=True),
165 v.UnicodeString(not_empty=True),
166 v.ValidRepoUser(allow_disabled))
166 v.ValidRepoUser(allow_disabled))
167 return _UserGroupForm
167 return _UserGroupForm
168
168
169
169
170 def RepoGroupForm(edit=False, old_data=None, available_groups=None,
170 def RepoGroupForm(edit=False, old_data=None, available_groups=None,
171 can_create_in_root=False, allow_disabled=False):
171 can_create_in_root=False, allow_disabled=False):
172 old_data = old_data or {}
172 old_data = old_data or {}
173 available_groups = available_groups or []
173 available_groups = available_groups or []
174
174
175 class _RepoGroupForm(formencode.Schema):
175 class _RepoGroupForm(formencode.Schema):
176 allow_extra_fields = True
176 allow_extra_fields = True
177 filter_extra_fields = False
177 filter_extra_fields = False
178
178
179 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
179 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
180 v.SlugifyName(),)
180 v.SlugifyName(),)
181 group_description = v.UnicodeString(strip=True, min=1,
181 group_description = v.UnicodeString(strip=True, min=1,
182 not_empty=False)
182 not_empty=False)
183 group_copy_permissions = v.StringBoolean(if_missing=False)
183 group_copy_permissions = v.StringBoolean(if_missing=False)
184
184
185 group_parent_id = v.OneOf(available_groups, hideList=False,
185 group_parent_id = v.OneOf(available_groups, hideList=False,
186 testValueList=True, not_empty=True)
186 testValueList=True, not_empty=True)
187 enable_locking = v.StringBoolean(if_missing=False)
187 enable_locking = v.StringBoolean(if_missing=False)
188 chained_validators = [
188 chained_validators = [
189 v.ValidRepoGroup(edit, old_data, can_create_in_root)]
189 v.ValidRepoGroup(edit, old_data, can_create_in_root)]
190
190
191 if edit:
191 if edit:
192 # this is repo group owner
192 # this is repo group owner
193 user = All(
193 user = All(
194 v.UnicodeString(not_empty=True),
194 v.UnicodeString(not_empty=True),
195 v.ValidRepoUser(allow_disabled))
195 v.ValidRepoUser(allow_disabled))
196
196
197 return _RepoGroupForm
197 return _RepoGroupForm
198
198
199
199
200 def RegisterForm(edit=False, old_data={}):
200 def RegisterForm(edit=False, old_data={}):
201 class _RegisterForm(formencode.Schema):
201 class _RegisterForm(formencode.Schema):
202 allow_extra_fields = True
202 allow_extra_fields = True
203 filter_extra_fields = True
203 filter_extra_fields = True
204 username = All(
204 username = All(
205 v.ValidUsername(edit, old_data),
205 v.ValidUsername(edit, old_data),
206 v.UnicodeString(strip=True, min=1, not_empty=True)
206 v.UnicodeString(strip=True, min=1, not_empty=True)
207 )
207 )
208 password = All(
208 password = All(
209 v.ValidPassword(),
209 v.ValidPassword(),
210 v.UnicodeString(strip=False, min=6, not_empty=True)
210 v.UnicodeString(strip=False, min=6, not_empty=True)
211 )
211 )
212 password_confirmation = All(
212 password_confirmation = All(
213 v.ValidPassword(),
213 v.ValidPassword(),
214 v.UnicodeString(strip=False, min=6, not_empty=True)
214 v.UnicodeString(strip=False, min=6, not_empty=True)
215 )
215 )
216 active = v.StringBoolean(if_missing=False)
216 active = v.StringBoolean(if_missing=False)
217 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
217 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
218 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
218 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
219 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
219 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
220
220
221 chained_validators = [v.ValidPasswordsMatch()]
221 chained_validators = [v.ValidPasswordsMatch()]
222
222
223 return _RegisterForm
223 return _RegisterForm
224
224
225
225
226 def PasswordResetForm():
226 def PasswordResetForm():
227 class _PasswordResetForm(formencode.Schema):
227 class _PasswordResetForm(formencode.Schema):
228 allow_extra_fields = True
228 allow_extra_fields = True
229 filter_extra_fields = True
229 filter_extra_fields = True
230 email = All(v.ValidSystemEmail(), v.Email(not_empty=True))
230 email = All(v.ValidSystemEmail(), v.Email(not_empty=True))
231 return _PasswordResetForm
231 return _PasswordResetForm
232
232
233
233
234 def RepoForm(edit=False, old_data=None, repo_groups=None, landing_revs=None,
234 def RepoForm(edit=False, old_data=None, repo_groups=None, landing_revs=None,
235 allow_disabled=False):
235 allow_disabled=False):
236 old_data = old_data or {}
236 old_data = old_data or {}
237 repo_groups = repo_groups or []
237 repo_groups = repo_groups or []
238 landing_revs = landing_revs or []
238 landing_revs = landing_revs or []
239 supported_backends = BACKENDS.keys()
239 supported_backends = BACKENDS.keys()
240
240
241 class _RepoForm(formencode.Schema):
241 class _RepoForm(formencode.Schema):
242 allow_extra_fields = True
242 allow_extra_fields = True
243 filter_extra_fields = False
243 filter_extra_fields = False
244 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
244 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
245 v.SlugifyName(), v.CannotHaveGitSuffix())
245 v.SlugifyName(), v.CannotHaveGitSuffix())
246 repo_group = All(v.CanWriteGroup(old_data),
246 repo_group = All(v.CanWriteGroup(old_data),
247 v.OneOf(repo_groups, hideList=True))
247 v.OneOf(repo_groups, hideList=True))
248 repo_type = v.OneOf(supported_backends, required=False,
248 repo_type = v.OneOf(supported_backends, required=False,
249 if_missing=old_data.get('repo_type'))
249 if_missing=old_data.get('repo_type'))
250 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
250 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
251 repo_private = v.StringBoolean(if_missing=False)
251 repo_private = v.StringBoolean(if_missing=False)
252 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
252 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
253 repo_copy_permissions = v.StringBoolean(if_missing=False)
253 repo_copy_permissions = v.StringBoolean(if_missing=False)
254 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
254 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
255
255
256 repo_enable_statistics = v.StringBoolean(if_missing=False)
256 repo_enable_statistics = v.StringBoolean(if_missing=False)
257 repo_enable_downloads = v.StringBoolean(if_missing=False)
257 repo_enable_downloads = v.StringBoolean(if_missing=False)
258 repo_enable_locking = v.StringBoolean(if_missing=False)
258 repo_enable_locking = v.StringBoolean(if_missing=False)
259
259
260 if edit:
260 if edit:
261 # this is repo owner
261 # this is repo owner
262 user = All(
262 user = All(
263 v.UnicodeString(not_empty=True),
263 v.UnicodeString(not_empty=True),
264 v.ValidRepoUser(allow_disabled))
264 v.ValidRepoUser(allow_disabled))
265 clone_uri_change = v.UnicodeString(
265 clone_uri_change = v.UnicodeString(
266 not_empty=False, if_missing=v.Missing)
266 not_empty=False, if_missing=v.Missing)
267
267
268 chained_validators = [v.ValidCloneUri(),
268 chained_validators = [v.ValidCloneUri(),
269 v.ValidRepoName(edit, old_data)]
269 v.ValidRepoName(edit, old_data)]
270 return _RepoForm
270 return _RepoForm
271
271
272
272
273 def RepoPermsForm():
273 def RepoPermsForm():
274 class _RepoPermsForm(formencode.Schema):
274 class _RepoPermsForm(formencode.Schema):
275 allow_extra_fields = True
275 allow_extra_fields = True
276 filter_extra_fields = False
276 filter_extra_fields = False
277 chained_validators = [v.ValidPerms(type_='repo')]
277 chained_validators = [v.ValidPerms(type_='repo')]
278 return _RepoPermsForm
278 return _RepoPermsForm
279
279
280
280
281 def RepoGroupPermsForm(valid_recursive_choices):
281 def RepoGroupPermsForm(valid_recursive_choices):
282 class _RepoGroupPermsForm(formencode.Schema):
282 class _RepoGroupPermsForm(formencode.Schema):
283 allow_extra_fields = True
283 allow_extra_fields = True
284 filter_extra_fields = False
284 filter_extra_fields = False
285 recursive = v.OneOf(valid_recursive_choices)
285 recursive = v.OneOf(valid_recursive_choices)
286 chained_validators = [v.ValidPerms(type_='repo_group')]
286 chained_validators = [v.ValidPerms(type_='repo_group')]
287 return _RepoGroupPermsForm
287 return _RepoGroupPermsForm
288
288
289
289
290 def UserGroupPermsForm():
290 def UserGroupPermsForm():
291 class _UserPermsForm(formencode.Schema):
291 class _UserPermsForm(formencode.Schema):
292 allow_extra_fields = True
292 allow_extra_fields = True
293 filter_extra_fields = False
293 filter_extra_fields = False
294 chained_validators = [v.ValidPerms(type_='user_group')]
294 chained_validators = [v.ValidPerms(type_='user_group')]
295 return _UserPermsForm
295 return _UserPermsForm
296
296
297
297
298 def RepoFieldForm():
298 def RepoFieldForm():
299 class _RepoFieldForm(formencode.Schema):
299 class _RepoFieldForm(formencode.Schema):
300 filter_extra_fields = True
300 filter_extra_fields = True
301 allow_extra_fields = True
301 allow_extra_fields = True
302
302
303 new_field_key = All(v.FieldKey(),
303 new_field_key = All(v.FieldKey(),
304 v.UnicodeString(strip=True, min=3, not_empty=True))
304 v.UnicodeString(strip=True, min=3, not_empty=True))
305 new_field_value = v.UnicodeString(not_empty=False, if_missing=u'')
305 new_field_value = v.UnicodeString(not_empty=False, if_missing=u'')
306 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
306 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
307 if_missing='str')
307 if_missing='str')
308 new_field_label = v.UnicodeString(not_empty=False)
308 new_field_label = v.UnicodeString(not_empty=False)
309 new_field_desc = v.UnicodeString(not_empty=False)
309 new_field_desc = v.UnicodeString(not_empty=False)
310
310
311 return _RepoFieldForm
311 return _RepoFieldForm
312
312
313
313
314 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
314 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
315 repo_groups=[], landing_revs=[]):
315 repo_groups=[], landing_revs=[]):
316 class _RepoForkForm(formencode.Schema):
316 class _RepoForkForm(formencode.Schema):
317 allow_extra_fields = True
317 allow_extra_fields = True
318 filter_extra_fields = False
318 filter_extra_fields = False
319 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
319 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
320 v.SlugifyName())
320 v.SlugifyName())
321 repo_group = All(v.CanWriteGroup(),
321 repo_group = All(v.CanWriteGroup(),
322 v.OneOf(repo_groups, hideList=True))
322 v.OneOf(repo_groups, hideList=True))
323 repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends))
323 repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends))
324 description = v.UnicodeString(strip=True, min=1, not_empty=True)
324 description = v.UnicodeString(strip=True, min=1, not_empty=True)
325 private = v.StringBoolean(if_missing=False)
325 private = v.StringBoolean(if_missing=False)
326 copy_permissions = v.StringBoolean(if_missing=False)
326 copy_permissions = v.StringBoolean(if_missing=False)
327 fork_parent_id = v.UnicodeString()
327 fork_parent_id = v.UnicodeString()
328 chained_validators = [v.ValidForkName(edit, old_data)]
328 chained_validators = [v.ValidForkName(edit, old_data)]
329 landing_rev = v.OneOf(landing_revs, hideList=True)
329 landing_rev = v.OneOf(landing_revs, hideList=True)
330
330
331 return _RepoForkForm
331 return _RepoForkForm
332
332
333
333
334 def ApplicationSettingsForm():
334 def ApplicationSettingsForm():
335 class _ApplicationSettingsForm(formencode.Schema):
335 class _ApplicationSettingsForm(formencode.Schema):
336 allow_extra_fields = True
336 allow_extra_fields = True
337 filter_extra_fields = False
337 filter_extra_fields = False
338 rhodecode_title = v.UnicodeString(strip=True, max=40, not_empty=False)
338 rhodecode_title = v.UnicodeString(strip=True, max=40, not_empty=False)
339 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
339 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
340 rhodecode_pre_code = v.UnicodeString(strip=True, min=1, not_empty=False)
340 rhodecode_pre_code = v.UnicodeString(strip=True, min=1, not_empty=False)
341 rhodecode_post_code = v.UnicodeString(strip=True, min=1, not_empty=False)
341 rhodecode_post_code = v.UnicodeString(strip=True, min=1, not_empty=False)
342 rhodecode_captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
342 rhodecode_captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
343 rhodecode_captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
343 rhodecode_captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
344 rhodecode_create_personal_repo_group = v.StringBoolean(if_missing=False)
344 rhodecode_create_personal_repo_group = v.StringBoolean(if_missing=False)
345 rhodecode_personal_repo_group_pattern = v.UnicodeString(strip=True, min=1, not_empty=False)
345 rhodecode_personal_repo_group_pattern = v.UnicodeString(strip=True, min=1, not_empty=False)
346
346
347 return _ApplicationSettingsForm
347 return _ApplicationSettingsForm
348
348
349
349
350 def ApplicationVisualisationForm():
350 def ApplicationVisualisationForm():
351 class _ApplicationVisualisationForm(formencode.Schema):
351 class _ApplicationVisualisationForm(formencode.Schema):
352 allow_extra_fields = True
352 allow_extra_fields = True
353 filter_extra_fields = False
353 filter_extra_fields = False
354 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
354 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
355 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
355 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
356 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
356 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
357
357
358 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
358 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
359 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
359 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
360 rhodecode_dashboard_items = v.Int(min=5, not_empty=True)
360 rhodecode_dashboard_items = v.Int(min=5, not_empty=True)
361 rhodecode_admin_grid_items = v.Int(min=5, not_empty=True)
361 rhodecode_admin_grid_items = v.Int(min=5, not_empty=True)
362 rhodecode_show_version = v.StringBoolean(if_missing=False)
362 rhodecode_show_version = v.StringBoolean(if_missing=False)
363 rhodecode_use_gravatar = v.StringBoolean(if_missing=False)
363 rhodecode_use_gravatar = v.StringBoolean(if_missing=False)
364 rhodecode_markup_renderer = v.OneOf(['markdown', 'rst'])
364 rhodecode_markup_renderer = v.OneOf(['markdown', 'rst'])
365 rhodecode_gravatar_url = v.UnicodeString(min=3)
365 rhodecode_gravatar_url = v.UnicodeString(min=3)
366 rhodecode_clone_uri_tmpl = v.UnicodeString(min=3)
366 rhodecode_clone_uri_tmpl = v.UnicodeString(min=3)
367 rhodecode_support_url = v.UnicodeString()
367 rhodecode_support_url = v.UnicodeString()
368 rhodecode_show_revision_number = v.StringBoolean(if_missing=False)
368 rhodecode_show_revision_number = v.StringBoolean(if_missing=False)
369 rhodecode_show_sha_length = v.Int(min=4, not_empty=True)
369 rhodecode_show_sha_length = v.Int(min=4, not_empty=True)
370
370
371 return _ApplicationVisualisationForm
371 return _ApplicationVisualisationForm
372
372
373
373
374 class _BaseVcsSettingsForm(formencode.Schema):
374 class _BaseVcsSettingsForm(formencode.Schema):
375 allow_extra_fields = True
375 allow_extra_fields = True
376 filter_extra_fields = False
376 filter_extra_fields = False
377 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
377 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
378 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
378 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
379 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
379 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
380
380
381 # PR/Code-review
381 # PR/Code-review
382 rhodecode_pr_merge_enabled = v.StringBoolean(if_missing=False)
382 rhodecode_pr_merge_enabled = v.StringBoolean(if_missing=False)
383 rhodecode_use_outdated_comments = v.StringBoolean(if_missing=False)
383 rhodecode_use_outdated_comments = v.StringBoolean(if_missing=False)
384
384
385 # hg
385 # hg
386 extensions_largefiles = v.StringBoolean(if_missing=False)
386 extensions_largefiles = v.StringBoolean(if_missing=False)
387 extensions_evolve = v.StringBoolean(if_missing=False)
387 phases_publish = v.StringBoolean(if_missing=False)
388 phases_publish = v.StringBoolean(if_missing=False)
388 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
389 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
389
390
390 # git
391 # git
391 vcs_git_lfs_enabled = v.StringBoolean(if_missing=False)
392 vcs_git_lfs_enabled = v.StringBoolean(if_missing=False)
392
393
393 # svn
394 # svn
394 vcs_svn_proxy_http_requests_enabled = v.StringBoolean(if_missing=False)
395 vcs_svn_proxy_http_requests_enabled = v.StringBoolean(if_missing=False)
395 vcs_svn_proxy_http_server_url = v.UnicodeString(strip=True, if_missing=None)
396 vcs_svn_proxy_http_server_url = v.UnicodeString(strip=True, if_missing=None)
396
397
397
398
398 def ApplicationUiSettingsForm():
399 def ApplicationUiSettingsForm():
399 class _ApplicationUiSettingsForm(_BaseVcsSettingsForm):
400 class _ApplicationUiSettingsForm(_BaseVcsSettingsForm):
400 web_push_ssl = v.StringBoolean(if_missing=False)
401 web_push_ssl = v.StringBoolean(if_missing=False)
401 paths_root_path = All(
402 paths_root_path = All(
402 v.ValidPath(),
403 v.ValidPath(),
403 v.UnicodeString(strip=True, min=1, not_empty=True)
404 v.UnicodeString(strip=True, min=1, not_empty=True)
404 )
405 )
405 largefiles_usercache = All(
406 largefiles_usercache = All(
406 v.ValidPath(),
407 v.ValidPath(),
407 v.UnicodeString(strip=True, min=2, not_empty=True))
408 v.UnicodeString(strip=True, min=2, not_empty=True))
408 vcs_git_lfs_store_location = All(
409 vcs_git_lfs_store_location = All(
409 v.ValidPath(),
410 v.ValidPath(),
410 v.UnicodeString(strip=True, min=2, not_empty=True))
411 v.UnicodeString(strip=True, min=2, not_empty=True))
411 extensions_hgsubversion = v.StringBoolean(if_missing=False)
412 extensions_hgsubversion = v.StringBoolean(if_missing=False)
412 extensions_hggit = v.StringBoolean(if_missing=False)
413 extensions_hggit = v.StringBoolean(if_missing=False)
413 new_svn_branch = v.ValidSvnPattern(section='vcs_svn_branch')
414 new_svn_branch = v.ValidSvnPattern(section='vcs_svn_branch')
414 new_svn_tag = v.ValidSvnPattern(section='vcs_svn_tag')
415 new_svn_tag = v.ValidSvnPattern(section='vcs_svn_tag')
415
416
416 return _ApplicationUiSettingsForm
417 return _ApplicationUiSettingsForm
417
418
418
419
419 def RepoVcsSettingsForm(repo_name):
420 def RepoVcsSettingsForm(repo_name):
420 class _RepoVcsSettingsForm(_BaseVcsSettingsForm):
421 class _RepoVcsSettingsForm(_BaseVcsSettingsForm):
421 inherit_global_settings = v.StringBoolean(if_missing=False)
422 inherit_global_settings = v.StringBoolean(if_missing=False)
422 new_svn_branch = v.ValidSvnPattern(
423 new_svn_branch = v.ValidSvnPattern(
423 section='vcs_svn_branch', repo_name=repo_name)
424 section='vcs_svn_branch', repo_name=repo_name)
424 new_svn_tag = v.ValidSvnPattern(
425 new_svn_tag = v.ValidSvnPattern(
425 section='vcs_svn_tag', repo_name=repo_name)
426 section='vcs_svn_tag', repo_name=repo_name)
426
427
427 return _RepoVcsSettingsForm
428 return _RepoVcsSettingsForm
428
429
429
430
430 def LabsSettingsForm():
431 def LabsSettingsForm():
431 class _LabSettingsForm(formencode.Schema):
432 class _LabSettingsForm(formencode.Schema):
432 allow_extra_fields = True
433 allow_extra_fields = True
433 filter_extra_fields = False
434 filter_extra_fields = False
434
435
435 return _LabSettingsForm
436 return _LabSettingsForm
436
437
437
438
438 def ApplicationPermissionsForm(
439 def ApplicationPermissionsForm(
439 register_choices, password_reset_choices, extern_activate_choices):
440 register_choices, password_reset_choices, extern_activate_choices):
440 class _DefaultPermissionsForm(formencode.Schema):
441 class _DefaultPermissionsForm(formencode.Schema):
441 allow_extra_fields = True
442 allow_extra_fields = True
442 filter_extra_fields = True
443 filter_extra_fields = True
443
444
444 anonymous = v.StringBoolean(if_missing=False)
445 anonymous = v.StringBoolean(if_missing=False)
445 default_register = v.OneOf(register_choices)
446 default_register = v.OneOf(register_choices)
446 default_register_message = v.UnicodeString()
447 default_register_message = v.UnicodeString()
447 default_password_reset = v.OneOf(password_reset_choices)
448 default_password_reset = v.OneOf(password_reset_choices)
448 default_extern_activate = v.OneOf(extern_activate_choices)
449 default_extern_activate = v.OneOf(extern_activate_choices)
449
450
450 return _DefaultPermissionsForm
451 return _DefaultPermissionsForm
451
452
452
453
453 def ObjectPermissionsForm(repo_perms_choices, group_perms_choices,
454 def ObjectPermissionsForm(repo_perms_choices, group_perms_choices,
454 user_group_perms_choices):
455 user_group_perms_choices):
455 class _ObjectPermissionsForm(formencode.Schema):
456 class _ObjectPermissionsForm(formencode.Schema):
456 allow_extra_fields = True
457 allow_extra_fields = True
457 filter_extra_fields = True
458 filter_extra_fields = True
458 overwrite_default_repo = v.StringBoolean(if_missing=False)
459 overwrite_default_repo = v.StringBoolean(if_missing=False)
459 overwrite_default_group = v.StringBoolean(if_missing=False)
460 overwrite_default_group = v.StringBoolean(if_missing=False)
460 overwrite_default_user_group = v.StringBoolean(if_missing=False)
461 overwrite_default_user_group = v.StringBoolean(if_missing=False)
461 default_repo_perm = v.OneOf(repo_perms_choices)
462 default_repo_perm = v.OneOf(repo_perms_choices)
462 default_group_perm = v.OneOf(group_perms_choices)
463 default_group_perm = v.OneOf(group_perms_choices)
463 default_user_group_perm = v.OneOf(user_group_perms_choices)
464 default_user_group_perm = v.OneOf(user_group_perms_choices)
464
465
465 return _ObjectPermissionsForm
466 return _ObjectPermissionsForm
466
467
467
468
468 def UserPermissionsForm(create_choices, create_on_write_choices,
469 def UserPermissionsForm(create_choices, create_on_write_choices,
469 repo_group_create_choices, user_group_create_choices,
470 repo_group_create_choices, user_group_create_choices,
470 fork_choices, inherit_default_permissions_choices):
471 fork_choices, inherit_default_permissions_choices):
471 class _DefaultPermissionsForm(formencode.Schema):
472 class _DefaultPermissionsForm(formencode.Schema):
472 allow_extra_fields = True
473 allow_extra_fields = True
473 filter_extra_fields = True
474 filter_extra_fields = True
474
475
475 anonymous = v.StringBoolean(if_missing=False)
476 anonymous = v.StringBoolean(if_missing=False)
476
477
477 default_repo_create = v.OneOf(create_choices)
478 default_repo_create = v.OneOf(create_choices)
478 default_repo_create_on_write = v.OneOf(create_on_write_choices)
479 default_repo_create_on_write = v.OneOf(create_on_write_choices)
479 default_user_group_create = v.OneOf(user_group_create_choices)
480 default_user_group_create = v.OneOf(user_group_create_choices)
480 default_repo_group_create = v.OneOf(repo_group_create_choices)
481 default_repo_group_create = v.OneOf(repo_group_create_choices)
481 default_fork_create = v.OneOf(fork_choices)
482 default_fork_create = v.OneOf(fork_choices)
482 default_inherit_default_permissions = v.OneOf(inherit_default_permissions_choices)
483 default_inherit_default_permissions = v.OneOf(inherit_default_permissions_choices)
483
484
484 return _DefaultPermissionsForm
485 return _DefaultPermissionsForm
485
486
486
487
487 def UserIndividualPermissionsForm():
488 def UserIndividualPermissionsForm():
488 class _DefaultPermissionsForm(formencode.Schema):
489 class _DefaultPermissionsForm(formencode.Schema):
489 allow_extra_fields = True
490 allow_extra_fields = True
490 filter_extra_fields = True
491 filter_extra_fields = True
491
492
492 inherit_default_permissions = v.StringBoolean(if_missing=False)
493 inherit_default_permissions = v.StringBoolean(if_missing=False)
493
494
494 return _DefaultPermissionsForm
495 return _DefaultPermissionsForm
495
496
496
497
497 def DefaultsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
498 def DefaultsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
498 class _DefaultsForm(formencode.Schema):
499 class _DefaultsForm(formencode.Schema):
499 allow_extra_fields = True
500 allow_extra_fields = True
500 filter_extra_fields = True
501 filter_extra_fields = True
501 default_repo_type = v.OneOf(supported_backends)
502 default_repo_type = v.OneOf(supported_backends)
502 default_repo_private = v.StringBoolean(if_missing=False)
503 default_repo_private = v.StringBoolean(if_missing=False)
503 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
504 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
504 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
505 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
505 default_repo_enable_locking = v.StringBoolean(if_missing=False)
506 default_repo_enable_locking = v.StringBoolean(if_missing=False)
506
507
507 return _DefaultsForm
508 return _DefaultsForm
508
509
509
510
510 def AuthSettingsForm():
511 def AuthSettingsForm():
511 class _AuthSettingsForm(formencode.Schema):
512 class _AuthSettingsForm(formencode.Schema):
512 allow_extra_fields = True
513 allow_extra_fields = True
513 filter_extra_fields = True
514 filter_extra_fields = True
514 auth_plugins = All(v.ValidAuthPlugins(),
515 auth_plugins = All(v.ValidAuthPlugins(),
515 v.UniqueListFromString()(not_empty=True))
516 v.UniqueListFromString()(not_empty=True))
516
517
517 return _AuthSettingsForm
518 return _AuthSettingsForm
518
519
519
520
520 def UserExtraEmailForm():
521 def UserExtraEmailForm():
521 class _UserExtraEmailForm(formencode.Schema):
522 class _UserExtraEmailForm(formencode.Schema):
522 email = All(v.UniqSystemEmail(), v.Email(not_empty=True))
523 email = All(v.UniqSystemEmail(), v.Email(not_empty=True))
523 return _UserExtraEmailForm
524 return _UserExtraEmailForm
524
525
525
526
526 def UserExtraIpForm():
527 def UserExtraIpForm():
527 class _UserExtraIpForm(formencode.Schema):
528 class _UserExtraIpForm(formencode.Schema):
528 ip = v.ValidIp()(not_empty=True)
529 ip = v.ValidIp()(not_empty=True)
529 return _UserExtraIpForm
530 return _UserExtraIpForm
530
531
531
532
532
533
533 def PullRequestForm(repo_id):
534 def PullRequestForm(repo_id):
534 class ReviewerForm(formencode.Schema):
535 class ReviewerForm(formencode.Schema):
535 user_id = v.Int(not_empty=True)
536 user_id = v.Int(not_empty=True)
536 reasons = All()
537 reasons = All()
537
538
538 class _PullRequestForm(formencode.Schema):
539 class _PullRequestForm(formencode.Schema):
539 allow_extra_fields = True
540 allow_extra_fields = True
540 filter_extra_fields = True
541 filter_extra_fields = True
541
542
542 user = v.UnicodeString(strip=True, required=True)
543 user = v.UnicodeString(strip=True, required=True)
543 source_repo = v.UnicodeString(strip=True, required=True)
544 source_repo = v.UnicodeString(strip=True, required=True)
544 source_ref = v.UnicodeString(strip=True, required=True)
545 source_ref = v.UnicodeString(strip=True, required=True)
545 target_repo = v.UnicodeString(strip=True, required=True)
546 target_repo = v.UnicodeString(strip=True, required=True)
546 target_ref = v.UnicodeString(strip=True, required=True)
547 target_ref = v.UnicodeString(strip=True, required=True)
547 revisions = All(#v.NotReviewedRevisions(repo_id)(),
548 revisions = All(#v.NotReviewedRevisions(repo_id)(),
548 v.UniqueList()(not_empty=True))
549 v.UniqueList()(not_empty=True))
549 review_members = formencode.ForEach(ReviewerForm())
550 review_members = formencode.ForEach(ReviewerForm())
550 pullrequest_title = v.UnicodeString(strip=True, required=True)
551 pullrequest_title = v.UnicodeString(strip=True, required=True)
551 pullrequest_desc = v.UnicodeString(strip=True, required=False)
552 pullrequest_desc = v.UnicodeString(strip=True, required=False)
552
553
553 return _PullRequestForm
554 return _PullRequestForm
554
555
555
556
556 def IssueTrackerPatternsForm():
557 def IssueTrackerPatternsForm():
557 class _IssueTrackerPatternsForm(formencode.Schema):
558 class _IssueTrackerPatternsForm(formencode.Schema):
558 allow_extra_fields = True
559 allow_extra_fields = True
559 filter_extra_fields = False
560 filter_extra_fields = False
560 chained_validators = [v.ValidPattern()]
561 chained_validators = [v.ValidPattern()]
561 return _IssueTrackerPatternsForm
562 return _IssueTrackerPatternsForm
@@ -1,802 +1,810 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22 import hashlib
22 import hashlib
23 import logging
23 import logging
24 from collections import namedtuple
24 from collections import namedtuple
25 from functools import wraps
25 from functools import wraps
26
26
27 from rhodecode.lib import caches
27 from rhodecode.lib import caches
28 from rhodecode.lib.utils2 import (
28 from rhodecode.lib.utils2 import (
29 Optional, AttributeDict, safe_str, remove_prefix, str2bool)
29 Optional, AttributeDict, safe_str, remove_prefix, str2bool)
30 from rhodecode.lib.vcs.backends import base
30 from rhodecode.lib.vcs.backends import base
31 from rhodecode.model import BaseModel
31 from rhodecode.model import BaseModel
32 from rhodecode.model.db import (
32 from rhodecode.model.db import (
33 RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi, RhodeCodeSetting)
33 RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi, RhodeCodeSetting)
34 from rhodecode.model.meta import Session
34 from rhodecode.model.meta import Session
35
35
36
36
37 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
38
38
39
39
40 UiSetting = namedtuple(
40 UiSetting = namedtuple(
41 'UiSetting', ['section', 'key', 'value', 'active'])
41 'UiSetting', ['section', 'key', 'value', 'active'])
42
42
43 SOCIAL_PLUGINS_LIST = ['github', 'bitbucket', 'twitter', 'google']
43 SOCIAL_PLUGINS_LIST = ['github', 'bitbucket', 'twitter', 'google']
44
44
45
45
46 class SettingNotFound(Exception):
46 class SettingNotFound(Exception):
47 def __init__(self):
47 def __init__(self):
48 super(SettingNotFound, self).__init__('Setting is not found')
48 super(SettingNotFound, self).__init__('Setting is not found')
49
49
50
50
51 class SettingsModel(BaseModel):
51 class SettingsModel(BaseModel):
52 BUILTIN_HOOKS = (
52 BUILTIN_HOOKS = (
53 RhodeCodeUi.HOOK_REPO_SIZE, RhodeCodeUi.HOOK_PUSH,
53 RhodeCodeUi.HOOK_REPO_SIZE, RhodeCodeUi.HOOK_PUSH,
54 RhodeCodeUi.HOOK_PRE_PUSH, RhodeCodeUi.HOOK_PRETX_PUSH,
54 RhodeCodeUi.HOOK_PRE_PUSH, RhodeCodeUi.HOOK_PRETX_PUSH,
55 RhodeCodeUi.HOOK_PULL, RhodeCodeUi.HOOK_PRE_PULL)
55 RhodeCodeUi.HOOK_PULL, RhodeCodeUi.HOOK_PRE_PULL)
56 HOOKS_SECTION = 'hooks'
56 HOOKS_SECTION = 'hooks'
57
57
58 def __init__(self, sa=None, repo=None):
58 def __init__(self, sa=None, repo=None):
59 self.repo = repo
59 self.repo = repo
60 self.UiDbModel = RepoRhodeCodeUi if repo else RhodeCodeUi
60 self.UiDbModel = RepoRhodeCodeUi if repo else RhodeCodeUi
61 self.SettingsDbModel = (
61 self.SettingsDbModel = (
62 RepoRhodeCodeSetting if repo else RhodeCodeSetting)
62 RepoRhodeCodeSetting if repo else RhodeCodeSetting)
63 super(SettingsModel, self).__init__(sa)
63 super(SettingsModel, self).__init__(sa)
64
64
65 def get_ui_by_key(self, key):
65 def get_ui_by_key(self, key):
66 q = self.UiDbModel.query()
66 q = self.UiDbModel.query()
67 q = q.filter(self.UiDbModel.ui_key == key)
67 q = q.filter(self.UiDbModel.ui_key == key)
68 q = self._filter_by_repo(RepoRhodeCodeUi, q)
68 q = self._filter_by_repo(RepoRhodeCodeUi, q)
69 return q.scalar()
69 return q.scalar()
70
70
71 def get_ui_by_section(self, section):
71 def get_ui_by_section(self, section):
72 q = self.UiDbModel.query()
72 q = self.UiDbModel.query()
73 q = q.filter(self.UiDbModel.ui_section == section)
73 q = q.filter(self.UiDbModel.ui_section == section)
74 q = self._filter_by_repo(RepoRhodeCodeUi, q)
74 q = self._filter_by_repo(RepoRhodeCodeUi, q)
75 return q.all()
75 return q.all()
76
76
77 def get_ui_by_section_and_key(self, section, key):
77 def get_ui_by_section_and_key(self, section, key):
78 q = self.UiDbModel.query()
78 q = self.UiDbModel.query()
79 q = q.filter(self.UiDbModel.ui_section == section)
79 q = q.filter(self.UiDbModel.ui_section == section)
80 q = q.filter(self.UiDbModel.ui_key == key)
80 q = q.filter(self.UiDbModel.ui_key == key)
81 q = self._filter_by_repo(RepoRhodeCodeUi, q)
81 q = self._filter_by_repo(RepoRhodeCodeUi, q)
82 return q.scalar()
82 return q.scalar()
83
83
84 def get_ui(self, section=None, key=None):
84 def get_ui(self, section=None, key=None):
85 q = self.UiDbModel.query()
85 q = self.UiDbModel.query()
86 q = self._filter_by_repo(RepoRhodeCodeUi, q)
86 q = self._filter_by_repo(RepoRhodeCodeUi, q)
87
87
88 if section:
88 if section:
89 q = q.filter(self.UiDbModel.ui_section == section)
89 q = q.filter(self.UiDbModel.ui_section == section)
90 if key:
90 if key:
91 q = q.filter(self.UiDbModel.ui_key == key)
91 q = q.filter(self.UiDbModel.ui_key == key)
92
92
93 # TODO: mikhail: add caching
93 # TODO: mikhail: add caching
94 result = [
94 result = [
95 UiSetting(
95 UiSetting(
96 section=safe_str(r.ui_section), key=safe_str(r.ui_key),
96 section=safe_str(r.ui_section), key=safe_str(r.ui_key),
97 value=safe_str(r.ui_value), active=r.ui_active
97 value=safe_str(r.ui_value), active=r.ui_active
98 )
98 )
99 for r in q.all()
99 for r in q.all()
100 ]
100 ]
101 return result
101 return result
102
102
103 def get_builtin_hooks(self):
103 def get_builtin_hooks(self):
104 q = self.UiDbModel.query()
104 q = self.UiDbModel.query()
105 q = q.filter(self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
105 q = q.filter(self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
106 return self._get_hooks(q)
106 return self._get_hooks(q)
107
107
108 def get_custom_hooks(self):
108 def get_custom_hooks(self):
109 q = self.UiDbModel.query()
109 q = self.UiDbModel.query()
110 q = q.filter(~self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
110 q = q.filter(~self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
111 return self._get_hooks(q)
111 return self._get_hooks(q)
112
112
113 def create_ui_section_value(self, section, val, key=None, active=True):
113 def create_ui_section_value(self, section, val, key=None, active=True):
114 new_ui = self.UiDbModel()
114 new_ui = self.UiDbModel()
115 new_ui.ui_section = section
115 new_ui.ui_section = section
116 new_ui.ui_value = val
116 new_ui.ui_value = val
117 new_ui.ui_active = active
117 new_ui.ui_active = active
118
118
119 if self.repo:
119 if self.repo:
120 repo = self._get_repo(self.repo)
120 repo = self._get_repo(self.repo)
121 repository_id = repo.repo_id
121 repository_id = repo.repo_id
122 new_ui.repository_id = repository_id
122 new_ui.repository_id = repository_id
123
123
124 if not key:
124 if not key:
125 # keys are unique so they need appended info
125 # keys are unique so they need appended info
126 if self.repo:
126 if self.repo:
127 key = hashlib.sha1(
127 key = hashlib.sha1(
128 '{}{}{}'.format(section, val, repository_id)).hexdigest()
128 '{}{}{}'.format(section, val, repository_id)).hexdigest()
129 else:
129 else:
130 key = hashlib.sha1('{}{}'.format(section, val)).hexdigest()
130 key = hashlib.sha1('{}{}'.format(section, val)).hexdigest()
131
131
132 new_ui.ui_key = key
132 new_ui.ui_key = key
133
133
134 Session().add(new_ui)
134 Session().add(new_ui)
135 return new_ui
135 return new_ui
136
136
137 def create_or_update_hook(self, key, value):
137 def create_or_update_hook(self, key, value):
138 ui = (
138 ui = (
139 self.get_ui_by_section_and_key(self.HOOKS_SECTION, key) or
139 self.get_ui_by_section_and_key(self.HOOKS_SECTION, key) or
140 self.UiDbModel())
140 self.UiDbModel())
141 ui.ui_section = self.HOOKS_SECTION
141 ui.ui_section = self.HOOKS_SECTION
142 ui.ui_active = True
142 ui.ui_active = True
143 ui.ui_key = key
143 ui.ui_key = key
144 ui.ui_value = value
144 ui.ui_value = value
145
145
146 if self.repo:
146 if self.repo:
147 repo = self._get_repo(self.repo)
147 repo = self._get_repo(self.repo)
148 repository_id = repo.repo_id
148 repository_id = repo.repo_id
149 ui.repository_id = repository_id
149 ui.repository_id = repository_id
150
150
151 Session().add(ui)
151 Session().add(ui)
152 return ui
152 return ui
153
153
154 def delete_ui(self, id_):
154 def delete_ui(self, id_):
155 ui = self.UiDbModel.get(id_)
155 ui = self.UiDbModel.get(id_)
156 if not ui:
156 if not ui:
157 raise SettingNotFound()
157 raise SettingNotFound()
158 Session().delete(ui)
158 Session().delete(ui)
159
159
160 def get_setting_by_name(self, name):
160 def get_setting_by_name(self, name):
161 q = self._get_settings_query()
161 q = self._get_settings_query()
162 q = q.filter(self.SettingsDbModel.app_settings_name == name)
162 q = q.filter(self.SettingsDbModel.app_settings_name == name)
163 return q.scalar()
163 return q.scalar()
164
164
165 def create_or_update_setting(
165 def create_or_update_setting(
166 self, name, val=Optional(''), type_=Optional('unicode')):
166 self, name, val=Optional(''), type_=Optional('unicode')):
167 """
167 """
168 Creates or updates RhodeCode setting. If updates is triggered it will
168 Creates or updates RhodeCode setting. If updates is triggered it will
169 only update parameters that are explicityl set Optional instance will
169 only update parameters that are explicityl set Optional instance will
170 be skipped
170 be skipped
171
171
172 :param name:
172 :param name:
173 :param val:
173 :param val:
174 :param type_:
174 :param type_:
175 :return:
175 :return:
176 """
176 """
177
177
178 res = self.get_setting_by_name(name)
178 res = self.get_setting_by_name(name)
179 repo = self._get_repo(self.repo) if self.repo else None
179 repo = self._get_repo(self.repo) if self.repo else None
180
180
181 if not res:
181 if not res:
182 val = Optional.extract(val)
182 val = Optional.extract(val)
183 type_ = Optional.extract(type_)
183 type_ = Optional.extract(type_)
184
184
185 args = (
185 args = (
186 (repo.repo_id, name, val, type_)
186 (repo.repo_id, name, val, type_)
187 if repo else (name, val, type_))
187 if repo else (name, val, type_))
188 res = self.SettingsDbModel(*args)
188 res = self.SettingsDbModel(*args)
189
189
190 else:
190 else:
191 if self.repo:
191 if self.repo:
192 res.repository_id = repo.repo_id
192 res.repository_id = repo.repo_id
193
193
194 res.app_settings_name = name
194 res.app_settings_name = name
195 if not isinstance(type_, Optional):
195 if not isinstance(type_, Optional):
196 # update if set
196 # update if set
197 res.app_settings_type = type_
197 res.app_settings_type = type_
198 if not isinstance(val, Optional):
198 if not isinstance(val, Optional):
199 # update if set
199 # update if set
200 res.app_settings_value = val
200 res.app_settings_value = val
201
201
202 Session().add(res)
202 Session().add(res)
203 return res
203 return res
204
204
205 def invalidate_settings_cache(self):
205 def invalidate_settings_cache(self):
206 namespace = 'rhodecode_settings'
206 namespace = 'rhodecode_settings'
207 cache_manager = caches.get_cache_manager('sql_cache_short', namespace)
207 cache_manager = caches.get_cache_manager('sql_cache_short', namespace)
208 caches.clear_cache_manager(cache_manager)
208 caches.clear_cache_manager(cache_manager)
209
209
210 def get_all_settings(self, cache=False):
210 def get_all_settings(self, cache=False):
211
211
212 def _compute():
212 def _compute():
213 q = self._get_settings_query()
213 q = self._get_settings_query()
214 if not q:
214 if not q:
215 raise Exception('Could not get application settings !')
215 raise Exception('Could not get application settings !')
216
216
217 settings = {
217 settings = {
218 'rhodecode_' + result.app_settings_name: result.app_settings_value
218 'rhodecode_' + result.app_settings_name: result.app_settings_value
219 for result in q
219 for result in q
220 }
220 }
221 return settings
221 return settings
222
222
223 if cache:
223 if cache:
224 log.debug('Fetching app settings using cache')
224 log.debug('Fetching app settings using cache')
225 repo = self._get_repo(self.repo) if self.repo else None
225 repo = self._get_repo(self.repo) if self.repo else None
226 namespace = 'rhodecode_settings'
226 namespace = 'rhodecode_settings'
227 cache_manager = caches.get_cache_manager(
227 cache_manager = caches.get_cache_manager(
228 'sql_cache_short', namespace)
228 'sql_cache_short', namespace)
229 _cache_key = (
229 _cache_key = (
230 "get_repo_{}_settings".format(repo.repo_id)
230 "get_repo_{}_settings".format(repo.repo_id)
231 if repo else "get_app_settings")
231 if repo else "get_app_settings")
232
232
233 return cache_manager.get(_cache_key, createfunc=_compute)
233 return cache_manager.get(_cache_key, createfunc=_compute)
234
234
235 else:
235 else:
236 return _compute()
236 return _compute()
237
237
238 def get_auth_settings(self):
238 def get_auth_settings(self):
239 q = self._get_settings_query()
239 q = self._get_settings_query()
240 q = q.filter(
240 q = q.filter(
241 self.SettingsDbModel.app_settings_name.startswith('auth_'))
241 self.SettingsDbModel.app_settings_name.startswith('auth_'))
242 rows = q.all()
242 rows = q.all()
243 auth_settings = {
243 auth_settings = {
244 row.app_settings_name: row.app_settings_value for row in rows}
244 row.app_settings_name: row.app_settings_value for row in rows}
245 return auth_settings
245 return auth_settings
246
246
247 def get_auth_plugins(self):
247 def get_auth_plugins(self):
248 auth_plugins = self.get_setting_by_name("auth_plugins")
248 auth_plugins = self.get_setting_by_name("auth_plugins")
249 return auth_plugins.app_settings_value
249 return auth_plugins.app_settings_value
250
250
251 def get_default_repo_settings(self, strip_prefix=False):
251 def get_default_repo_settings(self, strip_prefix=False):
252 q = self._get_settings_query()
252 q = self._get_settings_query()
253 q = q.filter(
253 q = q.filter(
254 self.SettingsDbModel.app_settings_name.startswith('default_'))
254 self.SettingsDbModel.app_settings_name.startswith('default_'))
255 rows = q.all()
255 rows = q.all()
256
256
257 result = {}
257 result = {}
258 for row in rows:
258 for row in rows:
259 key = row.app_settings_name
259 key = row.app_settings_name
260 if strip_prefix:
260 if strip_prefix:
261 key = remove_prefix(key, prefix='default_')
261 key = remove_prefix(key, prefix='default_')
262 result.update({key: row.app_settings_value})
262 result.update({key: row.app_settings_value})
263 return result
263 return result
264
264
265 def get_repo(self):
265 def get_repo(self):
266 repo = self._get_repo(self.repo)
266 repo = self._get_repo(self.repo)
267 if not repo:
267 if not repo:
268 raise Exception(
268 raise Exception(
269 'Repository `{}` cannot be found inside the database'.format(
269 'Repository `{}` cannot be found inside the database'.format(
270 self.repo))
270 self.repo))
271 return repo
271 return repo
272
272
273 def _filter_by_repo(self, model, query):
273 def _filter_by_repo(self, model, query):
274 if self.repo:
274 if self.repo:
275 repo = self.get_repo()
275 repo = self.get_repo()
276 query = query.filter(model.repository_id == repo.repo_id)
276 query = query.filter(model.repository_id == repo.repo_id)
277 return query
277 return query
278
278
279 def _get_hooks(self, query):
279 def _get_hooks(self, query):
280 query = query.filter(self.UiDbModel.ui_section == self.HOOKS_SECTION)
280 query = query.filter(self.UiDbModel.ui_section == self.HOOKS_SECTION)
281 query = self._filter_by_repo(RepoRhodeCodeUi, query)
281 query = self._filter_by_repo(RepoRhodeCodeUi, query)
282 return query.all()
282 return query.all()
283
283
284 def _get_settings_query(self):
284 def _get_settings_query(self):
285 q = self.SettingsDbModel.query()
285 q = self.SettingsDbModel.query()
286 return self._filter_by_repo(RepoRhodeCodeSetting, q)
286 return self._filter_by_repo(RepoRhodeCodeSetting, q)
287
287
288 def list_enabled_social_plugins(self, settings):
288 def list_enabled_social_plugins(self, settings):
289 enabled = []
289 enabled = []
290 for plug in SOCIAL_PLUGINS_LIST:
290 for plug in SOCIAL_PLUGINS_LIST:
291 if str2bool(settings.get('rhodecode_auth_{}_enabled'.format(plug)
291 if str2bool(settings.get('rhodecode_auth_{}_enabled'.format(plug)
292 )):
292 )):
293 enabled.append(plug)
293 enabled.append(plug)
294 return enabled
294 return enabled
295
295
296
296
297 def assert_repo_settings(func):
297 def assert_repo_settings(func):
298 @wraps(func)
298 @wraps(func)
299 def _wrapper(self, *args, **kwargs):
299 def _wrapper(self, *args, **kwargs):
300 if not self.repo_settings:
300 if not self.repo_settings:
301 raise Exception('Repository is not specified')
301 raise Exception('Repository is not specified')
302 return func(self, *args, **kwargs)
302 return func(self, *args, **kwargs)
303 return _wrapper
303 return _wrapper
304
304
305
305
306 class IssueTrackerSettingsModel(object):
306 class IssueTrackerSettingsModel(object):
307 INHERIT_SETTINGS = 'inherit_issue_tracker_settings'
307 INHERIT_SETTINGS = 'inherit_issue_tracker_settings'
308 SETTINGS_PREFIX = 'issuetracker_'
308 SETTINGS_PREFIX = 'issuetracker_'
309
309
310 def __init__(self, sa=None, repo=None):
310 def __init__(self, sa=None, repo=None):
311 self.global_settings = SettingsModel(sa=sa)
311 self.global_settings = SettingsModel(sa=sa)
312 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
312 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
313
313
314 @property
314 @property
315 def inherit_global_settings(self):
315 def inherit_global_settings(self):
316 if not self.repo_settings:
316 if not self.repo_settings:
317 return True
317 return True
318 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
318 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
319 return setting.app_settings_value if setting else True
319 return setting.app_settings_value if setting else True
320
320
321 @inherit_global_settings.setter
321 @inherit_global_settings.setter
322 def inherit_global_settings(self, value):
322 def inherit_global_settings(self, value):
323 if self.repo_settings:
323 if self.repo_settings:
324 settings = self.repo_settings.create_or_update_setting(
324 settings = self.repo_settings.create_or_update_setting(
325 self.INHERIT_SETTINGS, value, type_='bool')
325 self.INHERIT_SETTINGS, value, type_='bool')
326 Session().add(settings)
326 Session().add(settings)
327
327
328 def _get_keyname(self, key, uid, prefix=''):
328 def _get_keyname(self, key, uid, prefix=''):
329 return '{0}{1}{2}_{3}'.format(
329 return '{0}{1}{2}_{3}'.format(
330 prefix, self.SETTINGS_PREFIX, key, uid)
330 prefix, self.SETTINGS_PREFIX, key, uid)
331
331
332 def _make_dict_for_settings(self, qs):
332 def _make_dict_for_settings(self, qs):
333 prefix_match = self._get_keyname('pat', '', 'rhodecode_')
333 prefix_match = self._get_keyname('pat', '', 'rhodecode_')
334
334
335 issuetracker_entries = {}
335 issuetracker_entries = {}
336 # create keys
336 # create keys
337 for k, v in qs.items():
337 for k, v in qs.items():
338 if k.startswith(prefix_match):
338 if k.startswith(prefix_match):
339 uid = k[len(prefix_match):]
339 uid = k[len(prefix_match):]
340 issuetracker_entries[uid] = None
340 issuetracker_entries[uid] = None
341
341
342 # populate
342 # populate
343 for uid in issuetracker_entries:
343 for uid in issuetracker_entries:
344 issuetracker_entries[uid] = AttributeDict({
344 issuetracker_entries[uid] = AttributeDict({
345 'pat': qs.get(self._get_keyname('pat', uid, 'rhodecode_')),
345 'pat': qs.get(self._get_keyname('pat', uid, 'rhodecode_')),
346 'url': qs.get(self._get_keyname('url', uid, 'rhodecode_')),
346 'url': qs.get(self._get_keyname('url', uid, 'rhodecode_')),
347 'pref': qs.get(self._get_keyname('pref', uid, 'rhodecode_')),
347 'pref': qs.get(self._get_keyname('pref', uid, 'rhodecode_')),
348 'desc': qs.get(self._get_keyname('desc', uid, 'rhodecode_')),
348 'desc': qs.get(self._get_keyname('desc', uid, 'rhodecode_')),
349 })
349 })
350 return issuetracker_entries
350 return issuetracker_entries
351
351
352 def get_global_settings(self, cache=False):
352 def get_global_settings(self, cache=False):
353 """
353 """
354 Returns list of global issue tracker settings
354 Returns list of global issue tracker settings
355 """
355 """
356 defaults = self.global_settings.get_all_settings(cache=cache)
356 defaults = self.global_settings.get_all_settings(cache=cache)
357 settings = self._make_dict_for_settings(defaults)
357 settings = self._make_dict_for_settings(defaults)
358 return settings
358 return settings
359
359
360 def get_repo_settings(self, cache=False):
360 def get_repo_settings(self, cache=False):
361 """
361 """
362 Returns list of issue tracker settings per repository
362 Returns list of issue tracker settings per repository
363 """
363 """
364 if not self.repo_settings:
364 if not self.repo_settings:
365 raise Exception('Repository is not specified')
365 raise Exception('Repository is not specified')
366 all_settings = self.repo_settings.get_all_settings(cache=cache)
366 all_settings = self.repo_settings.get_all_settings(cache=cache)
367 settings = self._make_dict_for_settings(all_settings)
367 settings = self._make_dict_for_settings(all_settings)
368 return settings
368 return settings
369
369
370 def get_settings(self, cache=False):
370 def get_settings(self, cache=False):
371 if self.inherit_global_settings:
371 if self.inherit_global_settings:
372 return self.get_global_settings(cache=cache)
372 return self.get_global_settings(cache=cache)
373 else:
373 else:
374 return self.get_repo_settings(cache=cache)
374 return self.get_repo_settings(cache=cache)
375
375
376 def delete_entries(self, uid):
376 def delete_entries(self, uid):
377 if self.repo_settings:
377 if self.repo_settings:
378 all_patterns = self.get_repo_settings()
378 all_patterns = self.get_repo_settings()
379 settings_model = self.repo_settings
379 settings_model = self.repo_settings
380 else:
380 else:
381 all_patterns = self.get_global_settings()
381 all_patterns = self.get_global_settings()
382 settings_model = self.global_settings
382 settings_model = self.global_settings
383 entries = all_patterns.get(uid)
383 entries = all_patterns.get(uid)
384
384
385 for del_key in entries:
385 for del_key in entries:
386 setting_name = self._get_keyname(del_key, uid)
386 setting_name = self._get_keyname(del_key, uid)
387 entry = settings_model.get_setting_by_name(setting_name)
387 entry = settings_model.get_setting_by_name(setting_name)
388 if entry:
388 if entry:
389 Session().delete(entry)
389 Session().delete(entry)
390
390
391 Session().commit()
391 Session().commit()
392
392
393 def create_or_update_setting(
393 def create_or_update_setting(
394 self, name, val=Optional(''), type_=Optional('unicode')):
394 self, name, val=Optional(''), type_=Optional('unicode')):
395 if self.repo_settings:
395 if self.repo_settings:
396 setting = self.repo_settings.create_or_update_setting(
396 setting = self.repo_settings.create_or_update_setting(
397 name, val, type_)
397 name, val, type_)
398 else:
398 else:
399 setting = self.global_settings.create_or_update_setting(
399 setting = self.global_settings.create_or_update_setting(
400 name, val, type_)
400 name, val, type_)
401 return setting
401 return setting
402
402
403
403
404 class VcsSettingsModel(object):
404 class VcsSettingsModel(object):
405
405
406 INHERIT_SETTINGS = 'inherit_vcs_settings'
406 INHERIT_SETTINGS = 'inherit_vcs_settings'
407 GENERAL_SETTINGS = (
407 GENERAL_SETTINGS = (
408 'use_outdated_comments',
408 'use_outdated_comments',
409 'pr_merge_enabled',
409 'pr_merge_enabled',
410 'hg_use_rebase_for_merging')
410 'hg_use_rebase_for_merging')
411
411
412 HOOKS_SETTINGS = (
412 HOOKS_SETTINGS = (
413 ('hooks', 'changegroup.repo_size'),
413 ('hooks', 'changegroup.repo_size'),
414 ('hooks', 'changegroup.push_logger'),
414 ('hooks', 'changegroup.push_logger'),
415 ('hooks', 'outgoing.pull_logger'),)
415 ('hooks', 'outgoing.pull_logger'),)
416 HG_SETTINGS = (
416 HG_SETTINGS = (
417 ('extensions', 'largefiles'),
417 ('extensions', 'largefiles'),
418 ('phases', 'publish'),)
418 ('phases', 'publish'),
419 ('extensions', 'evolve'),)
419 GIT_SETTINGS = (
420 GIT_SETTINGS = (
420 ('vcs_git_lfs', 'enabled'),)
421 ('vcs_git_lfs', 'enabled'),)
421
422 GLOBAL_HG_SETTINGS = (
422 GLOBAL_HG_SETTINGS = (
423 ('extensions', 'largefiles'),
423 ('extensions', 'largefiles'),
424 ('largefiles', 'usercache'),
424 ('largefiles', 'usercache'),
425 ('phases', 'publish'),
425 ('phases', 'publish'),
426 ('extensions', 'hgsubversion'))
426 ('extensions', 'hgsubversion'),
427 ('extensions', 'evolve'),)
427 GLOBAL_GIT_SETTINGS = (
428 GLOBAL_GIT_SETTINGS = (
428 ('vcs_git_lfs', 'enabled'),
429 ('vcs_git_lfs', 'enabled'),
429 ('vcs_git_lfs', 'store_location'))
430 ('vcs_git_lfs', 'store_location'))
430 GLOBAL_SVN_SETTINGS = (
431 GLOBAL_SVN_SETTINGS = (
431 ('vcs_svn_proxy', 'http_requests_enabled'),
432 ('vcs_svn_proxy', 'http_requests_enabled'),
432 ('vcs_svn_proxy', 'http_server_url'))
433 ('vcs_svn_proxy', 'http_server_url'))
433
434
434 SVN_BRANCH_SECTION = 'vcs_svn_branch'
435 SVN_BRANCH_SECTION = 'vcs_svn_branch'
435 SVN_TAG_SECTION = 'vcs_svn_tag'
436 SVN_TAG_SECTION = 'vcs_svn_tag'
436 SSL_SETTING = ('web', 'push_ssl')
437 SSL_SETTING = ('web', 'push_ssl')
437 PATH_SETTING = ('paths', '/')
438 PATH_SETTING = ('paths', '/')
438
439
439 def __init__(self, sa=None, repo=None):
440 def __init__(self, sa=None, repo=None):
440 self.global_settings = SettingsModel(sa=sa)
441 self.global_settings = SettingsModel(sa=sa)
441 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
442 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
442 self._ui_settings = (
443 self._ui_settings = (
443 self.HG_SETTINGS + self.GIT_SETTINGS + self.HOOKS_SETTINGS)
444 self.HG_SETTINGS + self.GIT_SETTINGS + self.HOOKS_SETTINGS)
444 self._svn_sections = (self.SVN_BRANCH_SECTION, self.SVN_TAG_SECTION)
445 self._svn_sections = (self.SVN_BRANCH_SECTION, self.SVN_TAG_SECTION)
445
446
446 @property
447 @property
447 @assert_repo_settings
448 @assert_repo_settings
448 def inherit_global_settings(self):
449 def inherit_global_settings(self):
449 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
450 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
450 return setting.app_settings_value if setting else True
451 return setting.app_settings_value if setting else True
451
452
452 @inherit_global_settings.setter
453 @inherit_global_settings.setter
453 @assert_repo_settings
454 @assert_repo_settings
454 def inherit_global_settings(self, value):
455 def inherit_global_settings(self, value):
455 self.repo_settings.create_or_update_setting(
456 self.repo_settings.create_or_update_setting(
456 self.INHERIT_SETTINGS, value, type_='bool')
457 self.INHERIT_SETTINGS, value, type_='bool')
457
458
458 def get_global_svn_branch_patterns(self):
459 def get_global_svn_branch_patterns(self):
459 return self.global_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
460 return self.global_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
460
461
461 @assert_repo_settings
462 @assert_repo_settings
462 def get_repo_svn_branch_patterns(self):
463 def get_repo_svn_branch_patterns(self):
463 return self.repo_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
464 return self.repo_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
464
465
465 def get_global_svn_tag_patterns(self):
466 def get_global_svn_tag_patterns(self):
466 return self.global_settings.get_ui_by_section(self.SVN_TAG_SECTION)
467 return self.global_settings.get_ui_by_section(self.SVN_TAG_SECTION)
467
468
468 @assert_repo_settings
469 @assert_repo_settings
469 def get_repo_svn_tag_patterns(self):
470 def get_repo_svn_tag_patterns(self):
470 return self.repo_settings.get_ui_by_section(self.SVN_TAG_SECTION)
471 return self.repo_settings.get_ui_by_section(self.SVN_TAG_SECTION)
471
472
472 def get_global_settings(self):
473 def get_global_settings(self):
473 return self._collect_all_settings(global_=True)
474 return self._collect_all_settings(global_=True)
474
475
475 @assert_repo_settings
476 @assert_repo_settings
476 def get_repo_settings(self):
477 def get_repo_settings(self):
477 return self._collect_all_settings(global_=False)
478 return self._collect_all_settings(global_=False)
478
479
479 @assert_repo_settings
480 @assert_repo_settings
480 def create_or_update_repo_settings(
481 def create_or_update_repo_settings(
481 self, data, inherit_global_settings=False):
482 self, data, inherit_global_settings=False):
482 from rhodecode.model.scm import ScmModel
483 from rhodecode.model.scm import ScmModel
483
484
484 self.inherit_global_settings = inherit_global_settings
485 self.inherit_global_settings = inherit_global_settings
485
486
486 repo = self.repo_settings.get_repo()
487 repo = self.repo_settings.get_repo()
487 if not inherit_global_settings:
488 if not inherit_global_settings:
488 if repo.repo_type == 'svn':
489 if repo.repo_type == 'svn':
489 self.create_repo_svn_settings(data)
490 self.create_repo_svn_settings(data)
490 else:
491 else:
491 self.create_or_update_repo_hook_settings(data)
492 self.create_or_update_repo_hook_settings(data)
492 self.create_or_update_repo_pr_settings(data)
493 self.create_or_update_repo_pr_settings(data)
493
494
494 if repo.repo_type == 'hg':
495 if repo.repo_type == 'hg':
495 self.create_or_update_repo_hg_settings(data)
496 self.create_or_update_repo_hg_settings(data)
496
497
497 if repo.repo_type == 'git':
498 if repo.repo_type == 'git':
498 self.create_or_update_repo_git_settings(data)
499 self.create_or_update_repo_git_settings(data)
499
500
500 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
501 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
501
502
502 @assert_repo_settings
503 @assert_repo_settings
503 def create_or_update_repo_hook_settings(self, data):
504 def create_or_update_repo_hook_settings(self, data):
504 for section, key in self.HOOKS_SETTINGS:
505 for section, key in self.HOOKS_SETTINGS:
505 data_key = self._get_form_ui_key(section, key)
506 data_key = self._get_form_ui_key(section, key)
506 if data_key not in data:
507 if data_key not in data:
507 raise ValueError(
508 raise ValueError(
508 'The given data does not contain {} key'.format(data_key))
509 'The given data does not contain {} key'.format(data_key))
509
510
510 active = data.get(data_key)
511 active = data.get(data_key)
511 repo_setting = self.repo_settings.get_ui_by_section_and_key(
512 repo_setting = self.repo_settings.get_ui_by_section_and_key(
512 section, key)
513 section, key)
513 if not repo_setting:
514 if not repo_setting:
514 global_setting = self.global_settings.\
515 global_setting = self.global_settings.\
515 get_ui_by_section_and_key(section, key)
516 get_ui_by_section_and_key(section, key)
516 self.repo_settings.create_ui_section_value(
517 self.repo_settings.create_ui_section_value(
517 section, global_setting.ui_value, key=key, active=active)
518 section, global_setting.ui_value, key=key, active=active)
518 else:
519 else:
519 repo_setting.ui_active = active
520 repo_setting.ui_active = active
520 Session().add(repo_setting)
521 Session().add(repo_setting)
521
522
522 def update_global_hook_settings(self, data):
523 def update_global_hook_settings(self, data):
523 for section, key in self.HOOKS_SETTINGS:
524 for section, key in self.HOOKS_SETTINGS:
524 data_key = self._get_form_ui_key(section, key)
525 data_key = self._get_form_ui_key(section, key)
525 if data_key not in data:
526 if data_key not in data:
526 raise ValueError(
527 raise ValueError(
527 'The given data does not contain {} key'.format(data_key))
528 'The given data does not contain {} key'.format(data_key))
528 active = data.get(data_key)
529 active = data.get(data_key)
529 repo_setting = self.global_settings.get_ui_by_section_and_key(
530 repo_setting = self.global_settings.get_ui_by_section_and_key(
530 section, key)
531 section, key)
531 repo_setting.ui_active = active
532 repo_setting.ui_active = active
532 Session().add(repo_setting)
533 Session().add(repo_setting)
533
534
534 @assert_repo_settings
535 @assert_repo_settings
535 def create_or_update_repo_pr_settings(self, data):
536 def create_or_update_repo_pr_settings(self, data):
536 return self._create_or_update_general_settings(
537 return self._create_or_update_general_settings(
537 self.repo_settings, data)
538 self.repo_settings, data)
538
539
539 def create_or_update_global_pr_settings(self, data):
540 def create_or_update_global_pr_settings(self, data):
540 return self._create_or_update_general_settings(
541 return self._create_or_update_general_settings(
541 self.global_settings, data)
542 self.global_settings, data)
542
543
543 @assert_repo_settings
544 @assert_repo_settings
544 def create_repo_svn_settings(self, data):
545 def create_repo_svn_settings(self, data):
545 return self._create_svn_settings(self.repo_settings, data)
546 return self._create_svn_settings(self.repo_settings, data)
546
547
547 @assert_repo_settings
548 @assert_repo_settings
548 def create_or_update_repo_hg_settings(self, data):
549 def create_or_update_repo_hg_settings(self, data):
549 largefiles, phases = \
550 largefiles, phases, evolve = \
550 self.HG_SETTINGS
551 self.HG_SETTINGS
551 largefiles_key, phases_key = \
552 largefiles_key, phases_key, evolve_key = \
552 self._get_settings_keys(self.HG_SETTINGS, data)
553 self._get_settings_keys(self.HG_SETTINGS, data)
553
554
554 self._create_or_update_ui(
555 self._create_or_update_ui(
555 self.repo_settings, *largefiles, value='',
556 self.repo_settings, *largefiles, value='',
556 active=data[largefiles_key])
557 active=data[largefiles_key])
557 self._create_or_update_ui(
558 self._create_or_update_ui(
559 self.repo_settings, *evolve, value='',
560 active=data[evolve_key])
561 self._create_or_update_ui(
558 self.repo_settings, *phases, value=safe_str(data[phases_key]))
562 self.repo_settings, *phases, value=safe_str(data[phases_key]))
559
563
560 def create_or_update_global_hg_settings(self, data):
564 def create_or_update_global_hg_settings(self, data):
561 largefiles, largefiles_store, phases, hgsubversion \
565 largefiles, largefiles_store, phases, hgsubversion, evolve \
562 = self.GLOBAL_HG_SETTINGS
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 = self._get_settings_keys(self.GLOBAL_HG_SETTINGS, data)
568 = self._get_settings_keys(self.GLOBAL_HG_SETTINGS, data)
569
565 self._create_or_update_ui(
570 self._create_or_update_ui(
566 self.global_settings, *largefiles, value='',
571 self.global_settings, *largefiles, value='',
567 active=data[largefiles_key])
572 active=data[largefiles_key])
568 self._create_or_update_ui(
573 self._create_or_update_ui(
569 self.global_settings, *largefiles_store,
574 self.global_settings, *largefiles_store,
570 value=data[largefiles_store_key])
575 value=data[largefiles_store_key])
571 self._create_or_update_ui(
576 self._create_or_update_ui(
572 self.global_settings, *phases, value=safe_str(data[phases_key]))
577 self.global_settings, *phases, value=safe_str(data[phases_key]))
573 self._create_or_update_ui(
578 self._create_or_update_ui(
574 self.global_settings, *hgsubversion, active=data[subversion_key])
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 def create_or_update_repo_git_settings(self, data):
584 def create_or_update_repo_git_settings(self, data):
577 # NOTE(marcink): # comma make unpack work properly
585 # NOTE(marcink): # comma make unpack work properly
578 lfs_enabled, \
586 lfs_enabled, \
579 = self.GIT_SETTINGS
587 = self.GIT_SETTINGS
580
588
581 lfs_enabled_key, \
589 lfs_enabled_key, \
582 = self._get_settings_keys(self.GIT_SETTINGS, data)
590 = self._get_settings_keys(self.GIT_SETTINGS, data)
583
591
584 self._create_or_update_ui(
592 self._create_or_update_ui(
585 self.repo_settings, *lfs_enabled, value=data[lfs_enabled_key],
593 self.repo_settings, *lfs_enabled, value=data[lfs_enabled_key],
586 active=data[lfs_enabled_key])
594 active=data[lfs_enabled_key])
587
595
588 def create_or_update_global_git_settings(self, data):
596 def create_or_update_global_git_settings(self, data):
589 lfs_enabled, lfs_store_location \
597 lfs_enabled, lfs_store_location \
590 = self.GLOBAL_GIT_SETTINGS
598 = self.GLOBAL_GIT_SETTINGS
591 lfs_enabled_key, lfs_store_location_key \
599 lfs_enabled_key, lfs_store_location_key \
592 = self._get_settings_keys(self.GLOBAL_GIT_SETTINGS, data)
600 = self._get_settings_keys(self.GLOBAL_GIT_SETTINGS, data)
593
601
594 self._create_or_update_ui(
602 self._create_or_update_ui(
595 self.global_settings, *lfs_enabled, value=data[lfs_enabled_key],
603 self.global_settings, *lfs_enabled, value=data[lfs_enabled_key],
596 active=data[lfs_enabled_key])
604 active=data[lfs_enabled_key])
597 self._create_or_update_ui(
605 self._create_or_update_ui(
598 self.global_settings, *lfs_store_location,
606 self.global_settings, *lfs_store_location,
599 value=data[lfs_store_location_key])
607 value=data[lfs_store_location_key])
600
608
601 def create_or_update_global_svn_settings(self, data):
609 def create_or_update_global_svn_settings(self, data):
602 # branch/tags patterns
610 # branch/tags patterns
603 self._create_svn_settings(self.global_settings, data)
611 self._create_svn_settings(self.global_settings, data)
604
612
605 http_requests_enabled, http_server_url = self.GLOBAL_SVN_SETTINGS
613 http_requests_enabled, http_server_url = self.GLOBAL_SVN_SETTINGS
606 http_requests_enabled_key, http_server_url_key = self._get_settings_keys(
614 http_requests_enabled_key, http_server_url_key = self._get_settings_keys(
607 self.GLOBAL_SVN_SETTINGS, data)
615 self.GLOBAL_SVN_SETTINGS, data)
608
616
609 self._create_or_update_ui(
617 self._create_or_update_ui(
610 self.global_settings, *http_requests_enabled,
618 self.global_settings, *http_requests_enabled,
611 value=safe_str(data[http_requests_enabled_key]))
619 value=safe_str(data[http_requests_enabled_key]))
612 self._create_or_update_ui(
620 self._create_or_update_ui(
613 self.global_settings, *http_server_url,
621 self.global_settings, *http_server_url,
614 value=data[http_server_url_key])
622 value=data[http_server_url_key])
615
623
616 def update_global_ssl_setting(self, value):
624 def update_global_ssl_setting(self, value):
617 self._create_or_update_ui(
625 self._create_or_update_ui(
618 self.global_settings, *self.SSL_SETTING, value=value)
626 self.global_settings, *self.SSL_SETTING, value=value)
619
627
620 def update_global_path_setting(self, value):
628 def update_global_path_setting(self, value):
621 self._create_or_update_ui(
629 self._create_or_update_ui(
622 self.global_settings, *self.PATH_SETTING, value=value)
630 self.global_settings, *self.PATH_SETTING, value=value)
623
631
624 @assert_repo_settings
632 @assert_repo_settings
625 def delete_repo_svn_pattern(self, id_):
633 def delete_repo_svn_pattern(self, id_):
626 self.repo_settings.delete_ui(id_)
634 self.repo_settings.delete_ui(id_)
627
635
628 def delete_global_svn_pattern(self, id_):
636 def delete_global_svn_pattern(self, id_):
629 self.global_settings.delete_ui(id_)
637 self.global_settings.delete_ui(id_)
630
638
631 @assert_repo_settings
639 @assert_repo_settings
632 def get_repo_ui_settings(self, section=None, key=None):
640 def get_repo_ui_settings(self, section=None, key=None):
633 global_uis = self.global_settings.get_ui(section, key)
641 global_uis = self.global_settings.get_ui(section, key)
634 repo_uis = self.repo_settings.get_ui(section, key)
642 repo_uis = self.repo_settings.get_ui(section, key)
635 filtered_repo_uis = self._filter_ui_settings(repo_uis)
643 filtered_repo_uis = self._filter_ui_settings(repo_uis)
636 filtered_repo_uis_keys = [
644 filtered_repo_uis_keys = [
637 (s.section, s.key) for s in filtered_repo_uis]
645 (s.section, s.key) for s in filtered_repo_uis]
638
646
639 def _is_global_ui_filtered(ui):
647 def _is_global_ui_filtered(ui):
640 return (
648 return (
641 (ui.section, ui.key) in filtered_repo_uis_keys
649 (ui.section, ui.key) in filtered_repo_uis_keys
642 or ui.section in self._svn_sections)
650 or ui.section in self._svn_sections)
643
651
644 filtered_global_uis = [
652 filtered_global_uis = [
645 ui for ui in global_uis if not _is_global_ui_filtered(ui)]
653 ui for ui in global_uis if not _is_global_ui_filtered(ui)]
646
654
647 return filtered_global_uis + filtered_repo_uis
655 return filtered_global_uis + filtered_repo_uis
648
656
649 def get_global_ui_settings(self, section=None, key=None):
657 def get_global_ui_settings(self, section=None, key=None):
650 return self.global_settings.get_ui(section, key)
658 return self.global_settings.get_ui(section, key)
651
659
652 def get_ui_settings_as_config_obj(self, section=None, key=None):
660 def get_ui_settings_as_config_obj(self, section=None, key=None):
653 config = base.Config()
661 config = base.Config()
654
662
655 ui_settings = self.get_ui_settings(section=section, key=key)
663 ui_settings = self.get_ui_settings(section=section, key=key)
656
664
657 for entry in ui_settings:
665 for entry in ui_settings:
658 config.set(entry.section, entry.key, entry.value)
666 config.set(entry.section, entry.key, entry.value)
659
667
660 return config
668 return config
661
669
662 def get_ui_settings(self, section=None, key=None):
670 def get_ui_settings(self, section=None, key=None):
663 if not self.repo_settings or self.inherit_global_settings:
671 if not self.repo_settings or self.inherit_global_settings:
664 return self.get_global_ui_settings(section, key)
672 return self.get_global_ui_settings(section, key)
665 else:
673 else:
666 return self.get_repo_ui_settings(section, key)
674 return self.get_repo_ui_settings(section, key)
667
675
668 def get_svn_patterns(self, section=None):
676 def get_svn_patterns(self, section=None):
669 if not self.repo_settings:
677 if not self.repo_settings:
670 return self.get_global_ui_settings(section)
678 return self.get_global_ui_settings(section)
671 else:
679 else:
672 return self.get_repo_ui_settings(section)
680 return self.get_repo_ui_settings(section)
673
681
674 @assert_repo_settings
682 @assert_repo_settings
675 def get_repo_general_settings(self):
683 def get_repo_general_settings(self):
676 global_settings = self.global_settings.get_all_settings()
684 global_settings = self.global_settings.get_all_settings()
677 repo_settings = self.repo_settings.get_all_settings()
685 repo_settings = self.repo_settings.get_all_settings()
678 filtered_repo_settings = self._filter_general_settings(repo_settings)
686 filtered_repo_settings = self._filter_general_settings(repo_settings)
679 global_settings.update(filtered_repo_settings)
687 global_settings.update(filtered_repo_settings)
680 return global_settings
688 return global_settings
681
689
682 def get_global_general_settings(self):
690 def get_global_general_settings(self):
683 return self.global_settings.get_all_settings()
691 return self.global_settings.get_all_settings()
684
692
685 def get_general_settings(self):
693 def get_general_settings(self):
686 if not self.repo_settings or self.inherit_global_settings:
694 if not self.repo_settings or self.inherit_global_settings:
687 return self.get_global_general_settings()
695 return self.get_global_general_settings()
688 else:
696 else:
689 return self.get_repo_general_settings()
697 return self.get_repo_general_settings()
690
698
691 def get_repos_location(self):
699 def get_repos_location(self):
692 return self.global_settings.get_ui_by_key('/').ui_value
700 return self.global_settings.get_ui_by_key('/').ui_value
693
701
694 def _filter_ui_settings(self, settings):
702 def _filter_ui_settings(self, settings):
695 filtered_settings = [
703 filtered_settings = [
696 s for s in settings if self._should_keep_setting(s)]
704 s for s in settings if self._should_keep_setting(s)]
697 return filtered_settings
705 return filtered_settings
698
706
699 def _should_keep_setting(self, setting):
707 def _should_keep_setting(self, setting):
700 keep = (
708 keep = (
701 (setting.section, setting.key) in self._ui_settings or
709 (setting.section, setting.key) in self._ui_settings or
702 setting.section in self._svn_sections)
710 setting.section in self._svn_sections)
703 return keep
711 return keep
704
712
705 def _filter_general_settings(self, settings):
713 def _filter_general_settings(self, settings):
706 keys = ['rhodecode_{}'.format(key) for key in self.GENERAL_SETTINGS]
714 keys = ['rhodecode_{}'.format(key) for key in self.GENERAL_SETTINGS]
707 return {
715 return {
708 k: settings[k]
716 k: settings[k]
709 for k in settings if k in keys}
717 for k in settings if k in keys}
710
718
711 def _collect_all_settings(self, global_=False):
719 def _collect_all_settings(self, global_=False):
712 settings = self.global_settings if global_ else self.repo_settings
720 settings = self.global_settings if global_ else self.repo_settings
713 result = {}
721 result = {}
714
722
715 for section, key in self._ui_settings:
723 for section, key in self._ui_settings:
716 ui = settings.get_ui_by_section_and_key(section, key)
724 ui = settings.get_ui_by_section_and_key(section, key)
717 result_key = self._get_form_ui_key(section, key)
725 result_key = self._get_form_ui_key(section, key)
718
726
719 if ui:
727 if ui:
720 if section in ('hooks', 'extensions'):
728 if section in ('hooks', 'extensions'):
721 result[result_key] = ui.ui_active
729 result[result_key] = ui.ui_active
722 elif result_key in ['vcs_git_lfs_enabled']:
730 elif result_key in ['vcs_git_lfs_enabled']:
723 result[result_key] = ui.ui_active
731 result[result_key] = ui.ui_active
724 else:
732 else:
725 result[result_key] = ui.ui_value
733 result[result_key] = ui.ui_value
726
734
727 for name in self.GENERAL_SETTINGS:
735 for name in self.GENERAL_SETTINGS:
728 setting = settings.get_setting_by_name(name)
736 setting = settings.get_setting_by_name(name)
729 if setting:
737 if setting:
730 result_key = 'rhodecode_{}'.format(name)
738 result_key = 'rhodecode_{}'.format(name)
731 result[result_key] = setting.app_settings_value
739 result[result_key] = setting.app_settings_value
732
740
733 return result
741 return result
734
742
735 def _get_form_ui_key(self, section, key):
743 def _get_form_ui_key(self, section, key):
736 return '{section}_{key}'.format(
744 return '{section}_{key}'.format(
737 section=section, key=key.replace('.', '_'))
745 section=section, key=key.replace('.', '_'))
738
746
739 def _create_or_update_ui(
747 def _create_or_update_ui(
740 self, settings, section, key, value=None, active=None):
748 self, settings, section, key, value=None, active=None):
741 ui = settings.get_ui_by_section_and_key(section, key)
749 ui = settings.get_ui_by_section_and_key(section, key)
742 if not ui:
750 if not ui:
743 active = True if active is None else active
751 active = True if active is None else active
744 settings.create_ui_section_value(
752 settings.create_ui_section_value(
745 section, value, key=key, active=active)
753 section, value, key=key, active=active)
746 else:
754 else:
747 if active is not None:
755 if active is not None:
748 ui.ui_active = active
756 ui.ui_active = active
749 if value is not None:
757 if value is not None:
750 ui.ui_value = value
758 ui.ui_value = value
751 Session().add(ui)
759 Session().add(ui)
752
760
753 def _create_svn_settings(self, settings, data):
761 def _create_svn_settings(self, settings, data):
754 svn_settings = {
762 svn_settings = {
755 'new_svn_branch': self.SVN_BRANCH_SECTION,
763 'new_svn_branch': self.SVN_BRANCH_SECTION,
756 'new_svn_tag': self.SVN_TAG_SECTION
764 'new_svn_tag': self.SVN_TAG_SECTION
757 }
765 }
758 for key in svn_settings:
766 for key in svn_settings:
759 if data.get(key):
767 if data.get(key):
760 settings.create_ui_section_value(svn_settings[key], data[key])
768 settings.create_ui_section_value(svn_settings[key], data[key])
761
769
762 def _create_or_update_general_settings(self, settings, data):
770 def _create_or_update_general_settings(self, settings, data):
763 for name in self.GENERAL_SETTINGS:
771 for name in self.GENERAL_SETTINGS:
764 data_key = 'rhodecode_{}'.format(name)
772 data_key = 'rhodecode_{}'.format(name)
765 if data_key not in data:
773 if data_key not in data:
766 raise ValueError(
774 raise ValueError(
767 'The given data does not contain {} key'.format(data_key))
775 'The given data does not contain {} key'.format(data_key))
768 setting = settings.create_or_update_setting(
776 setting = settings.create_or_update_setting(
769 name, data[data_key], 'bool')
777 name, data[data_key], 'bool')
770 Session().add(setting)
778 Session().add(setting)
771
779
772 def _get_settings_keys(self, settings, data):
780 def _get_settings_keys(self, settings, data):
773 data_keys = [self._get_form_ui_key(*s) for s in settings]
781 data_keys = [self._get_form_ui_key(*s) for s in settings]
774 for data_key in data_keys:
782 for data_key in data_keys:
775 if data_key not in data:
783 if data_key not in data:
776 raise ValueError(
784 raise ValueError(
777 'The given data does not contain {} key'.format(data_key))
785 'The given data does not contain {} key'.format(data_key))
778 return data_keys
786 return data_keys
779
787
780 def create_largeobjects_dirs_if_needed(self, repo_store_path):
788 def create_largeobjects_dirs_if_needed(self, repo_store_path):
781 """
789 """
782 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
790 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
783 does a repository scan if enabled in the settings.
791 does a repository scan if enabled in the settings.
784 """
792 """
785
793
786 from rhodecode.lib.vcs.backends.hg import largefiles_store
794 from rhodecode.lib.vcs.backends.hg import largefiles_store
787 from rhodecode.lib.vcs.backends.git import lfs_store
795 from rhodecode.lib.vcs.backends.git import lfs_store
788
796
789 paths = [
797 paths = [
790 largefiles_store(repo_store_path),
798 largefiles_store(repo_store_path),
791 lfs_store(repo_store_path)]
799 lfs_store(repo_store_path)]
792
800
793 for path in paths:
801 for path in paths:
794 if os.path.isdir(path):
802 if os.path.isdir(path):
795 continue
803 continue
796 if os.path.isfile(path):
804 if os.path.isfile(path):
797 continue
805 continue
798 # not a file nor dir, we try to create it
806 # not a file nor dir, we try to create it
799 try:
807 try:
800 os.makedirs(path)
808 os.makedirs(path)
801 except Exception:
809 except Exception:
802 log.warning('Failed to create largefiles dir:%s', path)
810 log.warning('Failed to create largefiles dir:%s', path)
@@ -1,541 +1,546 b''
1
1
2 // tables.less
2 // tables.less
3 // For use in RhodeCode application tables;
3 // For use in RhodeCode application tables;
4 // see style guide documentation for guidelines.
4 // see style guide documentation for guidelines.
5
5
6 // TABLES
6 // TABLES
7
7
8 .rctable,
8 .rctable,
9 table.rctable,
9 table.rctable,
10 table.dataTable {
10 table.dataTable {
11 clear:both;
11 clear:both;
12 width: 100%;
12 width: 100%;
13 margin: 0 auto @padding;
13 margin: 0 auto @padding;
14 padding: 0;
14 padding: 0;
15 vertical-align: baseline;
15 vertical-align: baseline;
16 line-height:1.5em;
16 line-height:1.5em;
17 border: none;
17 border: none;
18 outline: none;
18 outline: none;
19 border-collapse: collapse;
19 border-collapse: collapse;
20 border-spacing: 0;
20 border-spacing: 0;
21 color: @grey2;
21 color: @grey2;
22
22
23 b {
23 b {
24 font-weight: normal;
24 font-weight: normal;
25 }
25 }
26
26
27 em {
27 em {
28 font-weight: bold;
28 font-weight: bold;
29 font-style: normal;
29 font-style: normal;
30 }
30 }
31
31
32 th,
32 th,
33 td {
33 td {
34 height: auto;
34 height: auto;
35 max-width: 20%;
35 max-width: 20%;
36 padding: .65em 1em .65em 0;
36 padding: .65em 1em .65em 0;
37 vertical-align: middle;
37 vertical-align: middle;
38 border-bottom: @border-thickness solid @grey5;
38 border-bottom: @border-thickness solid @grey5;
39 white-space: normal;
39 white-space: normal;
40
40
41 &.td-radio,
41 &.td-radio,
42 &.td-checkbox {
42 &.td-checkbox {
43 padding-right: 0;
43 padding-right: 0;
44 text-align: center;
44 text-align: center;
45
45
46 input {
46 input {
47 margin: 0 1em;
47 margin: 0 1em;
48 }
48 }
49 }
49 }
50
50
51 &.truncate-wrap {
51 &.truncate-wrap {
52 white-space: nowrap !important;
52 white-space: nowrap !important;
53 }
53 }
54
54
55 pre {
55 pre {
56 margin: 0;
56 margin: 0;
57 }
57 }
58
58
59 .show_more {
59 .show_more {
60 height: inherit;
60 height: inherit;
61 }
61 }
62 }
62 }
63
63
64 .expired td {
64 .expired td {
65 background-color: @grey7;
65 background-color: @grey7;
66 }
66 }
67
67
68 .td-radio + .td-owner {
68 .td-radio + .td-owner {
69 padding-left: 1em;
69 padding-left: 1em;
70 }
70 }
71
71
72
72
73 th {
73 th {
74 text-align: left;
74 text-align: left;
75 font-family: @text-semibold;
75 font-family: @text-semibold;
76 }
76 }
77
77
78 .hl {
78 .hl {
79 td {
79 td {
80 background-color: lighten(@alert4,25%);
80 background-color: lighten(@alert4,25%);
81 }
81 }
82 }
82 }
83
83
84 // Special Data Cell Types
84 // Special Data Cell Types
85 // See style guide for desciptions and examples.
85 // See style guide for desciptions and examples.
86
86
87 td {
87 td {
88
88
89 &.user {
89 &.user {
90 padding-left: 1em;
90 padding-left: 1em;
91 }
91 }
92
92
93 &.td-rss {
93 &.td-rss {
94 width: 20px;
94 width: 20px;
95 min-width: 0;
95 min-width: 0;
96 margin: 0;
96 margin: 0;
97 }
97 }
98
98
99 &.quick_repo_menu {
99 &.quick_repo_menu {
100 width: 15px;
100 width: 15px;
101 text-align: center;
101 text-align: center;
102
102
103 &:hover {
103 &:hover {
104 background-color: @grey5;
104 background-color: @grey5;
105 }
105 }
106 }
106 }
107
107
108 &.td-hash {
108 &.td-hash {
109 min-width: 80px;
109 min-width: 80px;
110 width: 200px;
110 width: 200px;
111
112 .obsolete {
113 text-decoration: line-through;
114 color: lighten(@grey2,25%);
115 }
111 }
116 }
112
117
113 &.td-time {
118 &.td-time {
114 width: 160px;
119 width: 160px;
115 white-space: nowrap;
120 white-space: nowrap;
116 }
121 }
117
122
118 &.annotate{
123 &.annotate{
119 padding-right: 0;
124 padding-right: 0;
120
125
121 div.annotatediv{
126 div.annotatediv{
122 margin: 0 0.7em;
127 margin: 0 0.7em;
123 }
128 }
124 }
129 }
125
130
126 &.tags-col {
131 &.tags-col {
127 padding-right: 0;
132 padding-right: 0;
128 }
133 }
129
134
130 &.td-description {
135 &.td-description {
131 min-width: 350px;
136 min-width: 350px;
132
137
133 &.truncate, .truncate-wrap {
138 &.truncate, .truncate-wrap {
134 white-space: nowrap;
139 white-space: nowrap;
135 overflow: hidden;
140 overflow: hidden;
136 text-overflow: ellipsis;
141 text-overflow: ellipsis;
137 max-width: 450px;
142 max-width: 450px;
138 }
143 }
139 }
144 }
140
145
141 &.td-componentname {
146 &.td-componentname {
142 white-space: nowrap;
147 white-space: nowrap;
143 }
148 }
144
149
145 &.td-journalaction {
150 &.td-journalaction {
146 min-width: 300px;
151 min-width: 300px;
147
152
148 .journal_action_params {
153 .journal_action_params {
149 // waiting for feedback
154 // waiting for feedback
150 }
155 }
151 }
156 }
152
157
153 &.td-active {
158 &.td-active {
154 padding-left: .65em;
159 padding-left: .65em;
155 }
160 }
156
161
157 &.td-url {
162 &.td-url {
158 white-space: nowrap;
163 white-space: nowrap;
159 }
164 }
160
165
161 &.td-comments {
166 &.td-comments {
162 min-width: 3em;
167 min-width: 3em;
163 }
168 }
164
169
165 &.td-buttons {
170 &.td-buttons {
166 padding: .3em 0;
171 padding: .3em 0;
167 }
172 }
168
173
169 &.td-action {
174 &.td-action {
170 // this is for the remove/delete/edit buttons
175 // this is for the remove/delete/edit buttons
171 padding-right: 0;
176 padding-right: 0;
172 min-width: 95px;
177 min-width: 95px;
173 text-transform: capitalize;
178 text-transform: capitalize;
174
179
175 i {
180 i {
176 display: none;
181 display: none;
177 }
182 }
178 }
183 }
179
184
180 // TODO: lisa: this needs to be cleaned up with the buttons
185 // TODO: lisa: this needs to be cleaned up with the buttons
181 .grid_edit,
186 .grid_edit,
182 .grid_delete {
187 .grid_delete {
183 display: inline-block;
188 display: inline-block;
184 margin: 0 @padding/3 0 0;
189 margin: 0 @padding/3 0 0;
185 font-family: @text-light;
190 font-family: @text-light;
186
191
187 i {
192 i {
188 display: none;
193 display: none;
189 }
194 }
190 }
195 }
191
196
192 .grid_edit + .grid_delete {
197 .grid_edit + .grid_delete {
193 border-left: @border-thickness solid @grey5;
198 border-left: @border-thickness solid @grey5;
194 padding-left: @padding/2;
199 padding-left: @padding/2;
195 }
200 }
196
201
197 &.td-compare {
202 &.td-compare {
198
203
199 input {
204 input {
200 margin-right: 1em;
205 margin-right: 1em;
201 }
206 }
202
207
203 .compare-radio-button {
208 .compare-radio-button {
204 margin: 0 1em 0 0;
209 margin: 0 1em 0 0;
205 }
210 }
206
211
207
212
208 }
213 }
209
214
210 &.td-tags {
215 &.td-tags {
211 padding: .5em 1em .5em 0;
216 padding: .5em 1em .5em 0;
212 width: 140px;
217 width: 140px;
213
218
214 .tag {
219 .tag {
215 margin: 1px;
220 margin: 1px;
216 float: left;
221 float: left;
217 }
222 }
218 }
223 }
219
224
220 .icon-svn, .icon-hg, .icon-git {
225 .icon-svn, .icon-hg, .icon-git {
221 font-size: 1.4em;
226 font-size: 1.4em;
222 }
227 }
223
228
224 &.collapse_commit,
229 &.collapse_commit,
225 &.expand_commit {
230 &.expand_commit {
226 padding-right: 0;
231 padding-right: 0;
227 padding-left: 1em;
232 padding-left: 1em;
228 }
233 }
229 }
234 }
230
235
231 .perm_admin_row {
236 .perm_admin_row {
232 color: @grey4;
237 color: @grey4;
233 background-color: @grey6;
238 background-color: @grey6;
234 }
239 }
235
240
236 .noborder {
241 .noborder {
237 border: none;
242 border: none;
238
243
239 td {
244 td {
240 border: none;
245 border: none;
241 }
246 }
242 }
247 }
243 }
248 }
244
249
245 // TRUNCATING
250 // TRUNCATING
246 // TODO: lisaq: should this possibly be moved out of tables.less?
251 // TODO: lisaq: should this possibly be moved out of tables.less?
247 // for truncated text
252 // for truncated text
248 // used inside of table cells and in code block headers
253 // used inside of table cells and in code block headers
249 .truncate-wrap {
254 .truncate-wrap {
250 white-space: nowrap !important;
255 white-space: nowrap !important;
251
256
252 //truncated text
257 //truncated text
253 .truncate {
258 .truncate {
254 max-width: 450px;
259 max-width: 450px;
255 width: 300px;
260 width: 300px;
256 overflow: hidden;
261 overflow: hidden;
257 text-overflow: ellipsis;
262 text-overflow: ellipsis;
258 -o-text-overflow: ellipsis;
263 -o-text-overflow: ellipsis;
259 -ms-text-overflow: ellipsis;
264 -ms-text-overflow: ellipsis;
260
265
261 &.autoexpand {
266 &.autoexpand {
262 width: 120px;
267 width: 120px;
263 margin-right: 200px;
268 margin-right: 200px;
264 }
269 }
265 }
270 }
266 &:hover .truncate.autoexpand {
271 &:hover .truncate.autoexpand {
267 overflow: visible;
272 overflow: visible;
268 }
273 }
269
274
270 .tags-truncate {
275 .tags-truncate {
271 width: 150px;
276 width: 150px;
272 height: 22px;
277 height: 22px;
273 overflow: hidden;
278 overflow: hidden;
274
279
275 .tag {
280 .tag {
276 display: inline-block;
281 display: inline-block;
277 }
282 }
278
283
279 &.truncate {
284 &.truncate {
280 height: 22px;
285 height: 22px;
281 max-height:2em;
286 max-height:2em;
282 width: 140px;
287 width: 140px;
283 }
288 }
284 }
289 }
285 }
290 }
286
291
287 .apikeys_wrap {
292 .apikeys_wrap {
288 margin-bottom: @padding;
293 margin-bottom: @padding;
289
294
290 table.rctable td:first-child {
295 table.rctable td:first-child {
291 width: 340px;
296 width: 340px;
292 }
297 }
293 }
298 }
294
299
295
300
296
301
297 // SPECIAL CASES
302 // SPECIAL CASES
298
303
299 // Repository Followers
304 // Repository Followers
300 table.rctable.followers_data {
305 table.rctable.followers_data {
301 width: 75%;
306 width: 75%;
302 margin: 0;
307 margin: 0;
303 }
308 }
304
309
305 // Repository List
310 // Repository List
306 // Group Members List
311 // Group Members List
307 table.rctable.group_members,
312 table.rctable.group_members,
308 table#repo_list_table {
313 table#repo_list_table {
309 min-width: 600px;
314 min-width: 600px;
310 }
315 }
311
316
312 // Keyboard mappings
317 // Keyboard mappings
313 table.keyboard-mappings {
318 table.keyboard-mappings {
314 th {
319 th {
315 text-align: left;
320 text-align: left;
316 font-family: @text-semibold;
321 font-family: @text-semibold;
317 }
322 }
318 }
323 }
319
324
320 // Branches, Tags, and Bookmarks
325 // Branches, Tags, and Bookmarks
321 #obj_list_table.dataTable {
326 #obj_list_table.dataTable {
322 td.td-time {
327 td.td-time {
323 padding-right: 1em;
328 padding-right: 1em;
324 }
329 }
325 }
330 }
326
331
327 // User Admin
332 // User Admin
328 .rctable.useremails,
333 .rctable.useremails,
329 .rctable.account_emails {
334 .rctable.account_emails {
330 .tag,
335 .tag,
331 .btn {
336 .btn {
332 float: right;
337 float: right;
333 }
338 }
334 .btn { //to line up with tags
339 .btn { //to line up with tags
335 margin-right: 1.65em;
340 margin-right: 1.65em;
336 }
341 }
337 }
342 }
338
343
339 // User List
344 // User List
340 #user_list_table {
345 #user_list_table {
341
346
342 td.td-user {
347 td.td-user {
343 min-width: 100px;
348 min-width: 100px;
344 }
349 }
345 }
350 }
346
351
347 // Pull Request List Table
352 // Pull Request List Table
348 #pull_request_list_table.dataTable {
353 #pull_request_list_table.dataTable {
349
354
350 //TODO: lisa: This needs to be removed once the description is adjusted
355 //TODO: lisa: This needs to be removed once the description is adjusted
351 // for using an expand_commit button (see issue 765)
356 // for using an expand_commit button (see issue 765)
352 td {
357 td {
353 vertical-align: middle;
358 vertical-align: middle;
354 }
359 }
355 }
360 }
356
361
357 // Settings (no border)
362 // Settings (no border)
358 table.rctable.dl-settings {
363 table.rctable.dl-settings {
359 td {
364 td {
360 border: none;
365 border: none;
361 }
366 }
362 }
367 }
363
368
364
369
365 // Statistics
370 // Statistics
366 table.trending_language_tbl {
371 table.trending_language_tbl {
367 width: 100%;
372 width: 100%;
368 line-height: 1em;
373 line-height: 1em;
369
374
370 td div {
375 td div {
371 overflow: visible;
376 overflow: visible;
372 }
377 }
373 }
378 }
374
379
375 .trending_language_tbl, .trending_language_tbl td {
380 .trending_language_tbl, .trending_language_tbl td {
376 border: 0;
381 border: 0;
377 margin: 0;
382 margin: 0;
378 padding: 0;
383 padding: 0;
379 background: transparent;
384 background: transparent;
380 }
385 }
381
386
382 .trending_language_tbl, .trending_language_tbl tr {
387 .trending_language_tbl, .trending_language_tbl tr {
383 border-spacing: 0 3px;
388 border-spacing: 0 3px;
384 }
389 }
385
390
386 .trending_language {
391 .trending_language {
387 position: relative;
392 position: relative;
388 width: 100%;
393 width: 100%;
389 height: 19px;
394 height: 19px;
390 overflow: hidden;
395 overflow: hidden;
391 background-color: @grey6;
396 background-color: @grey6;
392
397
393 span, b{
398 span, b{
394 position: absolute;
399 position: absolute;
395 display: block;
400 display: block;
396 height: 12px;
401 height: 12px;
397 margin-bottom: 0px;
402 margin-bottom: 0px;
398 white-space: pre;
403 white-space: pre;
399 padding: floor(@basefontsize/4);
404 padding: floor(@basefontsize/4);
400 top: 0;
405 top: 0;
401 left: 0;
406 left: 0;
402 }
407 }
403
408
404 span{
409 span{
405 color: @text-color;
410 color: @text-color;
406 z-index: 0;
411 z-index: 0;
407 min-width: 20px;
412 min-width: 20px;
408 }
413 }
409
414
410 b {
415 b {
411 z-index: 1;
416 z-index: 1;
412 overflow: hidden;
417 overflow: hidden;
413 background-color: @rcblue;
418 background-color: @rcblue;
414 color: #FFF;
419 color: #FFF;
415 text-decoration: none;
420 text-decoration: none;
416 }
421 }
417
422
418 }
423 }
419
424
420 // Changesets
425 // Changesets
421 #changesets.rctable {
426 #changesets.rctable {
422
427
423 // td must be fixed height for graph
428 // td must be fixed height for graph
424 td {
429 td {
425 height: 32px;
430 height: 32px;
426 padding: 0 1em 0 0;
431 padding: 0 1em 0 0;
427 vertical-align: middle;
432 vertical-align: middle;
428 white-space: nowrap;
433 white-space: nowrap;
429
434
430 &.td-description {
435 &.td-description {
431 white-space: normal;
436 white-space: normal;
432 }
437 }
433
438
434 &.expand_commit {
439 &.expand_commit {
435 padding-right: 0;
440 padding-right: 0;
436 }
441 }
437 }
442 }
438 }
443 }
439
444
440 // Compare
445 // Compare
441 table.compare_view_commits {
446 table.compare_view_commits {
442 margin-top: @space;
447 margin-top: @space;
443
448
444 td.td-time {
449 td.td-time {
445 padding-left: .5em;
450 padding-left: .5em;
446 }
451 }
447
452
448 // special case to not show hover actions on hidden indicator
453 // special case to not show hover actions on hidden indicator
449 tr.compare_select_hidden:hover {
454 tr.compare_select_hidden:hover {
450 cursor: inherit;
455 cursor: inherit;
451
456
452 td {
457 td {
453 background-color: inherit;
458 background-color: inherit;
454 }
459 }
455 }
460 }
456
461
457 tr:hover {
462 tr:hover {
458 cursor: pointer;
463 cursor: pointer;
459
464
460 td {
465 td {
461 background-color: lighten(@alert4,25%);
466 background-color: lighten(@alert4,25%);
462 }
467 }
463 }
468 }
464
469
465
470
466 }
471 }
467
472
468 .file_history {
473 .file_history {
469 td.td-actions {
474 td.td-actions {
470 text-align: right;
475 text-align: right;
471 }
476 }
472 }
477 }
473
478
474 .compare_view_files {
479 .compare_view_files {
475
480
476 td.td-actions {
481 td.td-actions {
477 text-align: right;
482 text-align: right;
478 }
483 }
479
484
480 .flag_status {
485 .flag_status {
481 margin: 0 0 0 5px;
486 margin: 0 0 0 5px;
482 }
487 }
483
488
484 td.injected_diff {
489 td.injected_diff {
485
490
486 .code-difftable {
491 .code-difftable {
487 border:none;
492 border:none;
488 }
493 }
489
494
490 .diff-container {
495 .diff-container {
491 border: @border-thickness solid @border-default-color;
496 border: @border-thickness solid @border-default-color;
492 .border-radius(@border-radius);
497 .border-radius(@border-radius);
493 }
498 }
494
499
495 div.diffblock {
500 div.diffblock {
496 border:none;
501 border:none;
497 }
502 }
498
503
499 div.code-body {
504 div.code-body {
500 max-width: 1152px;
505 max-width: 1152px;
501 }
506 }
502 }
507 }
503
508
504 .rctable {
509 .rctable {
505
510
506 td {
511 td {
507 padding-top: @space;
512 padding-top: @space;
508 }
513 }
509
514
510 &:first-child td {
515 &:first-child td {
511 padding-top: 0;
516 padding-top: 0;
512 }
517 }
513 }
518 }
514
519
515 .comment-bubble,
520 .comment-bubble,
516 .show_comments {
521 .show_comments {
517 float: right;
522 float: right;
518 visibility: hidden;
523 visibility: hidden;
519 padding: 0 1em 0 0;
524 padding: 0 1em 0 0;
520 }
525 }
521
526
522 .injected_diff {
527 .injected_diff {
523 padding-bottom: @padding;
528 padding-bottom: @padding;
524 }
529 }
525 }
530 }
526
531
527 // Gist List
532 // Gist List
528 #gist_list_table {
533 #gist_list_table {
529 td {
534 td {
530 vertical-align: middle;
535 vertical-align: middle;
531
536
532 div{
537 div{
533 display: inline-block;
538 display: inline-block;
534 vertical-align: middle;
539 vertical-align: middle;
535 }
540 }
536
541
537 img{
542 img{
538 vertical-align: middle;
543 vertical-align: middle;
539 }
544 }
540 }
545 }
541 }
546 }
@@ -1,322 +1,335 b''
1 ## snippet for displaying vcs settings
1 ## snippet for displaying vcs settings
2 ## usage:
2 ## usage:
3 ## <%namespace name="vcss" file="/base/vcssettings.mako"/>
3 ## <%namespace name="vcss" file="/base/vcssettings.mako"/>
4 ## ${vcss.vcs_settings_fields()}
4 ## ${vcss.vcs_settings_fields()}
5
5
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)">
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 % if display_globals:
7 % if display_globals:
8 <div class="panel panel-default">
8 <div class="panel panel-default">
9 <div class="panel-heading" id="general">
9 <div class="panel-heading" id="general">
10 <h3 class="panel-title">${_('General')}<a class="permalink" href="#general"> ΒΆ</a></h3>
10 <h3 class="panel-title">${_('General')}<a class="permalink" href="#general"> ΒΆ</a></h3>
11 </div>
11 </div>
12 <div class="panel-body">
12 <div class="panel-body">
13 <div class="field">
13 <div class="field">
14 <div class="checkbox">
14 <div class="checkbox">
15 ${h.checkbox('web_push_ssl' + suffix, 'True')}
15 ${h.checkbox('web_push_ssl' + suffix, 'True')}
16 <label for="web_push_ssl${suffix}">${_('Require SSL for vcs operations')}</label>
16 <label for="web_push_ssl${suffix}">${_('Require SSL for vcs operations')}</label>
17 </div>
17 </div>
18 <div class="label">
18 <div class="label">
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>
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 </div>
20 </div>
21 </div>
21 </div>
22 </div>
22 </div>
23 </div>
23 </div>
24 % endif
24 % endif
25
25
26 % if display_globals:
26 % if display_globals:
27 <div class="panel panel-default">
27 <div class="panel panel-default">
28 <div class="panel-heading" id="vcs-storage-options">
28 <div class="panel-heading" id="vcs-storage-options">
29 <h3 class="panel-title">${_('Main Storage Location')}<a class="permalink" href="#vcs-storage-options"> ΒΆ</a></h3>
29 <h3 class="panel-title">${_('Main Storage Location')}<a class="permalink" href="#vcs-storage-options"> ΒΆ</a></h3>
30 </div>
30 </div>
31 <div class="panel-body">
31 <div class="panel-body">
32 <div class="field">
32 <div class="field">
33 <div class="inputx locked_input">
33 <div class="inputx locked_input">
34 %if allow_repo_location_change:
34 %if allow_repo_location_change:
35 ${h.text('paths_root_path',size=59,readonly="readonly", class_="disabled")}
35 ${h.text('paths_root_path',size=59,readonly="readonly", class_="disabled")}
36 <span id="path_unlock" class="tooltip"
36 <span id="path_unlock" class="tooltip"
37 title="${h.tooltip(_('Click to unlock. You must restart RhodeCode in order to make this setting take effect.'))}">
37 title="${h.tooltip(_('Click to unlock. You must restart RhodeCode in order to make this setting take effect.'))}">
38 <div class="btn btn-default lock_input_button"><i id="path_unlock_icon" class="icon-lock"></i></div>
38 <div class="btn btn-default lock_input_button"><i id="path_unlock_icon" class="icon-lock"></i></div>
39 </span>
39 </span>
40 %else:
40 %else:
41 ${_('Repository location change is disabled. You can enable this by changing the `allow_repo_location_change` inside .ini file.')}
41 ${_('Repository location change is disabled. You can enable this by changing the `allow_repo_location_change` inside .ini file.')}
42 ## form still requires this but we cannot internally change it anyway
42 ## form still requires this but we cannot internally change it anyway
43 ${h.hidden('paths_root_path',size=30,readonly="readonly", class_="disabled")}
43 ${h.hidden('paths_root_path',size=30,readonly="readonly", class_="disabled")}
44 %endif
44 %endif
45 </div>
45 </div>
46 </div>
46 </div>
47 <div class="label">
47 <div class="label">
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>
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 </div>
49 </div>
50 </div>
50 </div>
51 </div>
51 </div>
52 % endif
52 % endif
53
53
54 % if display_globals or repo_type in ['git', 'hg']:
54 % if display_globals or repo_type in ['git', 'hg']:
55 <div class="panel panel-default">
55 <div class="panel panel-default">
56 <div class="panel-heading" id="vcs-hooks-options">
56 <div class="panel-heading" id="vcs-hooks-options">
57 <h3 class="panel-title">${_('Internal Hooks')}<a class="permalink" href="#vcs-hooks-options"> ΒΆ</a></h3>
57 <h3 class="panel-title">${_('Internal Hooks')}<a class="permalink" href="#vcs-hooks-options"> ΒΆ</a></h3>
58 </div>
58 </div>
59 <div class="panel-body">
59 <div class="panel-body">
60 <div class="field">
60 <div class="field">
61 <div class="checkbox">
61 <div class="checkbox">
62 ${h.checkbox('hooks_changegroup_repo_size' + suffix, 'True', **kwargs)}
62 ${h.checkbox('hooks_changegroup_repo_size' + suffix, 'True', **kwargs)}
63 <label for="hooks_changegroup_repo_size${suffix}">${_('Show repository size after push')}</label>
63 <label for="hooks_changegroup_repo_size${suffix}">${_('Show repository size after push')}</label>
64 </div>
64 </div>
65
65
66 <div class="label">
66 <div class="label">
67 <span class="help-block">${_('Trigger a hook that calculates repository size after each push.')}</span>
67 <span class="help-block">${_('Trigger a hook that calculates repository size after each push.')}</span>
68 </div>
68 </div>
69 <div class="checkbox">
69 <div class="checkbox">
70 ${h.checkbox('hooks_changegroup_push_logger' + suffix, 'True', **kwargs)}
70 ${h.checkbox('hooks_changegroup_push_logger' + suffix, 'True', **kwargs)}
71 <label for="hooks_changegroup_push_logger${suffix}">${_('Execute pre/post push hooks')}</label>
71 <label for="hooks_changegroup_push_logger${suffix}">${_('Execute pre/post push hooks')}</label>
72 </div>
72 </div>
73 <div class="label">
73 <div class="label">
74 <span class="help-block">${_('Execute Built in pre/post push hooks. This also executes rcextensions hooks.')}</span>
74 <span class="help-block">${_('Execute Built in pre/post push hooks. This also executes rcextensions hooks.')}</span>
75 </div>
75 </div>
76 <div class="checkbox">
76 <div class="checkbox">
77 ${h.checkbox('hooks_outgoing_pull_logger' + suffix, 'True', **kwargs)}
77 ${h.checkbox('hooks_outgoing_pull_logger' + suffix, 'True', **kwargs)}
78 <label for="hooks_outgoing_pull_logger${suffix}">${_('Execute pre/post pull hooks')}</label>
78 <label for="hooks_outgoing_pull_logger${suffix}">${_('Execute pre/post pull hooks')}</label>
79 </div>
79 </div>
80 <div class="label">
80 <div class="label">
81 <span class="help-block">${_('Execute Built in pre/post pull hooks. This also executes rcextensions hooks.')}</span>
81 <span class="help-block">${_('Execute Built in pre/post pull hooks. This also executes rcextensions hooks.')}</span>
82 </div>
82 </div>
83 </div>
83 </div>
84 </div>
84 </div>
85 </div>
85 </div>
86 % endif
86 % endif
87
87
88 % if display_globals or repo_type in ['hg']:
88 % if display_globals or repo_type in ['hg']:
89 <div class="panel panel-default">
89 <div class="panel panel-default">
90 <div class="panel-heading" id="vcs-hg-options">
90 <div class="panel-heading" id="vcs-hg-options">
91 <h3 class="panel-title">${_('Mercurial Settings')}<a class="permalink" href="#vcs-hg-options"> ΒΆ</a></h3>
91 <h3 class="panel-title">${_('Mercurial Settings')}<a class="permalink" href="#vcs-hg-options"> ΒΆ</a></h3>
92 </div>
92 </div>
93 <div class="panel-body">
93 <div class="panel-body">
94 <div class="checkbox">
94 <div class="checkbox">
95 ${h.checkbox('extensions_largefiles' + suffix, 'True', **kwargs)}
95 ${h.checkbox('extensions_largefiles' + suffix, 'True', **kwargs)}
96 <label for="extensions_largefiles${suffix}">${_('Enable largefiles extension')}</label>
96 <label for="extensions_largefiles${suffix}">${_('Enable largefiles extension')}</label>
97 </div>
97 </div>
98 <div class="label">
98 <div class="label">
99 % if display_globals:
99 % if display_globals:
100 <span class="help-block">${_('Enable Largefiles extensions for all repositories.')}</span>
100 <span class="help-block">${_('Enable Largefiles extensions for all repositories.')}</span>
101 % else:
101 % else:
102 <span class="help-block">${_('Enable Largefiles extensions for this repository.')}</span>
102 <span class="help-block">${_('Enable Largefiles extensions for this repository.')}</span>
103 % endif
103 % endif
104 </div>
104 </div>
105
105
106 % if display_globals:
106 % if display_globals:
107 <div class="field">
107 <div class="field">
108 <div class="input">
108 <div class="input">
109 ${h.text('largefiles_usercache' + suffix, size=59)}
109 ${h.text('largefiles_usercache' + suffix, size=59)}
110 </div>
110 </div>
111 </div>
111 </div>
112 <div class="label">
112 <div class="label">
113 <span class="help-block">${_('Filesystem location where Mercurial largefile objects should be stored.')}</span>
113 <span class="help-block">${_('Filesystem location where Mercurial largefile objects should be stored.')}</span>
114 </div>
114 </div>
115 % endif
115 % endif
116
116
117 <div class="checkbox">
117 <div class="checkbox">
118 ${h.checkbox('phases_publish' + suffix, 'True', **kwargs)}
118 ${h.checkbox('phases_publish' + suffix, 'True', **kwargs)}
119 <label for="phases_publish${suffix}">${_('Set repositories as publishing') if display_globals else _('Set repository as publishing')}</label>
119 <label for="phases_publish${suffix}">${_('Set repositories as publishing') if display_globals else _('Set repository as publishing')}</label>
120 </div>
120 </div>
121 <div class="label">
121 <div class="label">
122 <span class="help-block">${_('When this is enabled all commits in the repository are seen as public commits by clients.')}</span>
122 <span class="help-block">${_('When this is enabled all commits in the repository are seen as public commits by clients.')}</span>
123 </div>
123 </div>
124 % if display_globals:
124 % if display_globals:
125 <div class="checkbox">
125 <div class="checkbox">
126 ${h.checkbox('extensions_hgsubversion' + suffix,'True')}
126 ${h.checkbox('extensions_hgsubversion' + suffix,'True')}
127 <label for="extensions_hgsubversion${suffix}">${_('Enable hgsubversion extension')}</label>
127 <label for="extensions_hgsubversion${suffix}">${_('Enable hgsubversion extension')}</label>
128 </div>
128 </div>
129 <div class="label">
129 <div class="label">
130 <span class="help-block">${_('Requires hgsubversion library to be installed. Allows cloning remote SVN repositories and migrates them to Mercurial type.')}</span>
130 <span class="help-block">${_('Requires hgsubversion library to be installed. Allows cloning remote SVN repositories and migrates them to Mercurial type.')}</span>
131 </div>
131 </div>
132 % endif
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 </div>
146 </div>
134 </div>
147 </div>
135 ## LABS for HG
148 ## LABS for HG
136 % if c.labs_active:
149 % if c.labs_active:
137 <div class="panel panel-danger">
150 <div class="panel panel-danger">
138 <div class="panel-heading">
151 <div class="panel-heading">
139 <h3 class="panel-title">${_('Mercurial Labs Settings')} (${_('These features are considered experimental and may not work as expected.')})</h3>
152 <h3 class="panel-title">${_('Mercurial Labs Settings')} (${_('These features are considered experimental and may not work as expected.')})</h3>
140 </div>
153 </div>
141 <div class="panel-body">
154 <div class="panel-body">
142
155
143 <div class="checkbox">
156 <div class="checkbox">
144 ${h.checkbox('rhodecode_hg_use_rebase_for_merging' + suffix, 'True', **kwargs)}
157 ${h.checkbox('rhodecode_hg_use_rebase_for_merging' + suffix, 'True', **kwargs)}
145 <label for="rhodecode_hg_use_rebase_for_merging${suffix}">${_('Use rebase as merge strategy')}</label>
158 <label for="rhodecode_hg_use_rebase_for_merging${suffix}">${_('Use rebase as merge strategy')}</label>
146 </div>
159 </div>
147 <div class="label">
160 <div class="label">
148 <span class="help-block">${_('Use rebase instead of creating a merge commit when merging via web interface.')}</span>
161 <span class="help-block">${_('Use rebase instead of creating a merge commit when merging via web interface.')}</span>
149 </div>
162 </div>
150
163
151 </div>
164 </div>
152 </div>
165 </div>
153 % endif
166 % endif
154
167
155 % endif
168 % endif
156
169
157 % if display_globals or repo_type in ['git']:
170 % if display_globals or repo_type in ['git']:
158 <div class="panel panel-default">
171 <div class="panel panel-default">
159 <div class="panel-heading" id="vcs-git-options">
172 <div class="panel-heading" id="vcs-git-options">
160 <h3 class="panel-title">${_('Git Settings')}<a class="permalink" href="#vcs-git-options"> ΒΆ</a></h3>
173 <h3 class="panel-title">${_('Git Settings')}<a class="permalink" href="#vcs-git-options"> ΒΆ</a></h3>
161 </div>
174 </div>
162 <div class="panel-body">
175 <div class="panel-body">
163 <div class="checkbox">
176 <div class="checkbox">
164 ${h.checkbox('vcs_git_lfs_enabled' + suffix, 'True', **kwargs)}
177 ${h.checkbox('vcs_git_lfs_enabled' + suffix, 'True', **kwargs)}
165 <label for="vcs_git_lfs_enabled${suffix}">${_('Enable lfs extension')}</label>
178 <label for="vcs_git_lfs_enabled${suffix}">${_('Enable lfs extension')}</label>
166 </div>
179 </div>
167 <div class="label">
180 <div class="label">
168 % if display_globals:
181 % if display_globals:
169 <span class="help-block">${_('Enable lfs extensions for all repositories.')}</span>
182 <span class="help-block">${_('Enable lfs extensions for all repositories.')}</span>
170 % else:
183 % else:
171 <span class="help-block">${_('Enable lfs extensions for this repository.')}</span>
184 <span class="help-block">${_('Enable lfs extensions for this repository.')}</span>
172 % endif
185 % endif
173 </div>
186 </div>
174
187
175 % if display_globals:
188 % if display_globals:
176 <div class="field">
189 <div class="field">
177 <div class="input">
190 <div class="input">
178 ${h.text('vcs_git_lfs_store_location' + suffix, size=59)}
191 ${h.text('vcs_git_lfs_store_location' + suffix, size=59)}
179 </div>
192 </div>
180 </div>
193 </div>
181 <div class="label">
194 <div class="label">
182 <span class="help-block">${_('Filesystem location where Git lfs objects should be stored.')}</span>
195 <span class="help-block">${_('Filesystem location where Git lfs objects should be stored.')}</span>
183 </div>
196 </div>
184 % endif
197 % endif
185 </div>
198 </div>
186 </div>
199 </div>
187 % endif
200 % endif
188
201
189
202
190 % if display_globals:
203 % if display_globals:
191 <div class="panel panel-default">
204 <div class="panel panel-default">
192 <div class="panel-heading" id="vcs-global-svn-options">
205 <div class="panel-heading" id="vcs-global-svn-options">
193 <h3 class="panel-title">${_('Global Subversion Settings')}<a class="permalink" href="#vcs-global-svn-options"> ΒΆ</a></h3>
206 <h3 class="panel-title">${_('Global Subversion Settings')}<a class="permalink" href="#vcs-global-svn-options"> ΒΆ</a></h3>
194 </div>
207 </div>
195 <div class="panel-body">
208 <div class="panel-body">
196 <div class="field">
209 <div class="field">
197 <div class="checkbox">
210 <div class="checkbox">
198 ${h.checkbox('vcs_svn_proxy_http_requests_enabled' + suffix, 'True', **kwargs)}
211 ${h.checkbox('vcs_svn_proxy_http_requests_enabled' + suffix, 'True', **kwargs)}
199 <label for="vcs_svn_proxy_http_requests_enabled${suffix}">${_('Proxy subversion HTTP requests')}</label>
212 <label for="vcs_svn_proxy_http_requests_enabled${suffix}">${_('Proxy subversion HTTP requests')}</label>
200 </div>
213 </div>
201 <div class="label">
214 <div class="label">
202 <span class="help-block">
215 <span class="help-block">
203 ${_('Subversion HTTP Support. Enables communication with SVN over HTTP protocol.')}
216 ${_('Subversion HTTP Support. Enables communication with SVN over HTTP protocol.')}
204 <a href="${h.route_url('enterprise_svn_setup')}" target="_blank">${_('SVN Protocol setup Documentation')}</a>.
217 <a href="${h.route_url('enterprise_svn_setup')}" target="_blank">${_('SVN Protocol setup Documentation')}</a>.
205 </span>
218 </span>
206 </div>
219 </div>
207 </div>
220 </div>
208 <div class="field">
221 <div class="field">
209 <div class="label">
222 <div class="label">
210 <label for="vcs_svn_proxy_http_server_url">${_('Subversion HTTP Server URL')}</label><br/>
223 <label for="vcs_svn_proxy_http_server_url">${_('Subversion HTTP Server URL')}</label><br/>
211 </div>
224 </div>
212 <div class="input">
225 <div class="input">
213 ${h.text('vcs_svn_proxy_http_server_url',size=59)}
226 ${h.text('vcs_svn_proxy_http_server_url',size=59)}
214 % if c.svn_proxy_generate_config:
227 % if c.svn_proxy_generate_config:
215 <span class="buttons">
228 <span class="buttons">
216 <button class="btn btn-primary" id="vcs_svn_generate_cfg">${_('Generate Apache Config')}</button>
229 <button class="btn btn-primary" id="vcs_svn_generate_cfg">${_('Generate Apache Config')}</button>
217 </span>
230 </span>
218 % endif
231 % endif
219 </div>
232 </div>
220 </div>
233 </div>
221 </div>
234 </div>
222 </div>
235 </div>
223 % endif
236 % endif
224
237
225 % if display_globals or repo_type in ['svn']:
238 % if display_globals or repo_type in ['svn']:
226 <div class="panel panel-default">
239 <div class="panel panel-default">
227 <div class="panel-heading" id="vcs-svn-options">
240 <div class="panel-heading" id="vcs-svn-options">
228 <h3 class="panel-title">${_('Subversion Settings')}<a class="permalink" href="#vcs-svn-options"> ΒΆ</a></h3>
241 <h3 class="panel-title">${_('Subversion Settings')}<a class="permalink" href="#vcs-svn-options"> ΒΆ</a></h3>
229 </div>
242 </div>
230 <div class="panel-body">
243 <div class="panel-body">
231 <div class="field">
244 <div class="field">
232 <div class="content" >
245 <div class="content" >
233 <label>${_('Repository patterns')}</label><br/>
246 <label>${_('Repository patterns')}</label><br/>
234 </div>
247 </div>
235 </div>
248 </div>
236 <div class="label">
249 <div class="label">
237 <span class="help-block">${_('Patterns for identifying SVN branches and tags. For recursive search, use "*". Eg.: "/branches/*"')}</span>
250 <span class="help-block">${_('Patterns for identifying SVN branches and tags. For recursive search, use "*". Eg.: "/branches/*"')}</span>
238 </div>
251 </div>
239
252
240 <div class="field branch_patterns">
253 <div class="field branch_patterns">
241 <div class="input" >
254 <div class="input" >
242 <label>${_('Branches')}:</label><br/>
255 <label>${_('Branches')}:</label><br/>
243 </div>
256 </div>
244 % if svn_branch_patterns:
257 % if svn_branch_patterns:
245 % for branch in svn_branch_patterns:
258 % for branch in svn_branch_patterns:
246 <div class="input adjacent" id="${'id%s' % branch.ui_id}">
259 <div class="input adjacent" id="${'id%s' % branch.ui_id}">
247 ${h.hidden('branch_ui_key' + suffix, branch.ui_key)}
260 ${h.hidden('branch_ui_key' + suffix, branch.ui_key)}
248 ${h.text('branch_value_%d' % branch.ui_id + suffix, branch.ui_value, size=59, readonly="readonly", class_='disabled')}
261 ${h.text('branch_value_%d' % branch.ui_id + suffix, branch.ui_value, size=59, readonly="readonly", class_='disabled')}
249 % if kwargs.get('disabled') != 'disabled':
262 % if kwargs.get('disabled') != 'disabled':
250 <span class="btn btn-x" onclick="ajaxDeletePattern(${branch.ui_id},'${'id%s' % branch.ui_id}')">
263 <span class="btn btn-x" onclick="ajaxDeletePattern(${branch.ui_id},'${'id%s' % branch.ui_id}')">
251 ${_('Delete')}
264 ${_('Delete')}
252 </span>
265 </span>
253 % endif
266 % endif
254 </div>
267 </div>
255 % endfor
268 % endfor
256 %endif
269 %endif
257 </div>
270 </div>
258 % if kwargs.get('disabled') != 'disabled':
271 % if kwargs.get('disabled') != 'disabled':
259 <div class="field branch_patterns">
272 <div class="field branch_patterns">
260 <div class="input" >
273 <div class="input" >
261 ${h.text('new_svn_branch',size=59,placeholder='New branch pattern')}
274 ${h.text('new_svn_branch',size=59,placeholder='New branch pattern')}
262 </div>
275 </div>
263 </div>
276 </div>
264 % endif
277 % endif
265 <div class="field tag_patterns">
278 <div class="field tag_patterns">
266 <div class="input" >
279 <div class="input" >
267 <label>${_('Tags')}:</label><br/>
280 <label>${_('Tags')}:</label><br/>
268 </div>
281 </div>
269 % if svn_tag_patterns:
282 % if svn_tag_patterns:
270 % for tag in svn_tag_patterns:
283 % for tag in svn_tag_patterns:
271 <div class="input" id="${'id%s' % tag.ui_id + suffix}">
284 <div class="input" id="${'id%s' % tag.ui_id + suffix}">
272 ${h.hidden('tag_ui_key' + suffix, tag.ui_key)}
285 ${h.hidden('tag_ui_key' + suffix, tag.ui_key)}
273 ${h.text('tag_ui_value_new_%d' % tag.ui_id + suffix, tag.ui_value, size=59, readonly="readonly", class_='disabled tag_input')}
286 ${h.text('tag_ui_value_new_%d' % tag.ui_id + suffix, tag.ui_value, size=59, readonly="readonly", class_='disabled tag_input')}
274 % if kwargs.get('disabled') != 'disabled':
287 % if kwargs.get('disabled') != 'disabled':
275 <span class="btn btn-x" onclick="ajaxDeletePattern(${tag.ui_id},'${'id%s' % tag.ui_id}')">
288 <span class="btn btn-x" onclick="ajaxDeletePattern(${tag.ui_id},'${'id%s' % tag.ui_id}')">
276 ${_('Delete')}
289 ${_('Delete')}
277 </span>
290 </span>
278 %endif
291 %endif
279 </div>
292 </div>
280 % endfor
293 % endfor
281 % endif
294 % endif
282 </div>
295 </div>
283 % if kwargs.get('disabled') != 'disabled':
296 % if kwargs.get('disabled') != 'disabled':
284 <div class="field tag_patterns">
297 <div class="field tag_patterns">
285 <div class="input" >
298 <div class="input" >
286 ${h.text('new_svn_tag' + suffix, size=59, placeholder='New tag pattern')}
299 ${h.text('new_svn_tag' + suffix, size=59, placeholder='New tag pattern')}
287 </div>
300 </div>
288 </div>
301 </div>
289 %endif
302 %endif
290 </div>
303 </div>
291 </div>
304 </div>
292 % else:
305 % else:
293 ${h.hidden('new_svn_branch' + suffix, '')}
306 ${h.hidden('new_svn_branch' + suffix, '')}
294 ${h.hidden('new_svn_tag' + suffix, '')}
307 ${h.hidden('new_svn_tag' + suffix, '')}
295 % endif
308 % endif
296
309
297
310
298 % if display_globals or repo_type in ['hg', 'git']:
311 % if display_globals or repo_type in ['hg', 'git']:
299 <div class="panel panel-default">
312 <div class="panel panel-default">
300 <div class="panel-heading" id="vcs-pull-requests-options">
313 <div class="panel-heading" id="vcs-pull-requests-options">
301 <h3 class="panel-title">${_('Pull Request Settings')}<a class="permalink" href="#vcs-pull-requests-options"> ΒΆ</a></h3>
314 <h3 class="panel-title">${_('Pull Request Settings')}<a class="permalink" href="#vcs-pull-requests-options"> ΒΆ</a></h3>
302 </div>
315 </div>
303 <div class="panel-body">
316 <div class="panel-body">
304 <div class="checkbox">
317 <div class="checkbox">
305 ${h.checkbox('rhodecode_pr_merge_enabled' + suffix, 'True', **kwargs)}
318 ${h.checkbox('rhodecode_pr_merge_enabled' + suffix, 'True', **kwargs)}
306 <label for="rhodecode_pr_merge_enabled${suffix}">${_('Enable server-side merge for pull requests')}</label>
319 <label for="rhodecode_pr_merge_enabled${suffix}">${_('Enable server-side merge for pull requests')}</label>
307 </div>
320 </div>
308 <div class="label">
321 <div class="label">
309 <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>
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 </div>
323 </div>
311 <div class="checkbox">
324 <div class="checkbox">
312 ${h.checkbox('rhodecode_use_outdated_comments' + suffix, 'True', **kwargs)}
325 ${h.checkbox('rhodecode_use_outdated_comments' + suffix, 'True', **kwargs)}
313 <label for="rhodecode_use_outdated_comments${suffix}">${_('Invalidate and relocate inline comments during update')}</label>
326 <label for="rhodecode_use_outdated_comments${suffix}">${_('Invalidate and relocate inline comments during update')}</label>
314 </div>
327 </div>
315 <div class="label">
328 <div class="label">
316 <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>
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 </div>
330 </div>
318 </div>
331 </div>
319 </div>
332 </div>
320 % endif
333 % endif
321
334
322 </%def>
335 </%def>
@@ -1,127 +1,142 b''
1 ## small box that displays changed/added/removed details fetched by AJAX
1 ## small box that displays changed/added/removed details fetched by AJAX
2 <%namespace name="base" file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
3
3
4
4
5 % if c.prev_page:
5 % if c.prev_page:
6 <tr>
6 <tr>
7 <td colspan="9" class="load-more-commits">
7 <td colspan="9" class="load-more-commits">
8 <a class="prev-commits" href="#loadPrevCommits" onclick="commitsController.loadPrev(this, ${c.prev_page}, '${c.branch_name}');return false">
8 <a class="prev-commits" href="#loadPrevCommits" onclick="commitsController.loadPrev(this, ${c.prev_page}, '${c.branch_name}');return false">
9 ${_('load previous')}
9 ${_('load previous')}
10 </a>
10 </a>
11 </td>
11 </td>
12 </tr>
12 </tr>
13 % endif
13 % endif
14
14
15 % for cnt,commit in enumerate(c.pagination):
15 % for cnt,commit in enumerate(c.pagination):
16 <tr id="sha_${commit.raw_id}" class="changelogRow container ${'tablerow%s' % (cnt%2)}">
16 <tr id="sha_${commit.raw_id}" class="changelogRow container ${'tablerow%s' % (cnt%2)}">
17
17
18 <td class="td-checkbox">
18 <td class="td-checkbox">
19 ${h.checkbox(commit.raw_id,class_="commit-range")}
19 ${h.checkbox(commit.raw_id,class_="commit-range")}
20 </td>
20 </td>
21 <td class="td-status">
21 <td class="td-status">
22
22
23 %if c.statuses.get(commit.raw_id):
23 %if c.statuses.get(commit.raw_id):
24 <div class="changeset-status-ico">
24 <div class="changeset-status-ico">
25 %if c.statuses.get(commit.raw_id)[2]:
25 %if c.statuses.get(commit.raw_id)[2]:
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])}">
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 <div class="${'flag_status %s' % c.statuses.get(commit.raw_id)[0]}"></div>
27 <div class="${'flag_status %s' % c.statuses.get(commit.raw_id)[0]}"></div>
28 </a>
28 </a>
29 %else:
29 %else:
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)}">
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 <div class="${'flag_status %s' % c.statuses.get(commit.raw_id)[0]}"></div>
31 <div class="${'flag_status %s' % c.statuses.get(commit.raw_id)[0]}"></div>
32 </a>
32 </a>
33 %endif
33 %endif
34 </div>
34 </div>
35 %else:
35 %else:
36 <div class="tooltip flag_status not_reviewed" title="${_('Commit status: Not Reviewed')}"></div>
36 <div class="tooltip flag_status not_reviewed" title="${_('Commit status: Not Reviewed')}"></div>
37 %endif
37 %endif
38 </td>
38 </td>
39 <td class="td-comments comments-col">
39 <td class="td-comments comments-col">
40 %if c.comments.get(commit.raw_id):
40 %if c.comments.get(commit.raw_id):
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)}">
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 <i class="icon-comment"></i> ${len(c.comments[commit.raw_id])}
42 <i class="icon-comment"></i> ${len(c.comments[commit.raw_id])}
43 </a>
43 </a>
44 %endif
44 %endif
45 </td>
45 </td>
46 <td class="td-hash">
46 <td class="td-hash">
47 <code>
47 <code>
48 <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id)}">
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 </a>
50 </a>
51 % if hasattr(commit, 'phase'):
51 % if hasattr(commit, 'phase'):
52 % if commit.phase != 'public':
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 % endif
54 % endif
55 % endif
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 </code>
71 </code>
57 </td>
72 </td>
58 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_('Expand commit message')}" onclick="commitsController.expandCommit(this); return false">
73 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_('Expand commit message')}" onclick="commitsController.expandCommit(this); return false">
59 <div class="show_more_col">
74 <div class="show_more_col">
60 <i class="show_more"></i>&nbsp;
75 <i class="show_more"></i>&nbsp;
61 </div>
76 </div>
62 </td>
77 </td>
63 <td class="td-description mid">
78 <td class="td-description mid">
64 <div class="log-container truncate-wrap">
79 <div class="log-container truncate-wrap">
65 <div class="message truncate" id="c-${commit.raw_id}">${h.urlify_commit_message(commit.message, c.repo_name)}</div>
80 <div class="message truncate" id="c-${commit.raw_id}">${h.urlify_commit_message(commit.message, c.repo_name)}</div>
66 </div>
81 </div>
67 </td>
82 </td>
68
83
69 <td class="td-time">
84 <td class="td-time">
70 ${h.age_component(commit.date)}
85 ${h.age_component(commit.date)}
71 </td>
86 </td>
72 <td class="td-user">
87 <td class="td-user">
73 ${base.gravatar_with_user(commit.author)}
88 ${base.gravatar_with_user(commit.author)}
74 </td>
89 </td>
75
90
76 <td class="td-tags tags-col">
91 <td class="td-tags tags-col">
77 <div id="t-${commit.raw_id}">
92 <div id="t-${commit.raw_id}">
78
93
79 ## merge
94 ## merge
80 %if commit.merge:
95 %if commit.merge:
81 <span class="tag mergetag">
96 <span class="tag mergetag">
82 <i class="icon-merge"></i>${_('merge')}
97 <i class="icon-merge"></i>${_('merge')}
83 </span>
98 </span>
84 %endif
99 %endif
85
100
86 ## branch
101 ## branch
87 %if commit.branch:
102 %if commit.branch:
88 <span class="tag branchtag" title="${_('Branch %s') % commit.branch}">
103 <span class="tag branchtag" title="${_('Branch %s') % commit.branch}">
89 <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>
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 </span>
105 </span>
91 %endif
106 %endif
92
107
93 ## bookmarks
108 ## bookmarks
94 %if h.is_hg(c.rhodecode_repo):
109 %if h.is_hg(c.rhodecode_repo):
95 %for book in commit.bookmarks:
110 %for book in commit.bookmarks:
96 <span class="tag booktag" title="${_('Bookmark %s') % book}">
111 <span class="tag booktag" title="${_('Bookmark %s') % book}">
97 <a href="${h.url('files_home',repo_name=c.repo_name,revision=commit.raw_id)}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
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 </span>
113 </span>
99 %endfor
114 %endfor
100 %endif
115 %endif
101
116
102 ## tags
117 ## tags
103 %for tag in commit.tags:
118 %for tag in commit.tags:
104 <span class="tag tagtag" title="${_('Tag %s') % tag}">
119 <span class="tag tagtag" title="${_('Tag %s') % tag}">
105 <a href="${h.url('files_home',repo_name=c.repo_name,revision=commit.raw_id)}"><i class="icon-tag"></i>${h.shorter(tag)}</a>
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 </span>
121 </span>
107 %endfor
122 %endfor
108
123
109 </div>
124 </div>
110 </td>
125 </td>
111 </tr>
126 </tr>
112 % endfor
127 % endfor
113
128
114 % if c.next_page:
129 % if c.next_page:
115 <tr>
130 <tr>
116 <td colspan="9" class="load-more-commits">
131 <td colspan="9" class="load-more-commits">
117 <a class="next-commits" href="#loadNextCommits" onclick="commitsController.loadNext(this, ${c.next_page}, '${c.branch_name}');return false">
132 <a class="next-commits" href="#loadNextCommits" onclick="commitsController.loadNext(this, ${c.next_page}, '${c.branch_name}');return false">
118 ${_('load next')}
133 ${_('load next')}
119 </a>
134 </a>
120 </td>
135 </td>
121 </tr>
136 </tr>
122 % endif
137 % endif
123 <tr class="chunk-graph-data" style="display:none"
138 <tr class="chunk-graph-data" style="display:none"
124 data-graph='${c.graph_data|n}'
139 data-graph='${c.graph_data|n}'
125 data-node='${c.prev_page}:${c.next_page}'
140 data-node='${c.prev_page}:${c.next_page}'
126 data-commits='${c.graph_commits|n}'>
141 data-commits='${c.graph_commits|n}'>
127 </tr> No newline at end of file
142 </tr>
@@ -1,336 +1,351 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2
2
3 <%inherit file="/base/base.mako"/>
3 <%inherit file="/base/base.mako"/>
4 <%namespace name="diff_block" file="/changeset/diff_block.mako"/>
4 <%namespace name="diff_block" file="/changeset/diff_block.mako"/>
5
5
6 <%def name="title()">
6 <%def name="title()">
7 ${_('%s Commit') % c.repo_name} - ${h.show_id(c.commit)}
7 ${_('%s Commit') % c.repo_name} - ${h.show_id(c.commit)}
8 %if c.rhodecode_name:
8 %if c.rhodecode_name:
9 &middot; ${h.branding(c.rhodecode_name)}
9 &middot; ${h.branding(c.rhodecode_name)}
10 %endif
10 %endif
11 </%def>
11 </%def>
12
12
13 <%def name="menu_bar_nav()">
13 <%def name="menu_bar_nav()">
14 ${self.menu_items(active='repositories')}
14 ${self.menu_items(active='repositories')}
15 </%def>
15 </%def>
16
16
17 <%def name="menu_bar_subnav()">
17 <%def name="menu_bar_subnav()">
18 ${self.repo_menu(active='changelog')}
18 ${self.repo_menu(active='changelog')}
19 </%def>
19 </%def>
20
20
21 <%def name="main()">
21 <%def name="main()">
22 <script>
22 <script>
23 // TODO: marcink switch this to pyroutes
23 // TODO: marcink switch this to pyroutes
24 AJAX_COMMENT_DELETE_URL = "${url('changeset_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
24 AJAX_COMMENT_DELETE_URL = "${url('changeset_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
25 templateContext.commit_data.commit_id = "${c.commit.raw_id}";
25 templateContext.commit_data.commit_id = "${c.commit.raw_id}";
26 </script>
26 </script>
27 <div class="box">
27 <div class="box">
28 <div class="title">
28 <div class="title">
29 ${self.repo_page_title(c.rhodecode_db_repo)}
29 ${self.repo_page_title(c.rhodecode_db_repo)}
30 </div>
30 </div>
31
31
32 <div id="changeset_compare_view_content" class="summary changeset">
32 <div id="changeset_compare_view_content" class="summary changeset">
33 <div class="summary-detail">
33 <div class="summary-detail">
34 <div class="summary-detail-header">
34 <div class="summary-detail-header">
35 <span class="breadcrumbs files_location">
35 <span class="breadcrumbs files_location">
36 <h4>${_('Commit')}
36 <h4>${_('Commit')}
37 <code>
37 <code>
38 ${h.show_id(c.commit)}
38 ${h.show_id(c.commit)}
39 % if hasattr(c.commit, 'phase'):
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 % endif
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 </code>
57 </code>
43 </h4>
58 </h4>
44 </span>
59 </span>
45 <div class="pull-right">
60 <div class="pull-right">
46 <span id="parent_link">
61 <span id="parent_link">
47 <a href="#" title="${_('Parent Commit')}">${_('Parent')}</a>
62 <a href="#" title="${_('Parent Commit')}">${_('Parent')}</a>
48 </span>
63 </span>
49 |
64 |
50 <span id="child_link">
65 <span id="child_link">
51 <a href="#" title="${_('Child Commit')}">${_('Child')}</a>
66 <a href="#" title="${_('Child Commit')}">${_('Child')}</a>
52 </span>
67 </span>
53 </div>
68 </div>
54 </div>
69 </div>
55
70
56 <div class="fieldset">
71 <div class="fieldset">
57 <div class="left-label">
72 <div class="left-label">
58 ${_('Description')}:
73 ${_('Description')}:
59 </div>
74 </div>
60 <div class="right-content">
75 <div class="right-content">
61 <div id="trimmed_message_box" class="commit">${h.urlify_commit_message(c.commit.message,c.repo_name)}</div>
76 <div id="trimmed_message_box" class="commit">${h.urlify_commit_message(c.commit.message,c.repo_name)}</div>
62 <div id="message_expand" style="display:none;">
77 <div id="message_expand" style="display:none;">
63 ${_('Expand')}
78 ${_('Expand')}
64 </div>
79 </div>
65 </div>
80 </div>
66 </div>
81 </div>
67
82
68 %if c.statuses:
83 %if c.statuses:
69 <div class="fieldset">
84 <div class="fieldset">
70 <div class="left-label">
85 <div class="left-label">
71 ${_('Commit status')}:
86 ${_('Commit status')}:
72 </div>
87 </div>
73 <div class="right-content">
88 <div class="right-content">
74 <div class="changeset-status-ico">
89 <div class="changeset-status-ico">
75 <div class="${'flag_status %s' % c.statuses[0]} pull-left"></div>
90 <div class="${'flag_status %s' % c.statuses[0]} pull-left"></div>
76 </div>
91 </div>
77 <div title="${_('Commit status')}" class="changeset-status-lbl">[${h.commit_status_lbl(c.statuses[0])}]</div>
92 <div title="${_('Commit status')}" class="changeset-status-lbl">[${h.commit_status_lbl(c.statuses[0])}]</div>
78 </div>
93 </div>
79 </div>
94 </div>
80 %endif
95 %endif
81
96
82 <div class="fieldset">
97 <div class="fieldset">
83 <div class="left-label">
98 <div class="left-label">
84 ${_('References')}:
99 ${_('References')}:
85 </div>
100 </div>
86 <div class="right-content">
101 <div class="right-content">
87 <div class="tags">
102 <div class="tags">
88
103
89 %if c.commit.merge:
104 %if c.commit.merge:
90 <span class="mergetag tag">
105 <span class="mergetag tag">
91 <i class="icon-merge"></i>${_('merge')}
106 <i class="icon-merge"></i>${_('merge')}
92 </span>
107 </span>
93 %endif
108 %endif
94
109
95 %if h.is_hg(c.rhodecode_repo):
110 %if h.is_hg(c.rhodecode_repo):
96 %for book in c.commit.bookmarks:
111 %for book in c.commit.bookmarks:
97 <span class="booktag tag" title="${_('Bookmark %s') % book}">
112 <span class="booktag tag" title="${_('Bookmark %s') % book}">
98 <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>
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 </span>
114 </span>
100 %endfor
115 %endfor
101 %endif
116 %endif
102
117
103 %for tag in c.commit.tags:
118 %for tag in c.commit.tags:
104 <span class="tagtag tag" title="${_('Tag %s') % tag}">
119 <span class="tagtag tag" title="${_('Tag %s') % tag}">
105 <a href="${h.url('files_home',repo_name=c.repo_name,revision=c.commit.raw_id)}"><i class="icon-tag"></i>${tag}</a>
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 </span>
121 </span>
107 %endfor
122 %endfor
108
123
109 %if c.commit.branch:
124 %if c.commit.branch:
110 <span class="branchtag tag" title="${_('Branch %s') % c.commit.branch}">
125 <span class="branchtag tag" title="${_('Branch %s') % c.commit.branch}">
111 <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>
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 </span>
127 </span>
113 %endif
128 %endif
114 </div>
129 </div>
115 </div>
130 </div>
116 </div>
131 </div>
117
132
118 <div class="fieldset">
133 <div class="fieldset">
119 <div class="left-label">
134 <div class="left-label">
120 ${_('Diff options')}:
135 ${_('Diff options')}:
121 </div>
136 </div>
122 <div class="right-content">
137 <div class="right-content">
123 <div class="diff-actions">
138 <div class="diff-actions">
124 <a href="${h.url('changeset_raw_home',repo_name=c.repo_name,revision=c.commit.raw_id)}" class="tooltip" title="${h.tooltip(_('Raw diff'))}">
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 ${_('Raw Diff')}
140 ${_('Raw Diff')}
126 </a>
141 </a>
127 |
142 |
128 <a href="${h.url('changeset_patch_home',repo_name=c.repo_name,revision=c.commit.raw_id)}" class="tooltip" title="${h.tooltip(_('Patch diff'))}">
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 ${_('Patch Diff')}
144 ${_('Patch Diff')}
130 </a>
145 </a>
131 |
146 |
132 <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'))}">
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 ${_('Download Diff')}
148 ${_('Download Diff')}
134 </a>
149 </a>
135 |
150 |
136 ${c.ignorews_url(request.GET)}
151 ${c.ignorews_url(request.GET)}
137 |
152 |
138 ${c.context_url(request.GET)}
153 ${c.context_url(request.GET)}
139 </div>
154 </div>
140 </div>
155 </div>
141 </div>
156 </div>
142
157
143 <div class="fieldset">
158 <div class="fieldset">
144 <div class="left-label">
159 <div class="left-label">
145 ${_('Comments')}:
160 ${_('Comments')}:
146 </div>
161 </div>
147 <div class="right-content">
162 <div class="right-content">
148 <div class="comments-number">
163 <div class="comments-number">
149 %if c.comments:
164 %if c.comments:
150 <a href="#comments">${ungettext("%d Commit comment", "%d Commit comments", len(c.comments)) % len(c.comments)}</a>,
165 <a href="#comments">${ungettext("%d Commit comment", "%d Commit comments", len(c.comments)) % len(c.comments)}</a>,
151 %else:
166 %else:
152 ${ungettext("%d Commit comment", "%d Commit comments", len(c.comments)) % len(c.comments)}
167 ${ungettext("%d Commit comment", "%d Commit comments", len(c.comments)) % len(c.comments)}
153 %endif
168 %endif
154 %if c.inline_cnt:
169 %if c.inline_cnt:
155 <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>
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 %else:
171 %else:
157 ${ungettext("%d Inline Comment", "%d Inline Comments", c.inline_cnt) % c.inline_cnt}
172 ${ungettext("%d Inline Comment", "%d Inline Comments", c.inline_cnt) % c.inline_cnt}
158 %endif
173 %endif
159 </div>
174 </div>
160 </div>
175 </div>
161 </div>
176 </div>
162
177
163 <div class="fieldset">
178 <div class="fieldset">
164 <div class="left-label">
179 <div class="left-label">
165 ${_('Unresolved TODOs')}:
180 ${_('Unresolved TODOs')}:
166 </div>
181 </div>
167 <div class="right-content">
182 <div class="right-content">
168 <div class="comments-number">
183 <div class="comments-number">
169 % if c.unresolved_comments:
184 % if c.unresolved_comments:
170 % for co in c.unresolved_comments:
185 % for co in c.unresolved_comments:
171 <a class="permalink" href="#comment-${co.comment_id}" onclick="Rhodecode.comments.scrollToComment($('#comment-${co.comment_id}'))"> #${co.comment_id}</a>${'' if loop.last else ','}
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 % endfor
187 % endfor
173 % else:
188 % else:
174 ${_('There are no unresolved TODOs')}
189 ${_('There are no unresolved TODOs')}
175 % endif
190 % endif
176 </div>
191 </div>
177 </div>
192 </div>
178 </div>
193 </div>
179
194
180 </div> <!-- end summary-detail -->
195 </div> <!-- end summary-detail -->
181
196
182 <div id="commit-stats" class="sidebar-right">
197 <div id="commit-stats" class="sidebar-right">
183 <div class="summary-detail-header">
198 <div class="summary-detail-header">
184 <h4 class="item">
199 <h4 class="item">
185 ${_('Author')}
200 ${_('Author')}
186 </h4>
201 </h4>
187 </div>
202 </div>
188 <div class="sidebar-right-content">
203 <div class="sidebar-right-content">
189 ${self.gravatar_with_user(c.commit.author)}
204 ${self.gravatar_with_user(c.commit.author)}
190 <div class="user-inline-data">- ${h.age_component(c.commit.date)}</div>
205 <div class="user-inline-data">- ${h.age_component(c.commit.date)}</div>
191 </div>
206 </div>
192 </div><!-- end sidebar -->
207 </div><!-- end sidebar -->
193 </div> <!-- end summary -->
208 </div> <!-- end summary -->
194 <div class="cs_files">
209 <div class="cs_files">
195 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
210 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
196 ${cbdiffs.render_diffset_menu()}
211 ${cbdiffs.render_diffset_menu()}
197 ${cbdiffs.render_diffset(
212 ${cbdiffs.render_diffset(
198 c.changes[c.commit.raw_id], commit=c.commit, use_comments=True)}
213 c.changes[c.commit.raw_id], commit=c.commit, use_comments=True)}
199 </div>
214 </div>
200
215
201 ## template for inline comment form
216 ## template for inline comment form
202 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
217 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
203
218
204 ## render comments
219 ## render comments
205 ${comment.generate_comments(c.comments)}
220 ${comment.generate_comments(c.comments)}
206
221
207 ## main comment form and it status
222 ## main comment form and it status
208 ${comment.comments(h.url('changeset_comment', repo_name=c.repo_name, revision=c.commit.raw_id),
223 ${comment.comments(h.url('changeset_comment', repo_name=c.repo_name, revision=c.commit.raw_id),
209 h.commit_status(c.rhodecode_db_repo, c.commit.raw_id))}
224 h.commit_status(c.rhodecode_db_repo, c.commit.raw_id))}
210 </div>
225 </div>
211
226
212 ## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS
227 ## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS
213 <script type="text/javascript">
228 <script type="text/javascript">
214
229
215 $(document).ready(function() {
230 $(document).ready(function() {
216
231
217 var boxmax = parseInt($('#trimmed_message_box').css('max-height'), 10);
232 var boxmax = parseInt($('#trimmed_message_box').css('max-height'), 10);
218 if($('#trimmed_message_box').height() === boxmax){
233 if($('#trimmed_message_box').height() === boxmax){
219 $('#message_expand').show();
234 $('#message_expand').show();
220 }
235 }
221
236
222 $('#message_expand').on('click', function(e){
237 $('#message_expand').on('click', function(e){
223 $('#trimmed_message_box').css('max-height', 'none');
238 $('#trimmed_message_box').css('max-height', 'none');
224 $(this).hide();
239 $(this).hide();
225 });
240 });
226
241
227 $('.show-inline-comments').on('click', function(e){
242 $('.show-inline-comments').on('click', function(e){
228 var boxid = $(this).attr('data-comment-id');
243 var boxid = $(this).attr('data-comment-id');
229 var button = $(this);
244 var button = $(this);
230
245
231 if(button.hasClass("comments-visible")) {
246 if(button.hasClass("comments-visible")) {
232 $('#{0} .inline-comments'.format(boxid)).each(function(index){
247 $('#{0} .inline-comments'.format(boxid)).each(function(index){
233 $(this).hide();
248 $(this).hide();
234 });
249 });
235 button.removeClass("comments-visible");
250 button.removeClass("comments-visible");
236 } else {
251 } else {
237 $('#{0} .inline-comments'.format(boxid)).each(function(index){
252 $('#{0} .inline-comments'.format(boxid)).each(function(index){
238 $(this).show();
253 $(this).show();
239 });
254 });
240 button.addClass("comments-visible");
255 button.addClass("comments-visible");
241 }
256 }
242 });
257 });
243
258
244
259
245 // next links
260 // next links
246 $('#child_link').on('click', function(e){
261 $('#child_link').on('click', function(e){
247 // fetch via ajax what is going to be the next link, if we have
262 // fetch via ajax what is going to be the next link, if we have
248 // >1 links show them to user to choose
263 // >1 links show them to user to choose
249 if(!$('#child_link').hasClass('disabled')){
264 if(!$('#child_link').hasClass('disabled')){
250 $.ajax({
265 $.ajax({
251 url: '${h.url('changeset_children',repo_name=c.repo_name, revision=c.commit.raw_id)}',
266 url: '${h.url('changeset_children',repo_name=c.repo_name, revision=c.commit.raw_id)}',
252 success: function(data) {
267 success: function(data) {
253 if(data.results.length === 0){
268 if(data.results.length === 0){
254 $('#child_link').html("${_('No Child Commits')}").addClass('disabled');
269 $('#child_link').html("${_('No Child Commits')}").addClass('disabled');
255 }
270 }
256 if(data.results.length === 1){
271 if(data.results.length === 1){
257 var commit = data.results[0];
272 var commit = data.results[0];
258 window.location = pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': commit.raw_id});
273 window.location = pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': commit.raw_id});
259 }
274 }
260 else if(data.results.length === 2){
275 else if(data.results.length === 2){
261 $('#child_link').addClass('disabled');
276 $('#child_link').addClass('disabled');
262 $('#child_link').addClass('double');
277 $('#child_link').addClass('double');
263 var _html = '';
278 var _html = '';
264 _html +='<a title="__title__" href="__url__">__rev__</a> '
279 _html +='<a title="__title__" href="__url__">__rev__</a> '
265 .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6)))
280 .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6)))
266 .replace('__title__', data.results[0].message)
281 .replace('__title__', data.results[0].message)
267 .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[0].raw_id}));
282 .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[0].raw_id}));
268 _html +=' | ';
283 _html +=' | ';
269 _html +='<a title="__title__" href="__url__">__rev__</a> '
284 _html +='<a title="__title__" href="__url__">__rev__</a> '
270 .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
285 .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
271 .replace('__title__', data.results[1].message)
286 .replace('__title__', data.results[1].message)
272 .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[1].raw_id}));
287 .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[1].raw_id}));
273 $('#child_link').html(_html);
288 $('#child_link').html(_html);
274 }
289 }
275 }
290 }
276 });
291 });
277 e.preventDefault();
292 e.preventDefault();
278 }
293 }
279 });
294 });
280
295
281 // prev links
296 // prev links
282 $('#parent_link').on('click', function(e){
297 $('#parent_link').on('click', function(e){
283 // fetch via ajax what is going to be the next link, if we have
298 // fetch via ajax what is going to be the next link, if we have
284 // >1 links show them to user to choose
299 // >1 links show them to user to choose
285 if(!$('#parent_link').hasClass('disabled')){
300 if(!$('#parent_link').hasClass('disabled')){
286 $.ajax({
301 $.ajax({
287 url: '${h.url("changeset_parents",repo_name=c.repo_name, revision=c.commit.raw_id)}',
302 url: '${h.url("changeset_parents",repo_name=c.repo_name, revision=c.commit.raw_id)}',
288 success: function(data) {
303 success: function(data) {
289 if(data.results.length === 0){
304 if(data.results.length === 0){
290 $('#parent_link').html('${_('No Parent Commits')}').addClass('disabled');
305 $('#parent_link').html('${_('No Parent Commits')}').addClass('disabled');
291 }
306 }
292 if(data.results.length === 1){
307 if(data.results.length === 1){
293 var commit = data.results[0];
308 var commit = data.results[0];
294 window.location = pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': commit.raw_id});
309 window.location = pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': commit.raw_id});
295 }
310 }
296 else if(data.results.length === 2){
311 else if(data.results.length === 2){
297 $('#parent_link').addClass('disabled');
312 $('#parent_link').addClass('disabled');
298 $('#parent_link').addClass('double');
313 $('#parent_link').addClass('double');
299 var _html = '';
314 var _html = '';
300 _html +='<a title="__title__" href="__url__">Parent __rev__</a>'
315 _html +='<a title="__title__" href="__url__">Parent __rev__</a>'
301 .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6)))
316 .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6)))
302 .replace('__title__', data.results[0].message)
317 .replace('__title__', data.results[0].message)
303 .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[0].raw_id}));
318 .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[0].raw_id}));
304 _html +=' | ';
319 _html +=' | ';
305 _html +='<a title="__title__" href="__url__">Parent __rev__</a>'
320 _html +='<a title="__title__" href="__url__">Parent __rev__</a>'
306 .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
321 .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
307 .replace('__title__', data.results[1].message)
322 .replace('__title__', data.results[1].message)
308 .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[1].raw_id}));
323 .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[1].raw_id}));
309 $('#parent_link').html(_html);
324 $('#parent_link').html(_html);
310 }
325 }
311 }
326 }
312 });
327 });
313 e.preventDefault();
328 e.preventDefault();
314 }
329 }
315 });
330 });
316
331
317 if (location.hash) {
332 if (location.hash) {
318 var result = splitDelimitedHash(location.hash);
333 var result = splitDelimitedHash(location.hash);
319 var line = $('html').find(result.loc);
334 var line = $('html').find(result.loc);
320 if (line.length > 0){
335 if (line.length > 0){
321 offsetScroll(line, 70);
336 offsetScroll(line, 70);
322 }
337 }
323 }
338 }
324
339
325 // browse tree @ revision
340 // browse tree @ revision
326 $('#files_link').on('click', function(e){
341 $('#files_link').on('click', function(e){
327 window.location = '${h.url('files_home',repo_name=c.repo_name, revision=c.commit.raw_id, f_path='')}';
342 window.location = '${h.url('files_home',repo_name=c.repo_name, revision=c.commit.raw_id, f_path='')}';
328 e.preventDefault();
343 e.preventDefault();
329 });
344 });
330
345
331 // inject comments into their proper positions
346 // inject comments into their proper positions
332 var file_comments = $('.inline-comment-placeholder');
347 var file_comments = $('.inline-comment-placeholder');
333 })
348 })
334 </script>
349 </script>
335
350
336 </%def>
351 </%def>
@@ -1,1207 +1,1208 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import urllib
21 import urllib
22
22
23 import mock
23 import mock
24 import pytest
24 import pytest
25
25
26 from rhodecode.lib import auth
26 from rhodecode.lib import auth
27 from rhodecode.lib.utils2 import safe_str, str2bool, safe_unicode
27 from rhodecode.lib.utils2 import safe_str, str2bool, safe_unicode
28 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
28 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
29 from rhodecode.model.db import Repository, RepoGroup, UserRepoToPerm, User,\
29 from rhodecode.model.db import Repository, RepoGroup, UserRepoToPerm, User,\
30 Permission
30 Permission
31 from rhodecode.model.meta import Session
31 from rhodecode.model.meta import Session
32 from rhodecode.model.repo import RepoModel
32 from rhodecode.model.repo import RepoModel
33 from rhodecode.model.repo_group import RepoGroupModel
33 from rhodecode.model.repo_group import RepoGroupModel
34 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
34 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
35 from rhodecode.model.user import UserModel
35 from rhodecode.model.user import UserModel
36 from rhodecode.tests import (
36 from rhodecode.tests import (
37 login_user_session, url, assert_session_flash, TEST_USER_ADMIN_LOGIN,
37 login_user_session, url, assert_session_flash, TEST_USER_ADMIN_LOGIN,
38 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, HG_REPO, GIT_REPO,
38 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, HG_REPO, GIT_REPO,
39 logout_user_session)
39 logout_user_session)
40 from rhodecode.tests.fixture import Fixture, error_function
40 from rhodecode.tests.fixture import Fixture, error_function
41 from rhodecode.tests.utils import AssertResponse, repo_on_filesystem
41 from rhodecode.tests.utils import AssertResponse, repo_on_filesystem
42
42
43 fixture = Fixture()
43 fixture = Fixture()
44
44
45
45
46 @pytest.mark.usefixtures("app")
46 @pytest.mark.usefixtures("app")
47 class TestAdminRepos(object):
47 class TestAdminRepos(object):
48
48
49 def test_index(self):
49 def test_index(self):
50 self.app.get(url('repos'))
50 self.app.get(url('repos'))
51
51
52 def test_create_page_restricted(self, autologin_user, backend):
52 def test_create_page_restricted(self, autologin_user, backend):
53 with mock.patch('rhodecode.BACKENDS', {'git': 'git'}):
53 with mock.patch('rhodecode.BACKENDS', {'git': 'git'}):
54 response = self.app.get(url('new_repo'), status=200)
54 response = self.app.get(url('new_repo'), status=200)
55 assert_response = AssertResponse(response)
55 assert_response = AssertResponse(response)
56 element = assert_response.get_element('#repo_type')
56 element = assert_response.get_element('#repo_type')
57 assert element.text_content() == '\ngit\n'
57 assert element.text_content() == '\ngit\n'
58
58
59 def test_create_page_non_restricted(self, autologin_user, backend):
59 def test_create_page_non_restricted(self, autologin_user, backend):
60 response = self.app.get(url('new_repo'), status=200)
60 response = self.app.get(url('new_repo'), status=200)
61 assert_response = AssertResponse(response)
61 assert_response = AssertResponse(response)
62 assert_response.element_contains('#repo_type', 'git')
62 assert_response.element_contains('#repo_type', 'git')
63 assert_response.element_contains('#repo_type', 'svn')
63 assert_response.element_contains('#repo_type', 'svn')
64 assert_response.element_contains('#repo_type', 'hg')
64 assert_response.element_contains('#repo_type', 'hg')
65
65
66 @pytest.mark.parametrize("suffix",
66 @pytest.mark.parametrize("suffix",
67 [u'', u'xxa'], ids=['', 'non-ascii'])
67 [u'', u'xxa'], ids=['', 'non-ascii'])
68 def test_create(self, autologin_user, backend, suffix, csrf_token):
68 def test_create(self, autologin_user, backend, suffix, csrf_token):
69 repo_name_unicode = backend.new_repo_name(suffix=suffix)
69 repo_name_unicode = backend.new_repo_name(suffix=suffix)
70 repo_name = repo_name_unicode.encode('utf8')
70 repo_name = repo_name_unicode.encode('utf8')
71 description_unicode = u'description for newly created repo' + suffix
71 description_unicode = u'description for newly created repo' + suffix
72 description = description_unicode.encode('utf8')
72 description = description_unicode.encode('utf8')
73 response = self.app.post(
73 response = self.app.post(
74 url('repos'),
74 url('repos'),
75 fixture._get_repo_create_params(
75 fixture._get_repo_create_params(
76 repo_private=False,
76 repo_private=False,
77 repo_name=repo_name,
77 repo_name=repo_name,
78 repo_type=backend.alias,
78 repo_type=backend.alias,
79 repo_description=description,
79 repo_description=description,
80 csrf_token=csrf_token),
80 csrf_token=csrf_token),
81 status=302)
81 status=302)
82
82
83 self.assert_repository_is_created_correctly(
83 self.assert_repository_is_created_correctly(
84 repo_name, description, backend)
84 repo_name, description, backend)
85
85
86 def test_create_numeric(self, autologin_user, backend, csrf_token):
86 def test_create_numeric(self, autologin_user, backend, csrf_token):
87 numeric_repo = '1234'
87 numeric_repo = '1234'
88 repo_name = numeric_repo
88 repo_name = numeric_repo
89 description = 'description for newly created repo' + numeric_repo
89 description = 'description for newly created repo' + numeric_repo
90 self.app.post(
90 self.app.post(
91 url('repos'),
91 url('repos'),
92 fixture._get_repo_create_params(
92 fixture._get_repo_create_params(
93 repo_private=False,
93 repo_private=False,
94 repo_name=repo_name,
94 repo_name=repo_name,
95 repo_type=backend.alias,
95 repo_type=backend.alias,
96 repo_description=description,
96 repo_description=description,
97 csrf_token=csrf_token))
97 csrf_token=csrf_token))
98
98
99 self.assert_repository_is_created_correctly(
99 self.assert_repository_is_created_correctly(
100 repo_name, description, backend)
100 repo_name, description, backend)
101
101
102 @pytest.mark.parametrize("suffix", [u'', u'Δ…Δ‡Δ™'], ids=['', 'non-ascii'])
102 @pytest.mark.parametrize("suffix", [u'', u'Δ…Δ‡Δ™'], ids=['', 'non-ascii'])
103 def test_create_in_group(
103 def test_create_in_group(
104 self, autologin_user, backend, suffix, csrf_token):
104 self, autologin_user, backend, suffix, csrf_token):
105 # create GROUP
105 # create GROUP
106 group_name = 'sometest_%s' % backend.alias
106 group_name = 'sometest_%s' % backend.alias
107 gr = RepoGroupModel().create(group_name=group_name,
107 gr = RepoGroupModel().create(group_name=group_name,
108 group_description='test',
108 group_description='test',
109 owner=TEST_USER_ADMIN_LOGIN)
109 owner=TEST_USER_ADMIN_LOGIN)
110 Session().commit()
110 Session().commit()
111
111
112 repo_name = u'ingroup' + suffix
112 repo_name = u'ingroup' + suffix
113 repo_name_full = RepoGroup.url_sep().join(
113 repo_name_full = RepoGroup.url_sep().join(
114 [group_name, repo_name])
114 [group_name, repo_name])
115 description = u'description for newly created repo'
115 description = u'description for newly created repo'
116 self.app.post(
116 self.app.post(
117 url('repos'),
117 url('repos'),
118 fixture._get_repo_create_params(
118 fixture._get_repo_create_params(
119 repo_private=False,
119 repo_private=False,
120 repo_name=safe_str(repo_name),
120 repo_name=safe_str(repo_name),
121 repo_type=backend.alias,
121 repo_type=backend.alias,
122 repo_description=description,
122 repo_description=description,
123 repo_group=gr.group_id,
123 repo_group=gr.group_id,
124 csrf_token=csrf_token))
124 csrf_token=csrf_token))
125
125
126 # TODO: johbo: Cleanup work to fixture
126 # TODO: johbo: Cleanup work to fixture
127 try:
127 try:
128 self.assert_repository_is_created_correctly(
128 self.assert_repository_is_created_correctly(
129 repo_name_full, description, backend)
129 repo_name_full, description, backend)
130
130
131 new_repo = RepoModel().get_by_repo_name(repo_name_full)
131 new_repo = RepoModel().get_by_repo_name(repo_name_full)
132 inherited_perms = UserRepoToPerm.query().filter(
132 inherited_perms = UserRepoToPerm.query().filter(
133 UserRepoToPerm.repository_id == new_repo.repo_id).all()
133 UserRepoToPerm.repository_id == new_repo.repo_id).all()
134 assert len(inherited_perms) == 1
134 assert len(inherited_perms) == 1
135 finally:
135 finally:
136 RepoModel().delete(repo_name_full)
136 RepoModel().delete(repo_name_full)
137 RepoGroupModel().delete(group_name)
137 RepoGroupModel().delete(group_name)
138 Session().commit()
138 Session().commit()
139
139
140 def test_create_in_group_numeric(
140 def test_create_in_group_numeric(
141 self, autologin_user, backend, csrf_token):
141 self, autologin_user, backend, csrf_token):
142 # create GROUP
142 # create GROUP
143 group_name = 'sometest_%s' % backend.alias
143 group_name = 'sometest_%s' % backend.alias
144 gr = RepoGroupModel().create(group_name=group_name,
144 gr = RepoGroupModel().create(group_name=group_name,
145 group_description='test',
145 group_description='test',
146 owner=TEST_USER_ADMIN_LOGIN)
146 owner=TEST_USER_ADMIN_LOGIN)
147 Session().commit()
147 Session().commit()
148
148
149 repo_name = '12345'
149 repo_name = '12345'
150 repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
150 repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
151 description = 'description for newly created repo'
151 description = 'description for newly created repo'
152 self.app.post(
152 self.app.post(
153 url('repos'),
153 url('repos'),
154 fixture._get_repo_create_params(
154 fixture._get_repo_create_params(
155 repo_private=False,
155 repo_private=False,
156 repo_name=repo_name,
156 repo_name=repo_name,
157 repo_type=backend.alias,
157 repo_type=backend.alias,
158 repo_description=description,
158 repo_description=description,
159 repo_group=gr.group_id,
159 repo_group=gr.group_id,
160 csrf_token=csrf_token))
160 csrf_token=csrf_token))
161
161
162 # TODO: johbo: Cleanup work to fixture
162 # TODO: johbo: Cleanup work to fixture
163 try:
163 try:
164 self.assert_repository_is_created_correctly(
164 self.assert_repository_is_created_correctly(
165 repo_name_full, description, backend)
165 repo_name_full, description, backend)
166
166
167 new_repo = RepoModel().get_by_repo_name(repo_name_full)
167 new_repo = RepoModel().get_by_repo_name(repo_name_full)
168 inherited_perms = UserRepoToPerm.query()\
168 inherited_perms = UserRepoToPerm.query()\
169 .filter(UserRepoToPerm.repository_id == new_repo.repo_id).all()
169 .filter(UserRepoToPerm.repository_id == new_repo.repo_id).all()
170 assert len(inherited_perms) == 1
170 assert len(inherited_perms) == 1
171 finally:
171 finally:
172 RepoModel().delete(repo_name_full)
172 RepoModel().delete(repo_name_full)
173 RepoGroupModel().delete(group_name)
173 RepoGroupModel().delete(group_name)
174 Session().commit()
174 Session().commit()
175
175
176 def test_create_in_group_without_needed_permissions(self, backend):
176 def test_create_in_group_without_needed_permissions(self, backend):
177 session = login_user_session(
177 session = login_user_session(
178 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
178 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
179 csrf_token = auth.get_csrf_token(session)
179 csrf_token = auth.get_csrf_token(session)
180 # revoke
180 # revoke
181 user_model = UserModel()
181 user_model = UserModel()
182 # disable fork and create on default user
182 # disable fork and create on default user
183 user_model.revoke_perm(User.DEFAULT_USER, 'hg.create.repository')
183 user_model.revoke_perm(User.DEFAULT_USER, 'hg.create.repository')
184 user_model.grant_perm(User.DEFAULT_USER, 'hg.create.none')
184 user_model.grant_perm(User.DEFAULT_USER, 'hg.create.none')
185 user_model.revoke_perm(User.DEFAULT_USER, 'hg.fork.repository')
185 user_model.revoke_perm(User.DEFAULT_USER, 'hg.fork.repository')
186 user_model.grant_perm(User.DEFAULT_USER, 'hg.fork.none')
186 user_model.grant_perm(User.DEFAULT_USER, 'hg.fork.none')
187
187
188 # disable on regular user
188 # disable on regular user
189 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.repository')
189 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.repository')
190 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.none')
190 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.none')
191 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.repository')
191 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.repository')
192 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.none')
192 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.none')
193 Session().commit()
193 Session().commit()
194
194
195 # create GROUP
195 # create GROUP
196 group_name = 'reg_sometest_%s' % backend.alias
196 group_name = 'reg_sometest_%s' % backend.alias
197 gr = RepoGroupModel().create(group_name=group_name,
197 gr = RepoGroupModel().create(group_name=group_name,
198 group_description='test',
198 group_description='test',
199 owner=TEST_USER_ADMIN_LOGIN)
199 owner=TEST_USER_ADMIN_LOGIN)
200 Session().commit()
200 Session().commit()
201
201
202 group_name_allowed = 'reg_sometest_allowed_%s' % backend.alias
202 group_name_allowed = 'reg_sometest_allowed_%s' % backend.alias
203 gr_allowed = RepoGroupModel().create(
203 gr_allowed = RepoGroupModel().create(
204 group_name=group_name_allowed,
204 group_name=group_name_allowed,
205 group_description='test',
205 group_description='test',
206 owner=TEST_USER_REGULAR_LOGIN)
206 owner=TEST_USER_REGULAR_LOGIN)
207 Session().commit()
207 Session().commit()
208
208
209 repo_name = 'ingroup'
209 repo_name = 'ingroup'
210 description = 'description for newly created repo'
210 description = 'description for newly created repo'
211 response = self.app.post(
211 response = self.app.post(
212 url('repos'),
212 url('repos'),
213 fixture._get_repo_create_params(
213 fixture._get_repo_create_params(
214 repo_private=False,
214 repo_private=False,
215 repo_name=repo_name,
215 repo_name=repo_name,
216 repo_type=backend.alias,
216 repo_type=backend.alias,
217 repo_description=description,
217 repo_description=description,
218 repo_group=gr.group_id,
218 repo_group=gr.group_id,
219 csrf_token=csrf_token))
219 csrf_token=csrf_token))
220
220
221 response.mustcontain('Invalid value')
221 response.mustcontain('Invalid value')
222
222
223 # user is allowed to create in this group
223 # user is allowed to create in this group
224 repo_name = 'ingroup'
224 repo_name = 'ingroup'
225 repo_name_full = RepoGroup.url_sep().join(
225 repo_name_full = RepoGroup.url_sep().join(
226 [group_name_allowed, repo_name])
226 [group_name_allowed, repo_name])
227 description = 'description for newly created repo'
227 description = 'description for newly created repo'
228 response = self.app.post(
228 response = self.app.post(
229 url('repos'),
229 url('repos'),
230 fixture._get_repo_create_params(
230 fixture._get_repo_create_params(
231 repo_private=False,
231 repo_private=False,
232 repo_name=repo_name,
232 repo_name=repo_name,
233 repo_type=backend.alias,
233 repo_type=backend.alias,
234 repo_description=description,
234 repo_description=description,
235 repo_group=gr_allowed.group_id,
235 repo_group=gr_allowed.group_id,
236 csrf_token=csrf_token))
236 csrf_token=csrf_token))
237
237
238 # TODO: johbo: Cleanup in pytest fixture
238 # TODO: johbo: Cleanup in pytest fixture
239 try:
239 try:
240 self.assert_repository_is_created_correctly(
240 self.assert_repository_is_created_correctly(
241 repo_name_full, description, backend)
241 repo_name_full, description, backend)
242
242
243 new_repo = RepoModel().get_by_repo_name(repo_name_full)
243 new_repo = RepoModel().get_by_repo_name(repo_name_full)
244 inherited_perms = UserRepoToPerm.query().filter(
244 inherited_perms = UserRepoToPerm.query().filter(
245 UserRepoToPerm.repository_id == new_repo.repo_id).all()
245 UserRepoToPerm.repository_id == new_repo.repo_id).all()
246 assert len(inherited_perms) == 1
246 assert len(inherited_perms) == 1
247
247
248 assert repo_on_filesystem(repo_name_full)
248 assert repo_on_filesystem(repo_name_full)
249 finally:
249 finally:
250 RepoModel().delete(repo_name_full)
250 RepoModel().delete(repo_name_full)
251 RepoGroupModel().delete(group_name)
251 RepoGroupModel().delete(group_name)
252 RepoGroupModel().delete(group_name_allowed)
252 RepoGroupModel().delete(group_name_allowed)
253 Session().commit()
253 Session().commit()
254
254
255 def test_create_in_group_inherit_permissions(self, autologin_user, backend,
255 def test_create_in_group_inherit_permissions(self, autologin_user, backend,
256 csrf_token):
256 csrf_token):
257 # create GROUP
257 # create GROUP
258 group_name = 'sometest_%s' % backend.alias
258 group_name = 'sometest_%s' % backend.alias
259 gr = RepoGroupModel().create(group_name=group_name,
259 gr = RepoGroupModel().create(group_name=group_name,
260 group_description='test',
260 group_description='test',
261 owner=TEST_USER_ADMIN_LOGIN)
261 owner=TEST_USER_ADMIN_LOGIN)
262 perm = Permission.get_by_key('repository.write')
262 perm = Permission.get_by_key('repository.write')
263 RepoGroupModel().grant_user_permission(
263 RepoGroupModel().grant_user_permission(
264 gr, TEST_USER_REGULAR_LOGIN, perm)
264 gr, TEST_USER_REGULAR_LOGIN, perm)
265
265
266 # add repo permissions
266 # add repo permissions
267 Session().commit()
267 Session().commit()
268
268
269 repo_name = 'ingroup_inherited_%s' % backend.alias
269 repo_name = 'ingroup_inherited_%s' % backend.alias
270 repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
270 repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
271 description = 'description for newly created repo'
271 description = 'description for newly created repo'
272 self.app.post(
272 self.app.post(
273 url('repos'),
273 url('repos'),
274 fixture._get_repo_create_params(
274 fixture._get_repo_create_params(
275 repo_private=False,
275 repo_private=False,
276 repo_name=repo_name,
276 repo_name=repo_name,
277 repo_type=backend.alias,
277 repo_type=backend.alias,
278 repo_description=description,
278 repo_description=description,
279 repo_group=gr.group_id,
279 repo_group=gr.group_id,
280 repo_copy_permissions=True,
280 repo_copy_permissions=True,
281 csrf_token=csrf_token))
281 csrf_token=csrf_token))
282
282
283 # TODO: johbo: Cleanup to pytest fixture
283 # TODO: johbo: Cleanup to pytest fixture
284 try:
284 try:
285 self.assert_repository_is_created_correctly(
285 self.assert_repository_is_created_correctly(
286 repo_name_full, description, backend)
286 repo_name_full, description, backend)
287 except Exception:
287 except Exception:
288 RepoGroupModel().delete(group_name)
288 RepoGroupModel().delete(group_name)
289 Session().commit()
289 Session().commit()
290 raise
290 raise
291
291
292 # check if inherited permissions are applied
292 # check if inherited permissions are applied
293 new_repo = RepoModel().get_by_repo_name(repo_name_full)
293 new_repo = RepoModel().get_by_repo_name(repo_name_full)
294 inherited_perms = UserRepoToPerm.query().filter(
294 inherited_perms = UserRepoToPerm.query().filter(
295 UserRepoToPerm.repository_id == new_repo.repo_id).all()
295 UserRepoToPerm.repository_id == new_repo.repo_id).all()
296 assert len(inherited_perms) == 2
296 assert len(inherited_perms) == 2
297
297
298 assert TEST_USER_REGULAR_LOGIN in [
298 assert TEST_USER_REGULAR_LOGIN in [
299 x.user.username for x in inherited_perms]
299 x.user.username for x in inherited_perms]
300 assert 'repository.write' in [
300 assert 'repository.write' in [
301 x.permission.permission_name for x in inherited_perms]
301 x.permission.permission_name for x in inherited_perms]
302
302
303 RepoModel().delete(repo_name_full)
303 RepoModel().delete(repo_name_full)
304 RepoGroupModel().delete(group_name)
304 RepoGroupModel().delete(group_name)
305 Session().commit()
305 Session().commit()
306
306
307 @pytest.mark.xfail_backends(
307 @pytest.mark.xfail_backends(
308 "git", "hg", reason="Missing reposerver support")
308 "git", "hg", reason="Missing reposerver support")
309 def test_create_with_clone_uri(self, autologin_user, backend, reposerver,
309 def test_create_with_clone_uri(self, autologin_user, backend, reposerver,
310 csrf_token):
310 csrf_token):
311 source_repo = backend.create_repo(number_of_commits=2)
311 source_repo = backend.create_repo(number_of_commits=2)
312 source_repo_name = source_repo.repo_name
312 source_repo_name = source_repo.repo_name
313 reposerver.serve(source_repo.scm_instance())
313 reposerver.serve(source_repo.scm_instance())
314
314
315 repo_name = backend.new_repo_name()
315 repo_name = backend.new_repo_name()
316 response = self.app.post(
316 response = self.app.post(
317 url('repos'),
317 url('repos'),
318 fixture._get_repo_create_params(
318 fixture._get_repo_create_params(
319 repo_private=False,
319 repo_private=False,
320 repo_name=repo_name,
320 repo_name=repo_name,
321 repo_type=backend.alias,
321 repo_type=backend.alias,
322 repo_description='',
322 repo_description='',
323 clone_uri=reposerver.url,
323 clone_uri=reposerver.url,
324 csrf_token=csrf_token),
324 csrf_token=csrf_token),
325 status=302)
325 status=302)
326
326
327 # Should be redirected to the creating page
327 # Should be redirected to the creating page
328 response.mustcontain('repo_creating')
328 response.mustcontain('repo_creating')
329
329
330 # Expecting that both repositories have same history
330 # Expecting that both repositories have same history
331 source_repo = RepoModel().get_by_repo_name(source_repo_name)
331 source_repo = RepoModel().get_by_repo_name(source_repo_name)
332 source_vcs = source_repo.scm_instance()
332 source_vcs = source_repo.scm_instance()
333 repo = RepoModel().get_by_repo_name(repo_name)
333 repo = RepoModel().get_by_repo_name(repo_name)
334 repo_vcs = repo.scm_instance()
334 repo_vcs = repo.scm_instance()
335 assert source_vcs[0].message == repo_vcs[0].message
335 assert source_vcs[0].message == repo_vcs[0].message
336 assert source_vcs.count() == repo_vcs.count()
336 assert source_vcs.count() == repo_vcs.count()
337 assert source_vcs.commit_ids == repo_vcs.commit_ids
337 assert source_vcs.commit_ids == repo_vcs.commit_ids
338
338
339 @pytest.mark.xfail_backends("svn", reason="Depends on import support")
339 @pytest.mark.xfail_backends("svn", reason="Depends on import support")
340 def test_create_remote_repo_wrong_clone_uri(self, autologin_user, backend,
340 def test_create_remote_repo_wrong_clone_uri(self, autologin_user, backend,
341 csrf_token):
341 csrf_token):
342 repo_name = backend.new_repo_name()
342 repo_name = backend.new_repo_name()
343 description = 'description for newly created repo'
343 description = 'description for newly created repo'
344 response = self.app.post(
344 response = self.app.post(
345 url('repos'),
345 url('repos'),
346 fixture._get_repo_create_params(
346 fixture._get_repo_create_params(
347 repo_private=False,
347 repo_private=False,
348 repo_name=repo_name,
348 repo_name=repo_name,
349 repo_type=backend.alias,
349 repo_type=backend.alias,
350 repo_description=description,
350 repo_description=description,
351 clone_uri='http://repo.invalid/repo',
351 clone_uri='http://repo.invalid/repo',
352 csrf_token=csrf_token))
352 csrf_token=csrf_token))
353 response.mustcontain('invalid clone url')
353 response.mustcontain('invalid clone url')
354
354
355 @pytest.mark.xfail_backends("svn", reason="Depends on import support")
355 @pytest.mark.xfail_backends("svn", reason="Depends on import support")
356 def test_create_remote_repo_wrong_clone_uri_hg_svn(
356 def test_create_remote_repo_wrong_clone_uri_hg_svn(
357 self, autologin_user, backend, csrf_token):
357 self, autologin_user, backend, csrf_token):
358 repo_name = backend.new_repo_name()
358 repo_name = backend.new_repo_name()
359 description = 'description for newly created repo'
359 description = 'description for newly created repo'
360 response = self.app.post(
360 response = self.app.post(
361 url('repos'),
361 url('repos'),
362 fixture._get_repo_create_params(
362 fixture._get_repo_create_params(
363 repo_private=False,
363 repo_private=False,
364 repo_name=repo_name,
364 repo_name=repo_name,
365 repo_type=backend.alias,
365 repo_type=backend.alias,
366 repo_description=description,
366 repo_description=description,
367 clone_uri='svn+http://svn.invalid/repo',
367 clone_uri='svn+http://svn.invalid/repo',
368 csrf_token=csrf_token))
368 csrf_token=csrf_token))
369 response.mustcontain('invalid clone url')
369 response.mustcontain('invalid clone url')
370
370
371 def test_create_with_git_suffix(
371 def test_create_with_git_suffix(
372 self, autologin_user, backend, csrf_token):
372 self, autologin_user, backend, csrf_token):
373 repo_name = backend.new_repo_name() + ".git"
373 repo_name = backend.new_repo_name() + ".git"
374 description = 'description for newly created repo'
374 description = 'description for newly created repo'
375 response = self.app.post(
375 response = self.app.post(
376 url('repos'),
376 url('repos'),
377 fixture._get_repo_create_params(
377 fixture._get_repo_create_params(
378 repo_private=False,
378 repo_private=False,
379 repo_name=repo_name,
379 repo_name=repo_name,
380 repo_type=backend.alias,
380 repo_type=backend.alias,
381 repo_description=description,
381 repo_description=description,
382 csrf_token=csrf_token))
382 csrf_token=csrf_token))
383 response.mustcontain('Repository name cannot end with .git')
383 response.mustcontain('Repository name cannot end with .git')
384
384
385 @pytest.mark.parametrize("suffix", [u'', u'Δ…Δ™Ε‚'], ids=['', 'non-ascii'])
385 @pytest.mark.parametrize("suffix", [u'', u'Δ…Δ™Ε‚'], ids=['', 'non-ascii'])
386 def test_delete(self, autologin_user, backend, suffix, csrf_token):
386 def test_delete(self, autologin_user, backend, suffix, csrf_token):
387 repo = backend.create_repo(name_suffix=suffix)
387 repo = backend.create_repo(name_suffix=suffix)
388 repo_name = repo.repo_name
388 repo_name = repo.repo_name
389
389
390 response = self.app.post(url('repo', repo_name=repo_name),
390 response = self.app.post(url('repo', repo_name=repo_name),
391 params={'_method': 'delete',
391 params={'_method': 'delete',
392 'csrf_token': csrf_token})
392 'csrf_token': csrf_token})
393 assert_session_flash(response, 'Deleted repository %s' % (repo_name))
393 assert_session_flash(response, 'Deleted repository %s' % (repo_name))
394 response.follow()
394 response.follow()
395
395
396 # check if repo was deleted from db
396 # check if repo was deleted from db
397 assert RepoModel().get_by_repo_name(repo_name) is None
397 assert RepoModel().get_by_repo_name(repo_name) is None
398 assert not repo_on_filesystem(repo_name)
398 assert not repo_on_filesystem(repo_name)
399
399
400 def test_show(self, autologin_user, backend):
400 def test_show(self, autologin_user, backend):
401 self.app.get(url('repo', repo_name=backend.repo_name))
401 self.app.get(url('repo', repo_name=backend.repo_name))
402
402
403 def test_default_user_cannot_access_private_repo_in_a_group(
403 def test_default_user_cannot_access_private_repo_in_a_group(
404 self, autologin_user, user_util, backend, csrf_token):
404 self, autologin_user, user_util, backend, csrf_token):
405
405
406 group = user_util.create_repo_group()
406 group = user_util.create_repo_group()
407
407
408 repo = backend.create_repo(
408 repo = backend.create_repo(
409 repo_private=True, repo_group=group, repo_copy_permissions=True)
409 repo_private=True, repo_group=group, repo_copy_permissions=True)
410
410
411 permissions = _get_permission_for_user(
411 permissions = _get_permission_for_user(
412 user='default', repo=repo.repo_name)
412 user='default', repo=repo.repo_name)
413 assert len(permissions) == 1
413 assert len(permissions) == 1
414 assert permissions[0].permission.permission_name == 'repository.none'
414 assert permissions[0].permission.permission_name == 'repository.none'
415 assert permissions[0].repository.private is True
415 assert permissions[0].repository.private is True
416
416
417 def test_set_repo_fork_has_no_self_id(self, autologin_user, backend):
417 def test_set_repo_fork_has_no_self_id(self, autologin_user, backend):
418 repo = backend.repo
418 repo = backend.repo
419 response = self.app.get(
419 response = self.app.get(
420 url('edit_repo_advanced', repo_name=backend.repo_name))
420 url('edit_repo_advanced', repo_name=backend.repo_name))
421 opt = """<option value="%s">vcs_test_git</option>""" % repo.repo_id
421 opt = """<option value="%s">vcs_test_git</option>""" % repo.repo_id
422 response.mustcontain(no=[opt])
422 response.mustcontain(no=[opt])
423
423
424 def test_set_fork_of_target_repo(
424 def test_set_fork_of_target_repo(
425 self, autologin_user, backend, csrf_token):
425 self, autologin_user, backend, csrf_token):
426 target_repo = 'target_%s' % backend.alias
426 target_repo = 'target_%s' % backend.alias
427 fixture.create_repo(target_repo, repo_type=backend.alias)
427 fixture.create_repo(target_repo, repo_type=backend.alias)
428 repo2 = Repository.get_by_repo_name(target_repo)
428 repo2 = Repository.get_by_repo_name(target_repo)
429 response = self.app.post(
429 response = self.app.post(
430 url('edit_repo_advanced_fork', repo_name=backend.repo_name),
430 url('edit_repo_advanced_fork', repo_name=backend.repo_name),
431 params={'id_fork_of': repo2.repo_id, '_method': 'put',
431 params={'id_fork_of': repo2.repo_id, '_method': 'put',
432 'csrf_token': csrf_token})
432 'csrf_token': csrf_token})
433 repo = Repository.get_by_repo_name(backend.repo_name)
433 repo = Repository.get_by_repo_name(backend.repo_name)
434 repo2 = Repository.get_by_repo_name(target_repo)
434 repo2 = Repository.get_by_repo_name(target_repo)
435 assert_session_flash(
435 assert_session_flash(
436 response,
436 response,
437 'Marked repo %s as fork of %s' % (repo.repo_name, repo2.repo_name))
437 'Marked repo %s as fork of %s' % (repo.repo_name, repo2.repo_name))
438
438
439 assert repo.fork == repo2
439 assert repo.fork == repo2
440 response = response.follow()
440 response = response.follow()
441 # check if given repo is selected
441 # check if given repo is selected
442
442
443 opt = 'This repository is a fork of <a href="%s">%s</a>' % (
443 opt = 'This repository is a fork of <a href="%s">%s</a>' % (
444 url('summary_home', repo_name=repo2.repo_name), repo2.repo_name)
444 url('summary_home', repo_name=repo2.repo_name), repo2.repo_name)
445
445
446 response.mustcontain(opt)
446 response.mustcontain(opt)
447
447
448 fixture.destroy_repo(target_repo, forks='detach')
448 fixture.destroy_repo(target_repo, forks='detach')
449
449
450 @pytest.mark.backends("hg", "git")
450 @pytest.mark.backends("hg", "git")
451 def test_set_fork_of_other_type_repo(self, autologin_user, backend,
451 def test_set_fork_of_other_type_repo(self, autologin_user, backend,
452 csrf_token):
452 csrf_token):
453 TARGET_REPO_MAP = {
453 TARGET_REPO_MAP = {
454 'git': {
454 'git': {
455 'type': 'hg',
455 'type': 'hg',
456 'repo_name': HG_REPO},
456 'repo_name': HG_REPO},
457 'hg': {
457 'hg': {
458 'type': 'git',
458 'type': 'git',
459 'repo_name': GIT_REPO},
459 'repo_name': GIT_REPO},
460 }
460 }
461 target_repo = TARGET_REPO_MAP[backend.alias]
461 target_repo = TARGET_REPO_MAP[backend.alias]
462
462
463 repo2 = Repository.get_by_repo_name(target_repo['repo_name'])
463 repo2 = Repository.get_by_repo_name(target_repo['repo_name'])
464 response = self.app.post(
464 response = self.app.post(
465 url('edit_repo_advanced_fork', repo_name=backend.repo_name),
465 url('edit_repo_advanced_fork', repo_name=backend.repo_name),
466 params={'id_fork_of': repo2.repo_id, '_method': 'put',
466 params={'id_fork_of': repo2.repo_id, '_method': 'put',
467 'csrf_token': csrf_token})
467 'csrf_token': csrf_token})
468 assert_session_flash(
468 assert_session_flash(
469 response,
469 response,
470 'Cannot set repository as fork of repository with other type')
470 'Cannot set repository as fork of repository with other type')
471
471
472 def test_set_fork_of_none(self, autologin_user, backend, csrf_token):
472 def test_set_fork_of_none(self, autologin_user, backend, csrf_token):
473 # mark it as None
473 # mark it as None
474 response = self.app.post(
474 response = self.app.post(
475 url('edit_repo_advanced_fork', repo_name=backend.repo_name),
475 url('edit_repo_advanced_fork', repo_name=backend.repo_name),
476 params={'id_fork_of': None, '_method': 'put',
476 params={'id_fork_of': None, '_method': 'put',
477 'csrf_token': csrf_token})
477 'csrf_token': csrf_token})
478 assert_session_flash(
478 assert_session_flash(
479 response,
479 response,
480 'Marked repo %s as fork of %s'
480 'Marked repo %s as fork of %s'
481 % (backend.repo_name, "Nothing"))
481 % (backend.repo_name, "Nothing"))
482 assert backend.repo.fork is None
482 assert backend.repo.fork is None
483
483
484 def test_set_fork_of_same_repo(self, autologin_user, backend, csrf_token):
484 def test_set_fork_of_same_repo(self, autologin_user, backend, csrf_token):
485 repo = Repository.get_by_repo_name(backend.repo_name)
485 repo = Repository.get_by_repo_name(backend.repo_name)
486 response = self.app.post(
486 response = self.app.post(
487 url('edit_repo_advanced_fork', repo_name=backend.repo_name),
487 url('edit_repo_advanced_fork', repo_name=backend.repo_name),
488 params={'id_fork_of': repo.repo_id, '_method': 'put',
488 params={'id_fork_of': repo.repo_id, '_method': 'put',
489 'csrf_token': csrf_token})
489 'csrf_token': csrf_token})
490 assert_session_flash(
490 assert_session_flash(
491 response, 'An error occurred during this operation')
491 response, 'An error occurred during this operation')
492
492
493 def test_create_on_top_level_without_permissions(self, backend):
493 def test_create_on_top_level_without_permissions(self, backend):
494 session = login_user_session(
494 session = login_user_session(
495 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
495 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
496 csrf_token = auth.get_csrf_token(session)
496 csrf_token = auth.get_csrf_token(session)
497
497
498 # revoke
498 # revoke
499 user_model = UserModel()
499 user_model = UserModel()
500 # disable fork and create on default user
500 # disable fork and create on default user
501 user_model.revoke_perm(User.DEFAULT_USER, 'hg.create.repository')
501 user_model.revoke_perm(User.DEFAULT_USER, 'hg.create.repository')
502 user_model.grant_perm(User.DEFAULT_USER, 'hg.create.none')
502 user_model.grant_perm(User.DEFAULT_USER, 'hg.create.none')
503 user_model.revoke_perm(User.DEFAULT_USER, 'hg.fork.repository')
503 user_model.revoke_perm(User.DEFAULT_USER, 'hg.fork.repository')
504 user_model.grant_perm(User.DEFAULT_USER, 'hg.fork.none')
504 user_model.grant_perm(User.DEFAULT_USER, 'hg.fork.none')
505
505
506 # disable on regular user
506 # disable on regular user
507 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.repository')
507 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.repository')
508 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.none')
508 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.none')
509 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.repository')
509 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.repository')
510 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.none')
510 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.none')
511 Session().commit()
511 Session().commit()
512
512
513 repo_name = backend.new_repo_name()
513 repo_name = backend.new_repo_name()
514 description = 'description for newly created repo'
514 description = 'description for newly created repo'
515 response = self.app.post(
515 response = self.app.post(
516 url('repos'),
516 url('repos'),
517 fixture._get_repo_create_params(
517 fixture._get_repo_create_params(
518 repo_private=False,
518 repo_private=False,
519 repo_name=repo_name,
519 repo_name=repo_name,
520 repo_type=backend.alias,
520 repo_type=backend.alias,
521 repo_description=description,
521 repo_description=description,
522 csrf_token=csrf_token))
522 csrf_token=csrf_token))
523
523
524 response.mustcontain(
524 response.mustcontain(
525 u"You do not have the permission to store repositories in "
525 u"You do not have the permission to store repositories in "
526 u"the root location.")
526 u"the root location.")
527
527
528 @mock.patch.object(RepoModel, '_create_filesystem_repo', error_function)
528 @mock.patch.object(RepoModel, '_create_filesystem_repo', error_function)
529 def test_create_repo_when_filesystem_op_fails(
529 def test_create_repo_when_filesystem_op_fails(
530 self, autologin_user, backend, csrf_token):
530 self, autologin_user, backend, csrf_token):
531 repo_name = backend.new_repo_name()
531 repo_name = backend.new_repo_name()
532 description = 'description for newly created repo'
532 description = 'description for newly created repo'
533
533
534 response = self.app.post(
534 response = self.app.post(
535 url('repos'),
535 url('repos'),
536 fixture._get_repo_create_params(
536 fixture._get_repo_create_params(
537 repo_private=False,
537 repo_private=False,
538 repo_name=repo_name,
538 repo_name=repo_name,
539 repo_type=backend.alias,
539 repo_type=backend.alias,
540 repo_description=description,
540 repo_description=description,
541 csrf_token=csrf_token))
541 csrf_token=csrf_token))
542
542
543 assert_session_flash(
543 assert_session_flash(
544 response, 'Error creating repository %s' % repo_name)
544 response, 'Error creating repository %s' % repo_name)
545 # repo must not be in db
545 # repo must not be in db
546 assert backend.repo is None
546 assert backend.repo is None
547 # repo must not be in filesystem !
547 # repo must not be in filesystem !
548 assert not repo_on_filesystem(repo_name)
548 assert not repo_on_filesystem(repo_name)
549
549
550 def assert_repository_is_created_correctly(
550 def assert_repository_is_created_correctly(
551 self, repo_name, description, backend):
551 self, repo_name, description, backend):
552 repo_name_utf8 = safe_str(repo_name)
552 repo_name_utf8 = safe_str(repo_name)
553
553
554 # run the check page that triggers the flash message
554 # run the check page that triggers the flash message
555 response = self.app.get(url('repo_check_home', repo_name=repo_name))
555 response = self.app.get(url('repo_check_home', repo_name=repo_name))
556 assert response.json == {u'result': True}
556 assert response.json == {u'result': True}
557
557
558 flash_msg = u'Created repository <a href="/{}">{}</a>'.format(
558 flash_msg = u'Created repository <a href="/{}">{}</a>'.format(
559 urllib.quote(repo_name_utf8), repo_name)
559 urllib.quote(repo_name_utf8), repo_name)
560 assert_session_flash(response, flash_msg)
560 assert_session_flash(response, flash_msg)
561
561
562 # test if the repo was created in the database
562 # test if the repo was created in the database
563 new_repo = RepoModel().get_by_repo_name(repo_name)
563 new_repo = RepoModel().get_by_repo_name(repo_name)
564
564
565 assert new_repo.repo_name == repo_name
565 assert new_repo.repo_name == repo_name
566 assert new_repo.description == description
566 assert new_repo.description == description
567
567
568 # test if the repository is visible in the list ?
568 # test if the repository is visible in the list ?
569 response = self.app.get(url('summary_home', repo_name=repo_name))
569 response = self.app.get(url('summary_home', repo_name=repo_name))
570 response.mustcontain(repo_name)
570 response.mustcontain(repo_name)
571 response.mustcontain(backend.alias)
571 response.mustcontain(backend.alias)
572
572
573 assert repo_on_filesystem(repo_name)
573 assert repo_on_filesystem(repo_name)
574
574
575
575
576 @pytest.mark.usefixtures("app")
576 @pytest.mark.usefixtures("app")
577 class TestVcsSettings(object):
577 class TestVcsSettings(object):
578 FORM_DATA = {
578 FORM_DATA = {
579 'inherit_global_settings': False,
579 'inherit_global_settings': False,
580 'hooks_changegroup_repo_size': False,
580 'hooks_changegroup_repo_size': False,
581 'hooks_changegroup_push_logger': False,
581 'hooks_changegroup_push_logger': False,
582 'hooks_outgoing_pull_logger': False,
582 'hooks_outgoing_pull_logger': False,
583 'extensions_largefiles': False,
583 'extensions_largefiles': False,
584 'extensions_evolve': False,
584 'phases_publish': 'False',
585 'phases_publish': 'False',
585 'rhodecode_pr_merge_enabled': False,
586 'rhodecode_pr_merge_enabled': False,
586 'rhodecode_use_outdated_comments': False,
587 'rhodecode_use_outdated_comments': False,
587 'new_svn_branch': '',
588 'new_svn_branch': '',
588 'new_svn_tag': ''
589 'new_svn_tag': ''
589 }
590 }
590
591
591 @pytest.mark.skip_backends('svn')
592 @pytest.mark.skip_backends('svn')
592 def test_global_settings_initial_values(self, autologin_user, backend):
593 def test_global_settings_initial_values(self, autologin_user, backend):
593 repo_name = backend.repo_name
594 repo_name = backend.repo_name
594 response = self.app.get(url('repo_vcs_settings', repo_name=repo_name))
595 response = self.app.get(url('repo_vcs_settings', repo_name=repo_name))
595
596
596 expected_settings = (
597 expected_settings = (
597 'rhodecode_use_outdated_comments', 'rhodecode_pr_merge_enabled',
598 'rhodecode_use_outdated_comments', 'rhodecode_pr_merge_enabled',
598 'hooks_changegroup_repo_size', 'hooks_changegroup_push_logger',
599 'hooks_changegroup_repo_size', 'hooks_changegroup_push_logger',
599 'hooks_outgoing_pull_logger'
600 'hooks_outgoing_pull_logger'
600 )
601 )
601 for setting in expected_settings:
602 for setting in expected_settings:
602 self.assert_repo_value_equals_global_value(response, setting)
603 self.assert_repo_value_equals_global_value(response, setting)
603
604
604 def test_show_settings_requires_repo_admin_permission(
605 def test_show_settings_requires_repo_admin_permission(
605 self, backend, user_util, settings_util):
606 self, backend, user_util, settings_util):
606 repo = backend.create_repo()
607 repo = backend.create_repo()
607 repo_name = repo.repo_name
608 repo_name = repo.repo_name
608 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
609 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
609 user_util.grant_user_permission_to_repo(repo, user, 'repository.admin')
610 user_util.grant_user_permission_to_repo(repo, user, 'repository.admin')
610 login_user_session(
611 login_user_session(
611 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
612 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
612 self.app.get(url('repo_vcs_settings', repo_name=repo_name), status=200)
613 self.app.get(url('repo_vcs_settings', repo_name=repo_name), status=200)
613
614
614 def test_inherit_global_settings_flag_is_true_by_default(
615 def test_inherit_global_settings_flag_is_true_by_default(
615 self, autologin_user, backend):
616 self, autologin_user, backend):
616 repo_name = backend.repo_name
617 repo_name = backend.repo_name
617 response = self.app.get(url('repo_vcs_settings', repo_name=repo_name))
618 response = self.app.get(url('repo_vcs_settings', repo_name=repo_name))
618
619
619 assert_response = AssertResponse(response)
620 assert_response = AssertResponse(response)
620 element = assert_response.get_element('#inherit_global_settings')
621 element = assert_response.get_element('#inherit_global_settings')
621 assert element.checked
622 assert element.checked
622
623
623 @pytest.mark.parametrize('checked_value', [True, False])
624 @pytest.mark.parametrize('checked_value', [True, False])
624 def test_inherit_global_settings_value(
625 def test_inherit_global_settings_value(
625 self, autologin_user, backend, checked_value, settings_util):
626 self, autologin_user, backend, checked_value, settings_util):
626 repo = backend.create_repo()
627 repo = backend.create_repo()
627 repo_name = repo.repo_name
628 repo_name = repo.repo_name
628 settings_util.create_repo_rhodecode_setting(
629 settings_util.create_repo_rhodecode_setting(
629 repo, 'inherit_vcs_settings', checked_value, 'bool')
630 repo, 'inherit_vcs_settings', checked_value, 'bool')
630 response = self.app.get(url('repo_vcs_settings', repo_name=repo_name))
631 response = self.app.get(url('repo_vcs_settings', repo_name=repo_name))
631
632
632 assert_response = AssertResponse(response)
633 assert_response = AssertResponse(response)
633 element = assert_response.get_element('#inherit_global_settings')
634 element = assert_response.get_element('#inherit_global_settings')
634 assert element.checked == checked_value
635 assert element.checked == checked_value
635
636
636 @pytest.mark.skip_backends('svn')
637 @pytest.mark.skip_backends('svn')
637 def test_hooks_settings_are_created(
638 def test_hooks_settings_are_created(
638 self, autologin_user, backend, csrf_token):
639 self, autologin_user, backend, csrf_token):
639 repo_name = backend.repo_name
640 repo_name = backend.repo_name
640 data = self.FORM_DATA.copy()
641 data = self.FORM_DATA.copy()
641 data['csrf_token'] = csrf_token
642 data['csrf_token'] = csrf_token
642 self.app.post(
643 self.app.post(
643 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
644 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
644 settings = SettingsModel(repo=repo_name)
645 settings = SettingsModel(repo=repo_name)
645 try:
646 try:
646 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
647 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
647 ui = settings.get_ui_by_section_and_key(section, key)
648 ui = settings.get_ui_by_section_and_key(section, key)
648 assert ui.ui_active is False
649 assert ui.ui_active is False
649 finally:
650 finally:
650 self._cleanup_repo_settings(settings)
651 self._cleanup_repo_settings(settings)
651
652
652 def test_hooks_settings_are_not_created_for_svn(
653 def test_hooks_settings_are_not_created_for_svn(
653 self, autologin_user, backend_svn, csrf_token):
654 self, autologin_user, backend_svn, csrf_token):
654 repo_name = backend_svn.repo_name
655 repo_name = backend_svn.repo_name
655 data = self.FORM_DATA.copy()
656 data = self.FORM_DATA.copy()
656 data['csrf_token'] = csrf_token
657 data['csrf_token'] = csrf_token
657 self.app.post(
658 self.app.post(
658 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
659 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
659 settings = SettingsModel(repo=repo_name)
660 settings = SettingsModel(repo=repo_name)
660 try:
661 try:
661 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
662 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
662 ui = settings.get_ui_by_section_and_key(section, key)
663 ui = settings.get_ui_by_section_and_key(section, key)
663 assert ui is None
664 assert ui is None
664 finally:
665 finally:
665 self._cleanup_repo_settings(settings)
666 self._cleanup_repo_settings(settings)
666
667
667 @pytest.mark.skip_backends('svn')
668 @pytest.mark.skip_backends('svn')
668 def test_hooks_settings_are_updated(
669 def test_hooks_settings_are_updated(
669 self, autologin_user, backend, csrf_token):
670 self, autologin_user, backend, csrf_token):
670 repo_name = backend.repo_name
671 repo_name = backend.repo_name
671 settings = SettingsModel(repo=repo_name)
672 settings = SettingsModel(repo=repo_name)
672 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
673 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
673 settings.create_ui_section_value(section, '', key=key, active=True)
674 settings.create_ui_section_value(section, '', key=key, active=True)
674
675
675 data = self.FORM_DATA.copy()
676 data = self.FORM_DATA.copy()
676 data['csrf_token'] = csrf_token
677 data['csrf_token'] = csrf_token
677 self.app.post(
678 self.app.post(
678 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
679 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
679 try:
680 try:
680 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
681 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
681 ui = settings.get_ui_by_section_and_key(section, key)
682 ui = settings.get_ui_by_section_and_key(section, key)
682 assert ui.ui_active is False
683 assert ui.ui_active is False
683 finally:
684 finally:
684 self._cleanup_repo_settings(settings)
685 self._cleanup_repo_settings(settings)
685
686
686 def test_hooks_settings_are_not_updated_for_svn(
687 def test_hooks_settings_are_not_updated_for_svn(
687 self, autologin_user, backend_svn, csrf_token):
688 self, autologin_user, backend_svn, csrf_token):
688 repo_name = backend_svn.repo_name
689 repo_name = backend_svn.repo_name
689 settings = SettingsModel(repo=repo_name)
690 settings = SettingsModel(repo=repo_name)
690 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
691 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
691 settings.create_ui_section_value(section, '', key=key, active=True)
692 settings.create_ui_section_value(section, '', key=key, active=True)
692
693
693 data = self.FORM_DATA.copy()
694 data = self.FORM_DATA.copy()
694 data['csrf_token'] = csrf_token
695 data['csrf_token'] = csrf_token
695 self.app.post(
696 self.app.post(
696 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
697 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
697 try:
698 try:
698 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
699 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
699 ui = settings.get_ui_by_section_and_key(section, key)
700 ui = settings.get_ui_by_section_and_key(section, key)
700 assert ui.ui_active is True
701 assert ui.ui_active is True
701 finally:
702 finally:
702 self._cleanup_repo_settings(settings)
703 self._cleanup_repo_settings(settings)
703
704
704 @pytest.mark.skip_backends('svn')
705 @pytest.mark.skip_backends('svn')
705 def test_pr_settings_are_created(
706 def test_pr_settings_are_created(
706 self, autologin_user, backend, csrf_token):
707 self, autologin_user, backend, csrf_token):
707 repo_name = backend.repo_name
708 repo_name = backend.repo_name
708 data = self.FORM_DATA.copy()
709 data = self.FORM_DATA.copy()
709 data['csrf_token'] = csrf_token
710 data['csrf_token'] = csrf_token
710 self.app.post(
711 self.app.post(
711 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
712 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
712 settings = SettingsModel(repo=repo_name)
713 settings = SettingsModel(repo=repo_name)
713 try:
714 try:
714 for name in VcsSettingsModel.GENERAL_SETTINGS:
715 for name in VcsSettingsModel.GENERAL_SETTINGS:
715 setting = settings.get_setting_by_name(name)
716 setting = settings.get_setting_by_name(name)
716 assert setting.app_settings_value is False
717 assert setting.app_settings_value is False
717 finally:
718 finally:
718 self._cleanup_repo_settings(settings)
719 self._cleanup_repo_settings(settings)
719
720
720 def test_pr_settings_are_not_created_for_svn(
721 def test_pr_settings_are_not_created_for_svn(
721 self, autologin_user, backend_svn, csrf_token):
722 self, autologin_user, backend_svn, csrf_token):
722 repo_name = backend_svn.repo_name
723 repo_name = backend_svn.repo_name
723 data = self.FORM_DATA.copy()
724 data = self.FORM_DATA.copy()
724 data['csrf_token'] = csrf_token
725 data['csrf_token'] = csrf_token
725 self.app.post(
726 self.app.post(
726 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
727 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
727 settings = SettingsModel(repo=repo_name)
728 settings = SettingsModel(repo=repo_name)
728 try:
729 try:
729 for name in VcsSettingsModel.GENERAL_SETTINGS:
730 for name in VcsSettingsModel.GENERAL_SETTINGS:
730 setting = settings.get_setting_by_name(name)
731 setting = settings.get_setting_by_name(name)
731 assert setting is None
732 assert setting is None
732 finally:
733 finally:
733 self._cleanup_repo_settings(settings)
734 self._cleanup_repo_settings(settings)
734
735
735 def test_pr_settings_creation_requires_repo_admin_permission(
736 def test_pr_settings_creation_requires_repo_admin_permission(
736 self, backend, user_util, settings_util, csrf_token):
737 self, backend, user_util, settings_util, csrf_token):
737 repo = backend.create_repo()
738 repo = backend.create_repo()
738 repo_name = repo.repo_name
739 repo_name = repo.repo_name
739
740
740 logout_user_session(self.app, csrf_token)
741 logout_user_session(self.app, csrf_token)
741 session = login_user_session(
742 session = login_user_session(
742 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
743 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
743 new_csrf_token = auth.get_csrf_token(session)
744 new_csrf_token = auth.get_csrf_token(session)
744
745
745 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
746 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
746 repo = Repository.get_by_repo_name(repo_name)
747 repo = Repository.get_by_repo_name(repo_name)
747 user_util.grant_user_permission_to_repo(repo, user, 'repository.admin')
748 user_util.grant_user_permission_to_repo(repo, user, 'repository.admin')
748 data = self.FORM_DATA.copy()
749 data = self.FORM_DATA.copy()
749 data['csrf_token'] = new_csrf_token
750 data['csrf_token'] = new_csrf_token
750 settings = SettingsModel(repo=repo_name)
751 settings = SettingsModel(repo=repo_name)
751
752
752 try:
753 try:
753 self.app.post(
754 self.app.post(
754 url('repo_vcs_settings', repo_name=repo_name), data,
755 url('repo_vcs_settings', repo_name=repo_name), data,
755 status=302)
756 status=302)
756 finally:
757 finally:
757 self._cleanup_repo_settings(settings)
758 self._cleanup_repo_settings(settings)
758
759
759 @pytest.mark.skip_backends('svn')
760 @pytest.mark.skip_backends('svn')
760 def test_pr_settings_are_updated(
761 def test_pr_settings_are_updated(
761 self, autologin_user, backend, csrf_token):
762 self, autologin_user, backend, csrf_token):
762 repo_name = backend.repo_name
763 repo_name = backend.repo_name
763 settings = SettingsModel(repo=repo_name)
764 settings = SettingsModel(repo=repo_name)
764 for name in VcsSettingsModel.GENERAL_SETTINGS:
765 for name in VcsSettingsModel.GENERAL_SETTINGS:
765 settings.create_or_update_setting(name, True, 'bool')
766 settings.create_or_update_setting(name, True, 'bool')
766
767
767 data = self.FORM_DATA.copy()
768 data = self.FORM_DATA.copy()
768 data['csrf_token'] = csrf_token
769 data['csrf_token'] = csrf_token
769 self.app.post(
770 self.app.post(
770 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
771 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
771 try:
772 try:
772 for name in VcsSettingsModel.GENERAL_SETTINGS:
773 for name in VcsSettingsModel.GENERAL_SETTINGS:
773 setting = settings.get_setting_by_name(name)
774 setting = settings.get_setting_by_name(name)
774 assert setting.app_settings_value is False
775 assert setting.app_settings_value is False
775 finally:
776 finally:
776 self._cleanup_repo_settings(settings)
777 self._cleanup_repo_settings(settings)
777
778
778 def test_pr_settings_are_not_updated_for_svn(
779 def test_pr_settings_are_not_updated_for_svn(
779 self, autologin_user, backend_svn, csrf_token):
780 self, autologin_user, backend_svn, csrf_token):
780 repo_name = backend_svn.repo_name
781 repo_name = backend_svn.repo_name
781 settings = SettingsModel(repo=repo_name)
782 settings = SettingsModel(repo=repo_name)
782 for name in VcsSettingsModel.GENERAL_SETTINGS:
783 for name in VcsSettingsModel.GENERAL_SETTINGS:
783 settings.create_or_update_setting(name, True, 'bool')
784 settings.create_or_update_setting(name, True, 'bool')
784
785
785 data = self.FORM_DATA.copy()
786 data = self.FORM_DATA.copy()
786 data['csrf_token'] = csrf_token
787 data['csrf_token'] = csrf_token
787 self.app.post(
788 self.app.post(
788 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
789 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
789 try:
790 try:
790 for name in VcsSettingsModel.GENERAL_SETTINGS:
791 for name in VcsSettingsModel.GENERAL_SETTINGS:
791 setting = settings.get_setting_by_name(name)
792 setting = settings.get_setting_by_name(name)
792 assert setting.app_settings_value is True
793 assert setting.app_settings_value is True
793 finally:
794 finally:
794 self._cleanup_repo_settings(settings)
795 self._cleanup_repo_settings(settings)
795
796
796 def test_svn_settings_are_created(
797 def test_svn_settings_are_created(
797 self, autologin_user, backend_svn, csrf_token, settings_util):
798 self, autologin_user, backend_svn, csrf_token, settings_util):
798 repo_name = backend_svn.repo_name
799 repo_name = backend_svn.repo_name
799 data = self.FORM_DATA.copy()
800 data = self.FORM_DATA.copy()
800 data['new_svn_tag'] = 'svn-tag'
801 data['new_svn_tag'] = 'svn-tag'
801 data['new_svn_branch'] = 'svn-branch'
802 data['new_svn_branch'] = 'svn-branch'
802 data['csrf_token'] = csrf_token
803 data['csrf_token'] = csrf_token
803
804
804 # Create few global settings to make sure that uniqueness validators
805 # Create few global settings to make sure that uniqueness validators
805 # are not triggered
806 # are not triggered
806 settings_util.create_rhodecode_ui(
807 settings_util.create_rhodecode_ui(
807 VcsSettingsModel.SVN_BRANCH_SECTION, 'svn-branch')
808 VcsSettingsModel.SVN_BRANCH_SECTION, 'svn-branch')
808 settings_util.create_rhodecode_ui(
809 settings_util.create_rhodecode_ui(
809 VcsSettingsModel.SVN_TAG_SECTION, 'svn-tag')
810 VcsSettingsModel.SVN_TAG_SECTION, 'svn-tag')
810
811
811 self.app.post(
812 self.app.post(
812 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
813 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
813 settings = SettingsModel(repo=repo_name)
814 settings = SettingsModel(repo=repo_name)
814 try:
815 try:
815 svn_branches = settings.get_ui_by_section(
816 svn_branches = settings.get_ui_by_section(
816 VcsSettingsModel.SVN_BRANCH_SECTION)
817 VcsSettingsModel.SVN_BRANCH_SECTION)
817 svn_branch_names = [b.ui_value for b in svn_branches]
818 svn_branch_names = [b.ui_value for b in svn_branches]
818 svn_tags = settings.get_ui_by_section(
819 svn_tags = settings.get_ui_by_section(
819 VcsSettingsModel.SVN_TAG_SECTION)
820 VcsSettingsModel.SVN_TAG_SECTION)
820 svn_tag_names = [b.ui_value for b in svn_tags]
821 svn_tag_names = [b.ui_value for b in svn_tags]
821 assert 'svn-branch' in svn_branch_names
822 assert 'svn-branch' in svn_branch_names
822 assert 'svn-tag' in svn_tag_names
823 assert 'svn-tag' in svn_tag_names
823 finally:
824 finally:
824 self._cleanup_repo_settings(settings)
825 self._cleanup_repo_settings(settings)
825
826
826 def test_svn_settings_are_unique(
827 def test_svn_settings_are_unique(
827 self, autologin_user, backend_svn, csrf_token, settings_util):
828 self, autologin_user, backend_svn, csrf_token, settings_util):
828 repo = backend_svn.repo
829 repo = backend_svn.repo
829 repo_name = repo.repo_name
830 repo_name = repo.repo_name
830 data = self.FORM_DATA.copy()
831 data = self.FORM_DATA.copy()
831 data['new_svn_tag'] = 'test_tag'
832 data['new_svn_tag'] = 'test_tag'
832 data['new_svn_branch'] = 'test_branch'
833 data['new_svn_branch'] = 'test_branch'
833 data['csrf_token'] = csrf_token
834 data['csrf_token'] = csrf_token
834 settings_util.create_repo_rhodecode_ui(
835 settings_util.create_repo_rhodecode_ui(
835 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch')
836 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch')
836 settings_util.create_repo_rhodecode_ui(
837 settings_util.create_repo_rhodecode_ui(
837 repo, VcsSettingsModel.SVN_TAG_SECTION, 'test_tag')
838 repo, VcsSettingsModel.SVN_TAG_SECTION, 'test_tag')
838
839
839 response = self.app.post(
840 response = self.app.post(
840 url('repo_vcs_settings', repo_name=repo_name), data, status=200)
841 url('repo_vcs_settings', repo_name=repo_name), data, status=200)
841 response.mustcontain('Pattern already exists')
842 response.mustcontain('Pattern already exists')
842
843
843 def test_svn_settings_with_empty_values_are_not_created(
844 def test_svn_settings_with_empty_values_are_not_created(
844 self, autologin_user, backend_svn, csrf_token):
845 self, autologin_user, backend_svn, csrf_token):
845 repo_name = backend_svn.repo_name
846 repo_name = backend_svn.repo_name
846 data = self.FORM_DATA.copy()
847 data = self.FORM_DATA.copy()
847 data['csrf_token'] = csrf_token
848 data['csrf_token'] = csrf_token
848 self.app.post(
849 self.app.post(
849 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
850 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
850 settings = SettingsModel(repo=repo_name)
851 settings = SettingsModel(repo=repo_name)
851 try:
852 try:
852 svn_branches = settings.get_ui_by_section(
853 svn_branches = settings.get_ui_by_section(
853 VcsSettingsModel.SVN_BRANCH_SECTION)
854 VcsSettingsModel.SVN_BRANCH_SECTION)
854 svn_tags = settings.get_ui_by_section(
855 svn_tags = settings.get_ui_by_section(
855 VcsSettingsModel.SVN_TAG_SECTION)
856 VcsSettingsModel.SVN_TAG_SECTION)
856 assert len(svn_branches) == 0
857 assert len(svn_branches) == 0
857 assert len(svn_tags) == 0
858 assert len(svn_tags) == 0
858 finally:
859 finally:
859 self._cleanup_repo_settings(settings)
860 self._cleanup_repo_settings(settings)
860
861
861 def test_svn_settings_are_shown_for_svn_repository(
862 def test_svn_settings_are_shown_for_svn_repository(
862 self, autologin_user, backend_svn, csrf_token):
863 self, autologin_user, backend_svn, csrf_token):
863 repo_name = backend_svn.repo_name
864 repo_name = backend_svn.repo_name
864 response = self.app.get(
865 response = self.app.get(
865 url('repo_vcs_settings', repo_name=repo_name), status=200)
866 url('repo_vcs_settings', repo_name=repo_name), status=200)
866 response.mustcontain('Subversion Settings')
867 response.mustcontain('Subversion Settings')
867
868
868 @pytest.mark.skip_backends('svn')
869 @pytest.mark.skip_backends('svn')
869 def test_svn_settings_are_not_created_for_not_svn_repository(
870 def test_svn_settings_are_not_created_for_not_svn_repository(
870 self, autologin_user, backend, csrf_token):
871 self, autologin_user, backend, csrf_token):
871 repo_name = backend.repo_name
872 repo_name = backend.repo_name
872 data = self.FORM_DATA.copy()
873 data = self.FORM_DATA.copy()
873 data['csrf_token'] = csrf_token
874 data['csrf_token'] = csrf_token
874 self.app.post(
875 self.app.post(
875 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
876 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
876 settings = SettingsModel(repo=repo_name)
877 settings = SettingsModel(repo=repo_name)
877 try:
878 try:
878 svn_branches = settings.get_ui_by_section(
879 svn_branches = settings.get_ui_by_section(
879 VcsSettingsModel.SVN_BRANCH_SECTION)
880 VcsSettingsModel.SVN_BRANCH_SECTION)
880 svn_tags = settings.get_ui_by_section(
881 svn_tags = settings.get_ui_by_section(
881 VcsSettingsModel.SVN_TAG_SECTION)
882 VcsSettingsModel.SVN_TAG_SECTION)
882 assert len(svn_branches) == 0
883 assert len(svn_branches) == 0
883 assert len(svn_tags) == 0
884 assert len(svn_tags) == 0
884 finally:
885 finally:
885 self._cleanup_repo_settings(settings)
886 self._cleanup_repo_settings(settings)
886
887
887 @pytest.mark.skip_backends('svn')
888 @pytest.mark.skip_backends('svn')
888 def test_svn_settings_are_shown_only_for_svn_repository(
889 def test_svn_settings_are_shown_only_for_svn_repository(
889 self, autologin_user, backend, csrf_token):
890 self, autologin_user, backend, csrf_token):
890 repo_name = backend.repo_name
891 repo_name = backend.repo_name
891 response = self.app.get(
892 response = self.app.get(
892 url('repo_vcs_settings', repo_name=repo_name), status=200)
893 url('repo_vcs_settings', repo_name=repo_name), status=200)
893 response.mustcontain(no='Subversion Settings')
894 response.mustcontain(no='Subversion Settings')
894
895
895 def test_hg_settings_are_created(
896 def test_hg_settings_are_created(
896 self, autologin_user, backend_hg, csrf_token):
897 self, autologin_user, backend_hg, csrf_token):
897 repo_name = backend_hg.repo_name
898 repo_name = backend_hg.repo_name
898 data = self.FORM_DATA.copy()
899 data = self.FORM_DATA.copy()
899 data['new_svn_tag'] = 'svn-tag'
900 data['new_svn_tag'] = 'svn-tag'
900 data['new_svn_branch'] = 'svn-branch'
901 data['new_svn_branch'] = 'svn-branch'
901 data['csrf_token'] = csrf_token
902 data['csrf_token'] = csrf_token
902 self.app.post(
903 self.app.post(
903 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
904 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
904 settings = SettingsModel(repo=repo_name)
905 settings = SettingsModel(repo=repo_name)
905 try:
906 try:
906 largefiles_ui = settings.get_ui_by_section_and_key(
907 largefiles_ui = settings.get_ui_by_section_and_key(
907 'extensions', 'largefiles')
908 'extensions', 'largefiles')
908 assert largefiles_ui.ui_active is False
909 assert largefiles_ui.ui_active is False
909 phases_ui = settings.get_ui_by_section_and_key(
910 phases_ui = settings.get_ui_by_section_and_key(
910 'phases', 'publish')
911 'phases', 'publish')
911 assert str2bool(phases_ui.ui_value) is False
912 assert str2bool(phases_ui.ui_value) is False
912 finally:
913 finally:
913 self._cleanup_repo_settings(settings)
914 self._cleanup_repo_settings(settings)
914
915
915 def test_hg_settings_are_updated(
916 def test_hg_settings_are_updated(
916 self, autologin_user, backend_hg, csrf_token):
917 self, autologin_user, backend_hg, csrf_token):
917 repo_name = backend_hg.repo_name
918 repo_name = backend_hg.repo_name
918 settings = SettingsModel(repo=repo_name)
919 settings = SettingsModel(repo=repo_name)
919 settings.create_ui_section_value(
920 settings.create_ui_section_value(
920 'extensions', '', key='largefiles', active=True)
921 'extensions', '', key='largefiles', active=True)
921 settings.create_ui_section_value(
922 settings.create_ui_section_value(
922 'phases', '1', key='publish', active=True)
923 'phases', '1', key='publish', active=True)
923
924
924 data = self.FORM_DATA.copy()
925 data = self.FORM_DATA.copy()
925 data['csrf_token'] = csrf_token
926 data['csrf_token'] = csrf_token
926 self.app.post(
927 self.app.post(
927 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
928 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
928 try:
929 try:
929 largefiles_ui = settings.get_ui_by_section_and_key(
930 largefiles_ui = settings.get_ui_by_section_and_key(
930 'extensions', 'largefiles')
931 'extensions', 'largefiles')
931 assert largefiles_ui.ui_active is False
932 assert largefiles_ui.ui_active is False
932 phases_ui = settings.get_ui_by_section_and_key(
933 phases_ui = settings.get_ui_by_section_and_key(
933 'phases', 'publish')
934 'phases', 'publish')
934 assert str2bool(phases_ui.ui_value) is False
935 assert str2bool(phases_ui.ui_value) is False
935 finally:
936 finally:
936 self._cleanup_repo_settings(settings)
937 self._cleanup_repo_settings(settings)
937
938
938 def test_hg_settings_are_shown_for_hg_repository(
939 def test_hg_settings_are_shown_for_hg_repository(
939 self, autologin_user, backend_hg, csrf_token):
940 self, autologin_user, backend_hg, csrf_token):
940 repo_name = backend_hg.repo_name
941 repo_name = backend_hg.repo_name
941 response = self.app.get(
942 response = self.app.get(
942 url('repo_vcs_settings', repo_name=repo_name), status=200)
943 url('repo_vcs_settings', repo_name=repo_name), status=200)
943 response.mustcontain('Mercurial Settings')
944 response.mustcontain('Mercurial Settings')
944
945
945 @pytest.mark.skip_backends('hg')
946 @pytest.mark.skip_backends('hg')
946 def test_hg_settings_are_created_only_for_hg_repository(
947 def test_hg_settings_are_created_only_for_hg_repository(
947 self, autologin_user, backend, csrf_token):
948 self, autologin_user, backend, csrf_token):
948 repo_name = backend.repo_name
949 repo_name = backend.repo_name
949 data = self.FORM_DATA.copy()
950 data = self.FORM_DATA.copy()
950 data['csrf_token'] = csrf_token
951 data['csrf_token'] = csrf_token
951 self.app.post(
952 self.app.post(
952 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
953 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
953 settings = SettingsModel(repo=repo_name)
954 settings = SettingsModel(repo=repo_name)
954 try:
955 try:
955 largefiles_ui = settings.get_ui_by_section_and_key(
956 largefiles_ui = settings.get_ui_by_section_and_key(
956 'extensions', 'largefiles')
957 'extensions', 'largefiles')
957 assert largefiles_ui is None
958 assert largefiles_ui is None
958 phases_ui = settings.get_ui_by_section_and_key(
959 phases_ui = settings.get_ui_by_section_and_key(
959 'phases', 'publish')
960 'phases', 'publish')
960 assert phases_ui is None
961 assert phases_ui is None
961 finally:
962 finally:
962 self._cleanup_repo_settings(settings)
963 self._cleanup_repo_settings(settings)
963
964
964 @pytest.mark.skip_backends('hg')
965 @pytest.mark.skip_backends('hg')
965 def test_hg_settings_are_shown_only_for_hg_repository(
966 def test_hg_settings_are_shown_only_for_hg_repository(
966 self, autologin_user, backend, csrf_token):
967 self, autologin_user, backend, csrf_token):
967 repo_name = backend.repo_name
968 repo_name = backend.repo_name
968 response = self.app.get(
969 response = self.app.get(
969 url('repo_vcs_settings', repo_name=repo_name), status=200)
970 url('repo_vcs_settings', repo_name=repo_name), status=200)
970 response.mustcontain(no='Mercurial Settings')
971 response.mustcontain(no='Mercurial Settings')
971
972
972 @pytest.mark.skip_backends('hg')
973 @pytest.mark.skip_backends('hg')
973 def test_hg_settings_are_updated_only_for_hg_repository(
974 def test_hg_settings_are_updated_only_for_hg_repository(
974 self, autologin_user, backend, csrf_token):
975 self, autologin_user, backend, csrf_token):
975 repo_name = backend.repo_name
976 repo_name = backend.repo_name
976 settings = SettingsModel(repo=repo_name)
977 settings = SettingsModel(repo=repo_name)
977 settings.create_ui_section_value(
978 settings.create_ui_section_value(
978 'extensions', '', key='largefiles', active=True)
979 'extensions', '', key='largefiles', active=True)
979 settings.create_ui_section_value(
980 settings.create_ui_section_value(
980 'phases', '1', key='publish', active=True)
981 'phases', '1', key='publish', active=True)
981
982
982 data = self.FORM_DATA.copy()
983 data = self.FORM_DATA.copy()
983 data['csrf_token'] = csrf_token
984 data['csrf_token'] = csrf_token
984 self.app.post(
985 self.app.post(
985 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
986 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
986 try:
987 try:
987 largefiles_ui = settings.get_ui_by_section_and_key(
988 largefiles_ui = settings.get_ui_by_section_and_key(
988 'extensions', 'largefiles')
989 'extensions', 'largefiles')
989 assert largefiles_ui.ui_active is True
990 assert largefiles_ui.ui_active is True
990 phases_ui = settings.get_ui_by_section_and_key(
991 phases_ui = settings.get_ui_by_section_and_key(
991 'phases', 'publish')
992 'phases', 'publish')
992 assert phases_ui.ui_value == '1'
993 assert phases_ui.ui_value == '1'
993 finally:
994 finally:
994 self._cleanup_repo_settings(settings)
995 self._cleanup_repo_settings(settings)
995
996
996 def test_per_repo_svn_settings_are_displayed(
997 def test_per_repo_svn_settings_are_displayed(
997 self, autologin_user, backend_svn, settings_util):
998 self, autologin_user, backend_svn, settings_util):
998 repo = backend_svn.create_repo()
999 repo = backend_svn.create_repo()
999 repo_name = repo.repo_name
1000 repo_name = repo.repo_name
1000 branches = [
1001 branches = [
1001 settings_util.create_repo_rhodecode_ui(
1002 settings_util.create_repo_rhodecode_ui(
1002 repo, VcsSettingsModel.SVN_BRANCH_SECTION,
1003 repo, VcsSettingsModel.SVN_BRANCH_SECTION,
1003 'branch_{}'.format(i))
1004 'branch_{}'.format(i))
1004 for i in range(10)]
1005 for i in range(10)]
1005 tags = [
1006 tags = [
1006 settings_util.create_repo_rhodecode_ui(
1007 settings_util.create_repo_rhodecode_ui(
1007 repo, VcsSettingsModel.SVN_TAG_SECTION, 'tag_{}'.format(i))
1008 repo, VcsSettingsModel.SVN_TAG_SECTION, 'tag_{}'.format(i))
1008 for i in range(10)]
1009 for i in range(10)]
1009
1010
1010 response = self.app.get(
1011 response = self.app.get(
1011 url('repo_vcs_settings', repo_name=repo_name), status=200)
1012 url('repo_vcs_settings', repo_name=repo_name), status=200)
1012 assert_response = AssertResponse(response)
1013 assert_response = AssertResponse(response)
1013 for branch in branches:
1014 for branch in branches:
1014 css_selector = '[name=branch_value_{}]'.format(branch.ui_id)
1015 css_selector = '[name=branch_value_{}]'.format(branch.ui_id)
1015 element = assert_response.get_element(css_selector)
1016 element = assert_response.get_element(css_selector)
1016 assert element.value == branch.ui_value
1017 assert element.value == branch.ui_value
1017 for tag in tags:
1018 for tag in tags:
1018 css_selector = '[name=tag_ui_value_new_{}]'.format(tag.ui_id)
1019 css_selector = '[name=tag_ui_value_new_{}]'.format(tag.ui_id)
1019 element = assert_response.get_element(css_selector)
1020 element = assert_response.get_element(css_selector)
1020 assert element.value == tag.ui_value
1021 assert element.value == tag.ui_value
1021
1022
1022 def test_per_repo_hg_and_pr_settings_are_not_displayed_for_svn(
1023 def test_per_repo_hg_and_pr_settings_are_not_displayed_for_svn(
1023 self, autologin_user, backend_svn, settings_util):
1024 self, autologin_user, backend_svn, settings_util):
1024 repo = backend_svn.create_repo()
1025 repo = backend_svn.create_repo()
1025 repo_name = repo.repo_name
1026 repo_name = repo.repo_name
1026 response = self.app.get(
1027 response = self.app.get(
1027 url('repo_vcs_settings', repo_name=repo_name), status=200)
1028 url('repo_vcs_settings', repo_name=repo_name), status=200)
1028 response.mustcontain(no='<label>Hooks:</label>')
1029 response.mustcontain(no='<label>Hooks:</label>')
1029 response.mustcontain(no='<label>Pull Request Settings:</label>')
1030 response.mustcontain(no='<label>Pull Request Settings:</label>')
1030
1031
1031 def test_inherit_global_settings_value_is_saved(
1032 def test_inherit_global_settings_value_is_saved(
1032 self, autologin_user, backend, csrf_token):
1033 self, autologin_user, backend, csrf_token):
1033 repo_name = backend.repo_name
1034 repo_name = backend.repo_name
1034 data = self.FORM_DATA.copy()
1035 data = self.FORM_DATA.copy()
1035 data['csrf_token'] = csrf_token
1036 data['csrf_token'] = csrf_token
1036 data['inherit_global_settings'] = True
1037 data['inherit_global_settings'] = True
1037 self.app.post(
1038 self.app.post(
1038 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
1039 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
1039
1040
1040 settings = SettingsModel(repo=repo_name)
1041 settings = SettingsModel(repo=repo_name)
1041 vcs_settings = VcsSettingsModel(repo=repo_name)
1042 vcs_settings = VcsSettingsModel(repo=repo_name)
1042 try:
1043 try:
1043 assert vcs_settings.inherit_global_settings is True
1044 assert vcs_settings.inherit_global_settings is True
1044 finally:
1045 finally:
1045 self._cleanup_repo_settings(settings)
1046 self._cleanup_repo_settings(settings)
1046
1047
1047 def test_repo_cache_is_invalidated_when_settings_are_updated(
1048 def test_repo_cache_is_invalidated_when_settings_are_updated(
1048 self, autologin_user, backend, csrf_token):
1049 self, autologin_user, backend, csrf_token):
1049 repo_name = backend.repo_name
1050 repo_name = backend.repo_name
1050 data = self.FORM_DATA.copy()
1051 data = self.FORM_DATA.copy()
1051 data['csrf_token'] = csrf_token
1052 data['csrf_token'] = csrf_token
1052 data['inherit_global_settings'] = True
1053 data['inherit_global_settings'] = True
1053 settings = SettingsModel(repo=repo_name)
1054 settings = SettingsModel(repo=repo_name)
1054
1055
1055 invalidation_patcher = mock.patch(
1056 invalidation_patcher = mock.patch(
1056 'rhodecode.controllers.admin.repos.ScmModel.mark_for_invalidation')
1057 'rhodecode.controllers.admin.repos.ScmModel.mark_for_invalidation')
1057 with invalidation_patcher as invalidation_mock:
1058 with invalidation_patcher as invalidation_mock:
1058 self.app.post(
1059 self.app.post(
1059 url('repo_vcs_settings', repo_name=repo_name), data,
1060 url('repo_vcs_settings', repo_name=repo_name), data,
1060 status=302)
1061 status=302)
1061 try:
1062 try:
1062 invalidation_mock.assert_called_once_with(repo_name, delete=True)
1063 invalidation_mock.assert_called_once_with(repo_name, delete=True)
1063 finally:
1064 finally:
1064 self._cleanup_repo_settings(settings)
1065 self._cleanup_repo_settings(settings)
1065
1066
1066 def test_other_settings_not_saved_inherit_global_settings_is_true(
1067 def test_other_settings_not_saved_inherit_global_settings_is_true(
1067 self, autologin_user, backend, csrf_token):
1068 self, autologin_user, backend, csrf_token):
1068 repo_name = backend.repo_name
1069 repo_name = backend.repo_name
1069 data = self.FORM_DATA.copy()
1070 data = self.FORM_DATA.copy()
1070 data['csrf_token'] = csrf_token
1071 data['csrf_token'] = csrf_token
1071 data['inherit_global_settings'] = True
1072 data['inherit_global_settings'] = True
1072 self.app.post(
1073 self.app.post(
1073 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
1074 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
1074
1075
1075 settings = SettingsModel(repo=repo_name)
1076 settings = SettingsModel(repo=repo_name)
1076 ui_settings = (
1077 ui_settings = (
1077 VcsSettingsModel.HOOKS_SETTINGS + VcsSettingsModel.HG_SETTINGS)
1078 VcsSettingsModel.HOOKS_SETTINGS + VcsSettingsModel.HG_SETTINGS)
1078
1079
1079 vcs_settings = []
1080 vcs_settings = []
1080 try:
1081 try:
1081 for section, key in ui_settings:
1082 for section, key in ui_settings:
1082 ui = settings.get_ui_by_section_and_key(section, key)
1083 ui = settings.get_ui_by_section_and_key(section, key)
1083 if ui:
1084 if ui:
1084 vcs_settings.append(ui)
1085 vcs_settings.append(ui)
1085 vcs_settings.extend(settings.get_ui_by_section(
1086 vcs_settings.extend(settings.get_ui_by_section(
1086 VcsSettingsModel.SVN_BRANCH_SECTION))
1087 VcsSettingsModel.SVN_BRANCH_SECTION))
1087 vcs_settings.extend(settings.get_ui_by_section(
1088 vcs_settings.extend(settings.get_ui_by_section(
1088 VcsSettingsModel.SVN_TAG_SECTION))
1089 VcsSettingsModel.SVN_TAG_SECTION))
1089 for name in VcsSettingsModel.GENERAL_SETTINGS:
1090 for name in VcsSettingsModel.GENERAL_SETTINGS:
1090 setting = settings.get_setting_by_name(name)
1091 setting = settings.get_setting_by_name(name)
1091 if setting:
1092 if setting:
1092 vcs_settings.append(setting)
1093 vcs_settings.append(setting)
1093 assert vcs_settings == []
1094 assert vcs_settings == []
1094 finally:
1095 finally:
1095 self._cleanup_repo_settings(settings)
1096 self._cleanup_repo_settings(settings)
1096
1097
1097 def test_delete_svn_branch_and_tag_patterns(
1098 def test_delete_svn_branch_and_tag_patterns(
1098 self, autologin_user, backend_svn, settings_util, csrf_token):
1099 self, autologin_user, backend_svn, settings_util, csrf_token):
1099 repo = backend_svn.create_repo()
1100 repo = backend_svn.create_repo()
1100 repo_name = repo.repo_name
1101 repo_name = repo.repo_name
1101 branch = settings_util.create_repo_rhodecode_ui(
1102 branch = settings_util.create_repo_rhodecode_ui(
1102 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch',
1103 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch',
1103 cleanup=False)
1104 cleanup=False)
1104 tag = settings_util.create_repo_rhodecode_ui(
1105 tag = settings_util.create_repo_rhodecode_ui(
1105 repo, VcsSettingsModel.SVN_TAG_SECTION, 'test_tag', cleanup=False)
1106 repo, VcsSettingsModel.SVN_TAG_SECTION, 'test_tag', cleanup=False)
1106 data = {
1107 data = {
1107 '_method': 'delete',
1108 '_method': 'delete',
1108 'csrf_token': csrf_token
1109 'csrf_token': csrf_token
1109 }
1110 }
1110 for id_ in (branch.ui_id, tag.ui_id):
1111 for id_ in (branch.ui_id, tag.ui_id):
1111 data['delete_svn_pattern'] = id_,
1112 data['delete_svn_pattern'] = id_,
1112 self.app.post(
1113 self.app.post(
1113 url('repo_vcs_settings', repo_name=repo_name), data,
1114 url('repo_vcs_settings', repo_name=repo_name), data,
1114 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200)
1115 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200)
1115 settings = VcsSettingsModel(repo=repo_name)
1116 settings = VcsSettingsModel(repo=repo_name)
1116 assert settings.get_repo_svn_branch_patterns() == []
1117 assert settings.get_repo_svn_branch_patterns() == []
1117
1118
1118 def test_delete_svn_branch_requires_repo_admin_permission(
1119 def test_delete_svn_branch_requires_repo_admin_permission(
1119 self, backend_svn, user_util, settings_util, csrf_token):
1120 self, backend_svn, user_util, settings_util, csrf_token):
1120 repo = backend_svn.create_repo()
1121 repo = backend_svn.create_repo()
1121 repo_name = repo.repo_name
1122 repo_name = repo.repo_name
1122
1123
1123 logout_user_session(self.app, csrf_token)
1124 logout_user_session(self.app, csrf_token)
1124 session = login_user_session(
1125 session = login_user_session(
1125 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
1126 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
1126 csrf_token = auth.get_csrf_token(session)
1127 csrf_token = auth.get_csrf_token(session)
1127
1128
1128 repo = Repository.get_by_repo_name(repo_name)
1129 repo = Repository.get_by_repo_name(repo_name)
1129 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
1130 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
1130 user_util.grant_user_permission_to_repo(repo, user, 'repository.admin')
1131 user_util.grant_user_permission_to_repo(repo, user, 'repository.admin')
1131 branch = settings_util.create_repo_rhodecode_ui(
1132 branch = settings_util.create_repo_rhodecode_ui(
1132 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch',
1133 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch',
1133 cleanup=False)
1134 cleanup=False)
1134 data = {
1135 data = {
1135 '_method': 'delete',
1136 '_method': 'delete',
1136 'csrf_token': csrf_token,
1137 'csrf_token': csrf_token,
1137 'delete_svn_pattern': branch.ui_id
1138 'delete_svn_pattern': branch.ui_id
1138 }
1139 }
1139 self.app.post(
1140 self.app.post(
1140 url('repo_vcs_settings', repo_name=repo_name), data,
1141 url('repo_vcs_settings', repo_name=repo_name), data,
1141 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200)
1142 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200)
1142
1143
1143 def test_delete_svn_branch_raises_400_when_not_found(
1144 def test_delete_svn_branch_raises_400_when_not_found(
1144 self, autologin_user, backend_svn, settings_util, csrf_token):
1145 self, autologin_user, backend_svn, settings_util, csrf_token):
1145 repo_name = backend_svn.repo_name
1146 repo_name = backend_svn.repo_name
1146 data = {
1147 data = {
1147 '_method': 'delete',
1148 '_method': 'delete',
1148 'delete_svn_pattern': 123,
1149 'delete_svn_pattern': 123,
1149 'csrf_token': csrf_token
1150 'csrf_token': csrf_token
1150 }
1151 }
1151 self.app.post(
1152 self.app.post(
1152 url('repo_vcs_settings', repo_name=repo_name), data,
1153 url('repo_vcs_settings', repo_name=repo_name), data,
1153 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=400)
1154 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=400)
1154
1155
1155 def test_delete_svn_branch_raises_400_when_no_id_specified(
1156 def test_delete_svn_branch_raises_400_when_no_id_specified(
1156 self, autologin_user, backend_svn, settings_util, csrf_token):
1157 self, autologin_user, backend_svn, settings_util, csrf_token):
1157 repo_name = backend_svn.repo_name
1158 repo_name = backend_svn.repo_name
1158 data = {
1159 data = {
1159 '_method': 'delete',
1160 '_method': 'delete',
1160 'csrf_token': csrf_token
1161 'csrf_token': csrf_token
1161 }
1162 }
1162 self.app.post(
1163 self.app.post(
1163 url('repo_vcs_settings', repo_name=repo_name), data,
1164 url('repo_vcs_settings', repo_name=repo_name), data,
1164 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=400)
1165 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=400)
1165
1166
1166 def _cleanup_repo_settings(self, settings_model):
1167 def _cleanup_repo_settings(self, settings_model):
1167 cleanup = []
1168 cleanup = []
1168 ui_settings = (
1169 ui_settings = (
1169 VcsSettingsModel.HOOKS_SETTINGS + VcsSettingsModel.HG_SETTINGS)
1170 VcsSettingsModel.HOOKS_SETTINGS + VcsSettingsModel.HG_SETTINGS)
1170
1171
1171 for section, key in ui_settings:
1172 for section, key in ui_settings:
1172 ui = settings_model.get_ui_by_section_and_key(section, key)
1173 ui = settings_model.get_ui_by_section_and_key(section, key)
1173 if ui:
1174 if ui:
1174 cleanup.append(ui)
1175 cleanup.append(ui)
1175
1176
1176 cleanup.extend(settings_model.get_ui_by_section(
1177 cleanup.extend(settings_model.get_ui_by_section(
1177 VcsSettingsModel.INHERIT_SETTINGS))
1178 VcsSettingsModel.INHERIT_SETTINGS))
1178 cleanup.extend(settings_model.get_ui_by_section(
1179 cleanup.extend(settings_model.get_ui_by_section(
1179 VcsSettingsModel.SVN_BRANCH_SECTION))
1180 VcsSettingsModel.SVN_BRANCH_SECTION))
1180 cleanup.extend(settings_model.get_ui_by_section(
1181 cleanup.extend(settings_model.get_ui_by_section(
1181 VcsSettingsModel.SVN_TAG_SECTION))
1182 VcsSettingsModel.SVN_TAG_SECTION))
1182
1183
1183 for name in VcsSettingsModel.GENERAL_SETTINGS:
1184 for name in VcsSettingsModel.GENERAL_SETTINGS:
1184 setting = settings_model.get_setting_by_name(name)
1185 setting = settings_model.get_setting_by_name(name)
1185 if setting:
1186 if setting:
1186 cleanup.append(setting)
1187 cleanup.append(setting)
1187
1188
1188 for object_ in cleanup:
1189 for object_ in cleanup:
1189 Session().delete(object_)
1190 Session().delete(object_)
1190 Session().commit()
1191 Session().commit()
1191
1192
1192 def assert_repo_value_equals_global_value(self, response, setting):
1193 def assert_repo_value_equals_global_value(self, response, setting):
1193 assert_response = AssertResponse(response)
1194 assert_response = AssertResponse(response)
1194 global_css_selector = '[name={}_inherited]'.format(setting)
1195 global_css_selector = '[name={}_inherited]'.format(setting)
1195 repo_css_selector = '[name={}]'.format(setting)
1196 repo_css_selector = '[name={}]'.format(setting)
1196 repo_element = assert_response.get_element(repo_css_selector)
1197 repo_element = assert_response.get_element(repo_css_selector)
1197 global_element = assert_response.get_element(global_css_selector)
1198 global_element = assert_response.get_element(global_css_selector)
1198 assert repo_element.value == global_element.value
1199 assert repo_element.value == global_element.value
1199
1200
1200
1201
1201 def _get_permission_for_user(user, repo):
1202 def _get_permission_for_user(user, repo):
1202 perm = UserRepoToPerm.query()\
1203 perm = UserRepoToPerm.query()\
1203 .filter(UserRepoToPerm.repository ==
1204 .filter(UserRepoToPerm.repository ==
1204 Repository.get_by_repo_name(repo))\
1205 Repository.get_by_repo_name(repo))\
1205 .filter(UserRepoToPerm.user == User.get_by_username(user))\
1206 .filter(UserRepoToPerm.user == User.get_by_username(user))\
1206 .all()
1207 .all()
1207 return perm
1208 return perm
@@ -1,665 +1,682 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 import rhodecode
24 import rhodecode
25 from rhodecode.config.routing import ADMIN_PREFIX
25 from rhodecode.config.routing import ADMIN_PREFIX
26 from rhodecode.lib.utils2 import md5
26 from rhodecode.lib.utils2 import md5
27 from rhodecode.model.db import RhodeCodeUi
27 from rhodecode.model.db import RhodeCodeUi
28 from rhodecode.model.meta import Session
28 from rhodecode.model.meta import Session
29 from rhodecode.model.settings import SettingsModel, IssueTrackerSettingsModel
29 from rhodecode.model.settings import SettingsModel, IssueTrackerSettingsModel
30 from rhodecode.tests import url, assert_session_flash
30 from rhodecode.tests import url, assert_session_flash
31 from rhodecode.tests.utils import AssertResponse
31 from rhodecode.tests.utils import AssertResponse
32
32
33
33
34 UPDATE_DATA_QUALNAME = (
34 UPDATE_DATA_QUALNAME = (
35 'rhodecode.apps.admin.views.system_info.AdminSystemInfoSettingsView.get_update_data')
35 'rhodecode.apps.admin.views.system_info.AdminSystemInfoSettingsView.get_update_data')
36
36
37
37
38 @pytest.mark.usefixtures('autologin_user', 'app')
38 @pytest.mark.usefixtures('autologin_user', 'app')
39 class TestAdminSettingsController(object):
39 class TestAdminSettingsController(object):
40
40
41 @pytest.mark.parametrize('urlname', [
41 @pytest.mark.parametrize('urlname', [
42 'admin_settings_vcs',
42 'admin_settings_vcs',
43 'admin_settings_mapping',
43 'admin_settings_mapping',
44 'admin_settings_global',
44 'admin_settings_global',
45 'admin_settings_visual',
45 'admin_settings_visual',
46 'admin_settings_email',
46 'admin_settings_email',
47 'admin_settings_hooks',
47 'admin_settings_hooks',
48 'admin_settings_search',
48 'admin_settings_search',
49 ])
49 ])
50 def test_simple_get(self, urlname, app):
50 def test_simple_get(self, urlname, app):
51 app.get(url(urlname))
51 app.get(url(urlname))
52
52
53 def test_create_custom_hook(self, csrf_token):
53 def test_create_custom_hook(self, csrf_token):
54 response = self.app.post(
54 response = self.app.post(
55 url('admin_settings_hooks'),
55 url('admin_settings_hooks'),
56 params={
56 params={
57 'new_hook_ui_key': 'test_hooks_1',
57 'new_hook_ui_key': 'test_hooks_1',
58 'new_hook_ui_value': 'cd /tmp',
58 'new_hook_ui_value': 'cd /tmp',
59 'csrf_token': csrf_token})
59 'csrf_token': csrf_token})
60
60
61 response = response.follow()
61 response = response.follow()
62 response.mustcontain('test_hooks_1')
62 response.mustcontain('test_hooks_1')
63 response.mustcontain('cd /tmp')
63 response.mustcontain('cd /tmp')
64
64
65 def test_create_custom_hook_delete(self, csrf_token):
65 def test_create_custom_hook_delete(self, csrf_token):
66 response = self.app.post(
66 response = self.app.post(
67 url('admin_settings_hooks'),
67 url('admin_settings_hooks'),
68 params={
68 params={
69 'new_hook_ui_key': 'test_hooks_2',
69 'new_hook_ui_key': 'test_hooks_2',
70 'new_hook_ui_value': 'cd /tmp2',
70 'new_hook_ui_value': 'cd /tmp2',
71 'csrf_token': csrf_token})
71 'csrf_token': csrf_token})
72
72
73 response = response.follow()
73 response = response.follow()
74 response.mustcontain('test_hooks_2')
74 response.mustcontain('test_hooks_2')
75 response.mustcontain('cd /tmp2')
75 response.mustcontain('cd /tmp2')
76
76
77 hook_id = SettingsModel().get_ui_by_key('test_hooks_2').ui_id
77 hook_id = SettingsModel().get_ui_by_key('test_hooks_2').ui_id
78
78
79 # delete
79 # delete
80 self.app.post(
80 self.app.post(
81 url('admin_settings_hooks'),
81 url('admin_settings_hooks'),
82 params={'hook_id': hook_id, 'csrf_token': csrf_token})
82 params={'hook_id': hook_id, 'csrf_token': csrf_token})
83 response = self.app.get(url('admin_settings_hooks'))
83 response = self.app.get(url('admin_settings_hooks'))
84 response.mustcontain(no=['test_hooks_2'])
84 response.mustcontain(no=['test_hooks_2'])
85 response.mustcontain(no=['cd /tmp2'])
85 response.mustcontain(no=['cd /tmp2'])
86
86
87
87
88 @pytest.mark.usefixtures('autologin_user', 'app')
88 @pytest.mark.usefixtures('autologin_user', 'app')
89 class TestAdminSettingsGlobal(object):
89 class TestAdminSettingsGlobal(object):
90
90
91 def test_pre_post_code_code_active(self, csrf_token):
91 def test_pre_post_code_code_active(self, csrf_token):
92 pre_code = 'rc-pre-code-187652122'
92 pre_code = 'rc-pre-code-187652122'
93 post_code = 'rc-postcode-98165231'
93 post_code = 'rc-postcode-98165231'
94
94
95 response = self.post_and_verify_settings({
95 response = self.post_and_verify_settings({
96 'rhodecode_pre_code': pre_code,
96 'rhodecode_pre_code': pre_code,
97 'rhodecode_post_code': post_code,
97 'rhodecode_post_code': post_code,
98 'csrf_token': csrf_token,
98 'csrf_token': csrf_token,
99 })
99 })
100
100
101 response = response.follow()
101 response = response.follow()
102 response.mustcontain(pre_code, post_code)
102 response.mustcontain(pre_code, post_code)
103
103
104 def test_pre_post_code_code_inactive(self, csrf_token):
104 def test_pre_post_code_code_inactive(self, csrf_token):
105 pre_code = 'rc-pre-code-187652122'
105 pre_code = 'rc-pre-code-187652122'
106 post_code = 'rc-postcode-98165231'
106 post_code = 'rc-postcode-98165231'
107 response = self.post_and_verify_settings({
107 response = self.post_and_verify_settings({
108 'rhodecode_pre_code': '',
108 'rhodecode_pre_code': '',
109 'rhodecode_post_code': '',
109 'rhodecode_post_code': '',
110 'csrf_token': csrf_token,
110 'csrf_token': csrf_token,
111 })
111 })
112
112
113 response = response.follow()
113 response = response.follow()
114 response.mustcontain(no=[pre_code, post_code])
114 response.mustcontain(no=[pre_code, post_code])
115
115
116 def test_captcha_activate(self, csrf_token):
116 def test_captcha_activate(self, csrf_token):
117 self.post_and_verify_settings({
117 self.post_and_verify_settings({
118 'rhodecode_captcha_private_key': '1234567890',
118 'rhodecode_captcha_private_key': '1234567890',
119 'rhodecode_captcha_public_key': '1234567890',
119 'rhodecode_captcha_public_key': '1234567890',
120 'csrf_token': csrf_token,
120 'csrf_token': csrf_token,
121 })
121 })
122
122
123 response = self.app.get(ADMIN_PREFIX + '/register')
123 response = self.app.get(ADMIN_PREFIX + '/register')
124 response.mustcontain('captcha')
124 response.mustcontain('captcha')
125
125
126 def test_captcha_deactivate(self, csrf_token):
126 def test_captcha_deactivate(self, csrf_token):
127 self.post_and_verify_settings({
127 self.post_and_verify_settings({
128 'rhodecode_captcha_private_key': '',
128 'rhodecode_captcha_private_key': '',
129 'rhodecode_captcha_public_key': '1234567890',
129 'rhodecode_captcha_public_key': '1234567890',
130 'csrf_token': csrf_token,
130 'csrf_token': csrf_token,
131 })
131 })
132
132
133 response = self.app.get(ADMIN_PREFIX + '/register')
133 response = self.app.get(ADMIN_PREFIX + '/register')
134 response.mustcontain(no=['captcha'])
134 response.mustcontain(no=['captcha'])
135
135
136 def test_title_change(self, csrf_token):
136 def test_title_change(self, csrf_token):
137 old_title = 'RhodeCode'
137 old_title = 'RhodeCode'
138 new_title = old_title + '_changed'
138 new_title = old_title + '_changed'
139
139
140 for new_title in ['Changed', 'Ε»Γ³Ε‚wik', old_title]:
140 for new_title in ['Changed', 'Ε»Γ³Ε‚wik', old_title]:
141 response = self.post_and_verify_settings({
141 response = self.post_and_verify_settings({
142 'rhodecode_title': new_title,
142 'rhodecode_title': new_title,
143 'csrf_token': csrf_token,
143 'csrf_token': csrf_token,
144 })
144 })
145
145
146 response = response.follow()
146 response = response.follow()
147 response.mustcontain(
147 response.mustcontain(
148 """<div class="branding">- %s</div>""" % new_title)
148 """<div class="branding">- %s</div>""" % new_title)
149
149
150 def post_and_verify_settings(self, settings):
150 def post_and_verify_settings(self, settings):
151 old_title = 'RhodeCode'
151 old_title = 'RhodeCode'
152 old_realm = 'RhodeCode authentication'
152 old_realm = 'RhodeCode authentication'
153 params = {
153 params = {
154 'rhodecode_title': old_title,
154 'rhodecode_title': old_title,
155 'rhodecode_realm': old_realm,
155 'rhodecode_realm': old_realm,
156 'rhodecode_pre_code': '',
156 'rhodecode_pre_code': '',
157 'rhodecode_post_code': '',
157 'rhodecode_post_code': '',
158 'rhodecode_captcha_private_key': '',
158 'rhodecode_captcha_private_key': '',
159 'rhodecode_captcha_public_key': '',
159 'rhodecode_captcha_public_key': '',
160 'rhodecode_create_personal_repo_group': False,
160 'rhodecode_create_personal_repo_group': False,
161 'rhodecode_personal_repo_group_pattern': '${username}',
161 'rhodecode_personal_repo_group_pattern': '${username}',
162 }
162 }
163 params.update(settings)
163 params.update(settings)
164 response = self.app.post(url('admin_settings_global'), params=params)
164 response = self.app.post(url('admin_settings_global'), params=params)
165
165
166 assert_session_flash(response, 'Updated application settings')
166 assert_session_flash(response, 'Updated application settings')
167 app_settings = SettingsModel().get_all_settings()
167 app_settings = SettingsModel().get_all_settings()
168 del settings['csrf_token']
168 del settings['csrf_token']
169 for key, value in settings.iteritems():
169 for key, value in settings.iteritems():
170 assert app_settings[key] == value.decode('utf-8')
170 assert app_settings[key] == value.decode('utf-8')
171
171
172 return response
172 return response
173
173
174
174
175 @pytest.mark.usefixtures('autologin_user', 'app')
175 @pytest.mark.usefixtures('autologin_user', 'app')
176 class TestAdminSettingsVcs(object):
176 class TestAdminSettingsVcs(object):
177
177
178 def test_contains_svn_default_patterns(self, app):
178 def test_contains_svn_default_patterns(self, app):
179 response = app.get(url('admin_settings_vcs'))
179 response = app.get(url('admin_settings_vcs'))
180 expected_patterns = [
180 expected_patterns = [
181 '/trunk',
181 '/trunk',
182 '/branches/*',
182 '/branches/*',
183 '/tags/*',
183 '/tags/*',
184 ]
184 ]
185 for pattern in expected_patterns:
185 for pattern in expected_patterns:
186 response.mustcontain(pattern)
186 response.mustcontain(pattern)
187
187
188 def test_add_new_svn_branch_and_tag_pattern(
188 def test_add_new_svn_branch_and_tag_pattern(
189 self, app, backend_svn, form_defaults, disable_sql_cache,
189 self, app, backend_svn, form_defaults, disable_sql_cache,
190 csrf_token):
190 csrf_token):
191 form_defaults.update({
191 form_defaults.update({
192 'new_svn_branch': '/exp/branches/*',
192 'new_svn_branch': '/exp/branches/*',
193 'new_svn_tag': '/important_tags/*',
193 'new_svn_tag': '/important_tags/*',
194 'csrf_token': csrf_token,
194 'csrf_token': csrf_token,
195 })
195 })
196
196
197 response = app.post(
197 response = app.post(
198 url('admin_settings_vcs'), params=form_defaults, status=302)
198 url('admin_settings_vcs'), params=form_defaults, status=302)
199 response = response.follow()
199 response = response.follow()
200
200
201 # Expect to find the new values on the page
201 # Expect to find the new values on the page
202 response.mustcontain('/exp/branches/*')
202 response.mustcontain('/exp/branches/*')
203 response.mustcontain('/important_tags/*')
203 response.mustcontain('/important_tags/*')
204
204
205 # Expect that those patterns are used to match branches and tags now
205 # Expect that those patterns are used to match branches and tags now
206 repo = backend_svn['svn-simple-layout'].scm_instance()
206 repo = backend_svn['svn-simple-layout'].scm_instance()
207 assert 'exp/branches/exp-sphinx-docs' in repo.branches
207 assert 'exp/branches/exp-sphinx-docs' in repo.branches
208 assert 'important_tags/v0.5' in repo.tags
208 assert 'important_tags/v0.5' in repo.tags
209
209
210 def test_add_same_svn_value_twice_shows_an_error_message(
210 def test_add_same_svn_value_twice_shows_an_error_message(
211 self, app, form_defaults, csrf_token, settings_util):
211 self, app, form_defaults, csrf_token, settings_util):
212 settings_util.create_rhodecode_ui('vcs_svn_branch', '/test')
212 settings_util.create_rhodecode_ui('vcs_svn_branch', '/test')
213 settings_util.create_rhodecode_ui('vcs_svn_tag', '/test')
213 settings_util.create_rhodecode_ui('vcs_svn_tag', '/test')
214
214
215 response = app.post(
215 response = app.post(
216 url('admin_settings_vcs'),
216 url('admin_settings_vcs'),
217 params={
217 params={
218 'paths_root_path': form_defaults['paths_root_path'],
218 'paths_root_path': form_defaults['paths_root_path'],
219 'new_svn_branch': '/test',
219 'new_svn_branch': '/test',
220 'new_svn_tag': '/test',
220 'new_svn_tag': '/test',
221 'csrf_token': csrf_token,
221 'csrf_token': csrf_token,
222 },
222 },
223 status=200)
223 status=200)
224
224
225 response.mustcontain("Pattern already exists")
225 response.mustcontain("Pattern already exists")
226 response.mustcontain("Some form inputs contain invalid data.")
226 response.mustcontain("Some form inputs contain invalid data.")
227
227
228 @pytest.mark.parametrize('section', [
228 @pytest.mark.parametrize('section', [
229 'vcs_svn_branch',
229 'vcs_svn_branch',
230 'vcs_svn_tag',
230 'vcs_svn_tag',
231 ])
231 ])
232 def test_delete_svn_patterns(
232 def test_delete_svn_patterns(
233 self, section, app, csrf_token, settings_util):
233 self, section, app, csrf_token, settings_util):
234 setting = settings_util.create_rhodecode_ui(
234 setting = settings_util.create_rhodecode_ui(
235 section, '/test_delete', cleanup=False)
235 section, '/test_delete', cleanup=False)
236
236
237 app.post(
237 app.post(
238 url('admin_settings_vcs'),
238 url('admin_settings_vcs'),
239 params={
239 params={
240 '_method': 'delete',
240 '_method': 'delete',
241 'delete_svn_pattern': setting.ui_id,
241 'delete_svn_pattern': setting.ui_id,
242 'csrf_token': csrf_token},
242 'csrf_token': csrf_token},
243 headers={'X-REQUESTED-WITH': 'XMLHttpRequest'})
243 headers={'X-REQUESTED-WITH': 'XMLHttpRequest'})
244
244
245 @pytest.mark.parametrize('section', [
245 @pytest.mark.parametrize('section', [
246 'vcs_svn_branch',
246 'vcs_svn_branch',
247 'vcs_svn_tag',
247 'vcs_svn_tag',
248 ])
248 ])
249 def test_delete_svn_patterns_raises_400_when_no_xhr(
249 def test_delete_svn_patterns_raises_400_when_no_xhr(
250 self, section, app, csrf_token, settings_util):
250 self, section, app, csrf_token, settings_util):
251 setting = settings_util.create_rhodecode_ui(section, '/test_delete')
251 setting = settings_util.create_rhodecode_ui(section, '/test_delete')
252
252
253 app.post(
253 app.post(
254 url('admin_settings_vcs'),
254 url('admin_settings_vcs'),
255 params={
255 params={
256 '_method': 'delete',
256 '_method': 'delete',
257 'delete_svn_pattern': setting.ui_id,
257 'delete_svn_pattern': setting.ui_id,
258 'csrf_token': csrf_token},
258 'csrf_token': csrf_token},
259 status=400)
259 status=400)
260
260
261 def test_extensions_hgsubversion(self, app, form_defaults, csrf_token):
261 def test_extensions_hgsubversion(self, app, form_defaults, csrf_token):
262 form_defaults.update({
262 form_defaults.update({
263 'csrf_token': csrf_token,
263 'csrf_token': csrf_token,
264 'extensions_hgsubversion': 'True',
264 'extensions_hgsubversion': 'True',
265 })
265 })
266 response = app.post(
266 response = app.post(
267 url('admin_settings_vcs'),
267 url('admin_settings_vcs'),
268 params=form_defaults,
268 params=form_defaults,
269 status=302)
269 status=302)
270
270
271 response = response.follow()
271 response = response.follow()
272 extensions_input = (
272 extensions_input = (
273 '<input id="extensions_hgsubversion" '
273 '<input id="extensions_hgsubversion" '
274 'name="extensions_hgsubversion" type="checkbox" '
274 'name="extensions_hgsubversion" type="checkbox" '
275 'value="True" checked="checked" />')
275 'value="True" checked="checked" />')
276 response.mustcontain(extensions_input)
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 def test_has_a_section_for_pull_request_settings(self, app):
295 def test_has_a_section_for_pull_request_settings(self, app):
279 response = app.get(url('admin_settings_vcs'))
296 response = app.get(url('admin_settings_vcs'))
280 response.mustcontain('Pull Request Settings')
297 response.mustcontain('Pull Request Settings')
281
298
282 def test_has_an_input_for_invalidation_of_inline_comments(
299 def test_has_an_input_for_invalidation_of_inline_comments(
283 self, app):
300 self, app):
284 response = app.get(url('admin_settings_vcs'))
301 response = app.get(url('admin_settings_vcs'))
285 assert_response = AssertResponse(response)
302 assert_response = AssertResponse(response)
286 assert_response.one_element_exists(
303 assert_response.one_element_exists(
287 '[name=rhodecode_use_outdated_comments]')
304 '[name=rhodecode_use_outdated_comments]')
288
305
289 @pytest.mark.parametrize('new_value', [True, False])
306 @pytest.mark.parametrize('new_value', [True, False])
290 def test_allows_to_change_invalidation_of_inline_comments(
307 def test_allows_to_change_invalidation_of_inline_comments(
291 self, app, form_defaults, csrf_token, new_value):
308 self, app, form_defaults, csrf_token, new_value):
292 setting_key = 'use_outdated_comments'
309 setting_key = 'use_outdated_comments'
293 setting = SettingsModel().create_or_update_setting(
310 setting = SettingsModel().create_or_update_setting(
294 setting_key, not new_value, 'bool')
311 setting_key, not new_value, 'bool')
295 Session().add(setting)
312 Session().add(setting)
296 Session().commit()
313 Session().commit()
297
314
298 form_defaults.update({
315 form_defaults.update({
299 'csrf_token': csrf_token,
316 'csrf_token': csrf_token,
300 'rhodecode_use_outdated_comments': str(new_value),
317 'rhodecode_use_outdated_comments': str(new_value),
301 })
318 })
302 response = app.post(
319 response = app.post(
303 url('admin_settings_vcs'),
320 url('admin_settings_vcs'),
304 params=form_defaults,
321 params=form_defaults,
305 status=302)
322 status=302)
306 response = response.follow()
323 response = response.follow()
307 setting = SettingsModel().get_setting_by_name(setting_key)
324 setting = SettingsModel().get_setting_by_name(setting_key)
308 assert setting.app_settings_value is new_value
325 assert setting.app_settings_value is new_value
309
326
310 def test_has_a_section_for_labs_settings_if_enabled(self, app):
327 def test_has_a_section_for_labs_settings_if_enabled(self, app):
311 with mock.patch.dict(
328 with mock.patch.dict(
312 rhodecode.CONFIG, {'labs_settings_active': 'true'}):
329 rhodecode.CONFIG, {'labs_settings_active': 'true'}):
313 response = self.app.get(url('admin_settings_vcs'))
330 response = self.app.get(url('admin_settings_vcs'))
314 response.mustcontain('Labs Settings')
331 response.mustcontain('Labs Settings')
315
332
316 def test_has_not_a_section_for_labs_settings_if_disables(self, app):
333 def test_has_not_a_section_for_labs_settings_if_disables(self, app):
317 with mock.patch.dict(
334 with mock.patch.dict(
318 rhodecode.CONFIG, {'labs_settings_active': 'false'}):
335 rhodecode.CONFIG, {'labs_settings_active': 'false'}):
319 response = self.app.get(url('admin_settings_vcs'))
336 response = self.app.get(url('admin_settings_vcs'))
320 response.mustcontain(no='Labs Settings')
337 response.mustcontain(no='Labs Settings')
321
338
322 @pytest.mark.parametrize('new_value', [True, False])
339 @pytest.mark.parametrize('new_value', [True, False])
323 def test_allows_to_change_hg_rebase_merge_strategy(
340 def test_allows_to_change_hg_rebase_merge_strategy(
324 self, app, form_defaults, csrf_token, new_value):
341 self, app, form_defaults, csrf_token, new_value):
325 setting_key = 'hg_use_rebase_for_merging'
342 setting_key = 'hg_use_rebase_for_merging'
326
343
327 form_defaults.update({
344 form_defaults.update({
328 'csrf_token': csrf_token,
345 'csrf_token': csrf_token,
329 'rhodecode_' + setting_key: str(new_value),
346 'rhodecode_' + setting_key: str(new_value),
330 })
347 })
331
348
332 with mock.patch.dict(
349 with mock.patch.dict(
333 rhodecode.CONFIG, {'labs_settings_active': 'true'}):
350 rhodecode.CONFIG, {'labs_settings_active': 'true'}):
334 app.post(
351 app.post(
335 url('admin_settings_vcs'),
352 url('admin_settings_vcs'),
336 params=form_defaults,
353 params=form_defaults,
337 status=302)
354 status=302)
338
355
339 setting = SettingsModel().get_setting_by_name(setting_key)
356 setting = SettingsModel().get_setting_by_name(setting_key)
340 assert setting.app_settings_value is new_value
357 assert setting.app_settings_value is new_value
341
358
342 @pytest.fixture
359 @pytest.fixture
343 def disable_sql_cache(self, request):
360 def disable_sql_cache(self, request):
344 patcher = mock.patch(
361 patcher = mock.patch(
345 'rhodecode.lib.caching_query.FromCache.process_query')
362 'rhodecode.lib.caching_query.FromCache.process_query')
346 request.addfinalizer(patcher.stop)
363 request.addfinalizer(patcher.stop)
347 patcher.start()
364 patcher.start()
348
365
349 @pytest.fixture
366 @pytest.fixture
350 def form_defaults(self):
367 def form_defaults(self):
351 from rhodecode.controllers.admin.settings import SettingsController
368 from rhodecode.controllers.admin.settings import SettingsController
352 controller = SettingsController()
369 controller = SettingsController()
353 return controller._form_defaults()
370 return controller._form_defaults()
354
371
355 # TODO: johbo: What we really want is to checkpoint before a test run and
372 # TODO: johbo: What we really want is to checkpoint before a test run and
356 # reset the session afterwards.
373 # reset the session afterwards.
357 @pytest.fixture(scope='class', autouse=True)
374 @pytest.fixture(scope='class', autouse=True)
358 def cleanup_settings(self, request, pylonsapp):
375 def cleanup_settings(self, request, pylonsapp):
359 ui_id = RhodeCodeUi.ui_id
376 ui_id = RhodeCodeUi.ui_id
360 original_ids = list(
377 original_ids = list(
361 r.ui_id for r in RhodeCodeUi.query().values(ui_id))
378 r.ui_id for r in RhodeCodeUi.query().values(ui_id))
362
379
363 @request.addfinalizer
380 @request.addfinalizer
364 def cleanup():
381 def cleanup():
365 RhodeCodeUi.query().filter(
382 RhodeCodeUi.query().filter(
366 ui_id.notin_(original_ids)).delete(False)
383 ui_id.notin_(original_ids)).delete(False)
367
384
368
385
369 @pytest.mark.usefixtures('autologin_user', 'app')
386 @pytest.mark.usefixtures('autologin_user', 'app')
370 class TestLabsSettings(object):
387 class TestLabsSettings(object):
371 def test_get_settings_page_disabled(self):
388 def test_get_settings_page_disabled(self):
372 with mock.patch.dict(rhodecode.CONFIG,
389 with mock.patch.dict(rhodecode.CONFIG,
373 {'labs_settings_active': 'false'}):
390 {'labs_settings_active': 'false'}):
374 response = self.app.get(url('admin_settings_labs'), status=302)
391 response = self.app.get(url('admin_settings_labs'), status=302)
375
392
376 assert response.location.endswith(url('admin_settings'))
393 assert response.location.endswith(url('admin_settings'))
377
394
378 def test_get_settings_page_enabled(self):
395 def test_get_settings_page_enabled(self):
379 from rhodecode.controllers.admin import settings
396 from rhodecode.controllers.admin import settings
380 lab_settings = [
397 lab_settings = [
381 settings.LabSetting(
398 settings.LabSetting(
382 key='rhodecode_bool',
399 key='rhodecode_bool',
383 type='bool',
400 type='bool',
384 group='bool group',
401 group='bool group',
385 label='bool label',
402 label='bool label',
386 help='bool help'
403 help='bool help'
387 ),
404 ),
388 settings.LabSetting(
405 settings.LabSetting(
389 key='rhodecode_text',
406 key='rhodecode_text',
390 type='unicode',
407 type='unicode',
391 group='text group',
408 group='text group',
392 label='text label',
409 label='text label',
393 help='text help'
410 help='text help'
394 ),
411 ),
395 ]
412 ]
396 with mock.patch.dict(rhodecode.CONFIG,
413 with mock.patch.dict(rhodecode.CONFIG,
397 {'labs_settings_active': 'true'}):
414 {'labs_settings_active': 'true'}):
398 with mock.patch.object(settings, '_LAB_SETTINGS', lab_settings):
415 with mock.patch.object(settings, '_LAB_SETTINGS', lab_settings):
399 response = self.app.get(url('admin_settings_labs'))
416 response = self.app.get(url('admin_settings_labs'))
400
417
401 assert '<label>bool group:</label>' in response
418 assert '<label>bool group:</label>' in response
402 assert '<label for="rhodecode_bool">bool label</label>' in response
419 assert '<label for="rhodecode_bool">bool label</label>' in response
403 assert '<p class="help-block">bool help</p>' in response
420 assert '<p class="help-block">bool help</p>' in response
404 assert 'name="rhodecode_bool" type="checkbox"' in response
421 assert 'name="rhodecode_bool" type="checkbox"' in response
405
422
406 assert '<label>text group:</label>' in response
423 assert '<label>text group:</label>' in response
407 assert '<label for="rhodecode_text">text label</label>' in response
424 assert '<label for="rhodecode_text">text label</label>' in response
408 assert '<p class="help-block">text help</p>' in response
425 assert '<p class="help-block">text help</p>' in response
409 assert 'name="rhodecode_text" size="60" type="text"' in response
426 assert 'name="rhodecode_text" size="60" type="text"' in response
410
427
411
428
412 @pytest.mark.usefixtures('app')
429 @pytest.mark.usefixtures('app')
413 class TestOpenSourceLicenses(object):
430 class TestOpenSourceLicenses(object):
414
431
415 def _get_url(self):
432 def _get_url(self):
416 return ADMIN_PREFIX + '/settings/open_source'
433 return ADMIN_PREFIX + '/settings/open_source'
417
434
418 def test_records_are_displayed(self, autologin_user):
435 def test_records_are_displayed(self, autologin_user):
419 sample_licenses = {
436 sample_licenses = {
420 "python2.7-pytest-2.7.1": {
437 "python2.7-pytest-2.7.1": {
421 "UNKNOWN": None
438 "UNKNOWN": None
422 },
439 },
423 "python2.7-Markdown-2.6.2": {
440 "python2.7-Markdown-2.6.2": {
424 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
441 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
425 }
442 }
426 }
443 }
427 read_licenses_patch = mock.patch(
444 read_licenses_patch = mock.patch(
428 'rhodecode.apps.admin.views.open_source_licenses.read_opensource_licenses',
445 'rhodecode.apps.admin.views.open_source_licenses.read_opensource_licenses',
429 return_value=sample_licenses)
446 return_value=sample_licenses)
430 with read_licenses_patch:
447 with read_licenses_patch:
431 response = self.app.get(self._get_url(), status=200)
448 response = self.app.get(self._get_url(), status=200)
432
449
433 assert_response = AssertResponse(response)
450 assert_response = AssertResponse(response)
434 assert_response.element_contains(
451 assert_response.element_contains(
435 '.panel-heading', 'Licenses of Third Party Packages')
452 '.panel-heading', 'Licenses of Third Party Packages')
436 for name in sample_licenses:
453 for name in sample_licenses:
437 response.mustcontain(name)
454 response.mustcontain(name)
438 for license in sample_licenses[name]:
455 for license in sample_licenses[name]:
439 assert_response.element_contains('.panel-body', license)
456 assert_response.element_contains('.panel-body', license)
440
457
441 def test_records_can_be_read(self, autologin_user):
458 def test_records_can_be_read(self, autologin_user):
442 response = self.app.get(self._get_url(), status=200)
459 response = self.app.get(self._get_url(), status=200)
443 assert_response = AssertResponse(response)
460 assert_response = AssertResponse(response)
444 assert_response.element_contains(
461 assert_response.element_contains(
445 '.panel-heading', 'Licenses of Third Party Packages')
462 '.panel-heading', 'Licenses of Third Party Packages')
446
463
447 def test_forbidden_when_normal_user(self, autologin_regular_user):
464 def test_forbidden_when_normal_user(self, autologin_regular_user):
448 self.app.get(self._get_url(), status=403)
465 self.app.get(self._get_url(), status=403)
449
466
450
467
451 @pytest.mark.usefixtures('app')
468 @pytest.mark.usefixtures('app')
452 class TestUserSessions(object):
469 class TestUserSessions(object):
453
470
454 def _get_url(self, name='admin_settings_sessions'):
471 def _get_url(self, name='admin_settings_sessions'):
455 return {
472 return {
456 'admin_settings_sessions': ADMIN_PREFIX + '/settings/sessions',
473 'admin_settings_sessions': ADMIN_PREFIX + '/settings/sessions',
457 'admin_settings_sessions_cleanup': ADMIN_PREFIX + '/settings/sessions/cleanup'
474 'admin_settings_sessions_cleanup': ADMIN_PREFIX + '/settings/sessions/cleanup'
458 }[name]
475 }[name]
459
476
460 def test_forbidden_when_normal_user(self, autologin_regular_user):
477 def test_forbidden_when_normal_user(self, autologin_regular_user):
461 self.app.get(self._get_url(), status=403)
478 self.app.get(self._get_url(), status=403)
462
479
463 def test_show_sessions_page(self, autologin_user):
480 def test_show_sessions_page(self, autologin_user):
464 response = self.app.get(self._get_url(), status=200)
481 response = self.app.get(self._get_url(), status=200)
465 response.mustcontain('file')
482 response.mustcontain('file')
466
483
467 def test_cleanup_old_sessions(self, autologin_user, csrf_token):
484 def test_cleanup_old_sessions(self, autologin_user, csrf_token):
468
485
469 post_data = {
486 post_data = {
470 'csrf_token': csrf_token,
487 'csrf_token': csrf_token,
471 'expire_days': '60'
488 'expire_days': '60'
472 }
489 }
473 response = self.app.post(
490 response = self.app.post(
474 self._get_url('admin_settings_sessions_cleanup'), params=post_data,
491 self._get_url('admin_settings_sessions_cleanup'), params=post_data,
475 status=302)
492 status=302)
476 assert_session_flash(response, 'Cleaned up old sessions')
493 assert_session_flash(response, 'Cleaned up old sessions')
477
494
478
495
479 @pytest.mark.usefixtures('app')
496 @pytest.mark.usefixtures('app')
480 class TestAdminSystemInfo(object):
497 class TestAdminSystemInfo(object):
481 def _get_url(self, name='admin_settings_system'):
498 def _get_url(self, name='admin_settings_system'):
482 return {
499 return {
483 'admin_settings_system': ADMIN_PREFIX + '/settings/system',
500 'admin_settings_system': ADMIN_PREFIX + '/settings/system',
484 'admin_settings_system_update': ADMIN_PREFIX + '/settings/system/updates',
501 'admin_settings_system_update': ADMIN_PREFIX + '/settings/system/updates',
485 }[name]
502 }[name]
486
503
487 def test_forbidden_when_normal_user(self, autologin_regular_user):
504 def test_forbidden_when_normal_user(self, autologin_regular_user):
488 self.app.get(self._get_url(), status=403)
505 self.app.get(self._get_url(), status=403)
489
506
490 def test_system_info_page(self, autologin_user):
507 def test_system_info_page(self, autologin_user):
491 response = self.app.get(self._get_url())
508 response = self.app.get(self._get_url())
492 response.mustcontain('RhodeCode Community Edition, version {}'.format(
509 response.mustcontain('RhodeCode Community Edition, version {}'.format(
493 rhodecode.__version__))
510 rhodecode.__version__))
494
511
495 def test_system_update_new_version(self, autologin_user):
512 def test_system_update_new_version(self, autologin_user):
496 update_data = {
513 update_data = {
497 'versions': [
514 'versions': [
498 {
515 {
499 'version': '100.3.1415926535',
516 'version': '100.3.1415926535',
500 'general': 'The latest version we are ever going to ship'
517 'general': 'The latest version we are ever going to ship'
501 },
518 },
502 {
519 {
503 'version': '0.0.0',
520 'version': '0.0.0',
504 'general': 'The first version we ever shipped'
521 'general': 'The first version we ever shipped'
505 }
522 }
506 ]
523 ]
507 }
524 }
508 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
525 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
509 response = self.app.get(self._get_url('admin_settings_system_update'))
526 response = self.app.get(self._get_url('admin_settings_system_update'))
510 response.mustcontain('A <b>new version</b> is available')
527 response.mustcontain('A <b>new version</b> is available')
511
528
512 def test_system_update_nothing_new(self, autologin_user):
529 def test_system_update_nothing_new(self, autologin_user):
513 update_data = {
530 update_data = {
514 'versions': [
531 'versions': [
515 {
532 {
516 'version': '0.0.0',
533 'version': '0.0.0',
517 'general': 'The first version we ever shipped'
534 'general': 'The first version we ever shipped'
518 }
535 }
519 ]
536 ]
520 }
537 }
521 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
538 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
522 response = self.app.get(self._get_url('admin_settings_system_update'))
539 response = self.app.get(self._get_url('admin_settings_system_update'))
523 response.mustcontain(
540 response.mustcontain(
524 'You already have the <b>latest</b> stable version.')
541 'You already have the <b>latest</b> stable version.')
525
542
526 def test_system_update_bad_response(self, autologin_user):
543 def test_system_update_bad_response(self, autologin_user):
527 with mock.patch(UPDATE_DATA_QUALNAME, side_effect=ValueError('foo')):
544 with mock.patch(UPDATE_DATA_QUALNAME, side_effect=ValueError('foo')):
528 response = self.app.get(self._get_url('admin_settings_system_update'))
545 response = self.app.get(self._get_url('admin_settings_system_update'))
529 response.mustcontain(
546 response.mustcontain(
530 'Bad data sent from update server')
547 'Bad data sent from update server')
531
548
532
549
533 @pytest.mark.usefixtures("app")
550 @pytest.mark.usefixtures("app")
534 class TestAdminSettingsIssueTracker(object):
551 class TestAdminSettingsIssueTracker(object):
535 RC_PREFIX = 'rhodecode_'
552 RC_PREFIX = 'rhodecode_'
536 SHORT_PATTERN_KEY = 'issuetracker_pat_'
553 SHORT_PATTERN_KEY = 'issuetracker_pat_'
537 PATTERN_KEY = RC_PREFIX + SHORT_PATTERN_KEY
554 PATTERN_KEY = RC_PREFIX + SHORT_PATTERN_KEY
538
555
539 def test_issuetracker_index(self, autologin_user):
556 def test_issuetracker_index(self, autologin_user):
540 response = self.app.get(url('admin_settings_issuetracker'))
557 response = self.app.get(url('admin_settings_issuetracker'))
541 assert response.status_code == 200
558 assert response.status_code == 200
542
559
543 def test_add_empty_issuetracker_pattern(
560 def test_add_empty_issuetracker_pattern(
544 self, request, autologin_user, csrf_token):
561 self, request, autologin_user, csrf_token):
545 post_url = url('admin_settings_issuetracker_save')
562 post_url = url('admin_settings_issuetracker_save')
546 post_data = {
563 post_data = {
547 'csrf_token': csrf_token
564 'csrf_token': csrf_token
548 }
565 }
549 self.app.post(post_url, post_data, status=302)
566 self.app.post(post_url, post_data, status=302)
550
567
551 def test_add_issuetracker_pattern(
568 def test_add_issuetracker_pattern(
552 self, request, autologin_user, csrf_token):
569 self, request, autologin_user, csrf_token):
553 pattern = 'issuetracker_pat'
570 pattern = 'issuetracker_pat'
554 another_pattern = pattern+'1'
571 another_pattern = pattern+'1'
555 post_url = url('admin_settings_issuetracker_save')
572 post_url = url('admin_settings_issuetracker_save')
556 post_data = {
573 post_data = {
557 'new_pattern_pattern_0': pattern,
574 'new_pattern_pattern_0': pattern,
558 'new_pattern_url_0': 'url',
575 'new_pattern_url_0': 'url',
559 'new_pattern_prefix_0': 'prefix',
576 'new_pattern_prefix_0': 'prefix',
560 'new_pattern_description_0': 'description',
577 'new_pattern_description_0': 'description',
561 'new_pattern_pattern_1': another_pattern,
578 'new_pattern_pattern_1': another_pattern,
562 'new_pattern_url_1': 'url1',
579 'new_pattern_url_1': 'url1',
563 'new_pattern_prefix_1': 'prefix1',
580 'new_pattern_prefix_1': 'prefix1',
564 'new_pattern_description_1': 'description1',
581 'new_pattern_description_1': 'description1',
565 'csrf_token': csrf_token
582 'csrf_token': csrf_token
566 }
583 }
567 self.app.post(post_url, post_data, status=302)
584 self.app.post(post_url, post_data, status=302)
568 settings = SettingsModel().get_all_settings()
585 settings = SettingsModel().get_all_settings()
569 self.uid = md5(pattern)
586 self.uid = md5(pattern)
570 assert settings[self.PATTERN_KEY+self.uid] == pattern
587 assert settings[self.PATTERN_KEY+self.uid] == pattern
571 self.another_uid = md5(another_pattern)
588 self.another_uid = md5(another_pattern)
572 assert settings[self.PATTERN_KEY+self.another_uid] == another_pattern
589 assert settings[self.PATTERN_KEY+self.another_uid] == another_pattern
573
590
574 @request.addfinalizer
591 @request.addfinalizer
575 def cleanup():
592 def cleanup():
576 defaults = SettingsModel().get_all_settings()
593 defaults = SettingsModel().get_all_settings()
577
594
578 entries = [name for name in defaults if (
595 entries = [name for name in defaults if (
579 (self.uid in name) or (self.another_uid) in name)]
596 (self.uid in name) or (self.another_uid) in name)]
580 start = len(self.RC_PREFIX)
597 start = len(self.RC_PREFIX)
581 for del_key in entries:
598 for del_key in entries:
582 # TODO: anderson: get_by_name needs name without prefix
599 # TODO: anderson: get_by_name needs name without prefix
583 entry = SettingsModel().get_setting_by_name(del_key[start:])
600 entry = SettingsModel().get_setting_by_name(del_key[start:])
584 Session().delete(entry)
601 Session().delete(entry)
585
602
586 Session().commit()
603 Session().commit()
587
604
588 def test_edit_issuetracker_pattern(
605 def test_edit_issuetracker_pattern(
589 self, autologin_user, backend, csrf_token, request):
606 self, autologin_user, backend, csrf_token, request):
590 old_pattern = 'issuetracker_pat'
607 old_pattern = 'issuetracker_pat'
591 old_uid = md5(old_pattern)
608 old_uid = md5(old_pattern)
592 pattern = 'issuetracker_pat_new'
609 pattern = 'issuetracker_pat_new'
593 self.new_uid = md5(pattern)
610 self.new_uid = md5(pattern)
594
611
595 SettingsModel().create_or_update_setting(
612 SettingsModel().create_or_update_setting(
596 self.SHORT_PATTERN_KEY+old_uid, old_pattern, 'unicode')
613 self.SHORT_PATTERN_KEY+old_uid, old_pattern, 'unicode')
597
614
598 post_url = url('admin_settings_issuetracker_save')
615 post_url = url('admin_settings_issuetracker_save')
599 post_data = {
616 post_data = {
600 'new_pattern_pattern_0': pattern,
617 'new_pattern_pattern_0': pattern,
601 'new_pattern_url_0': 'url',
618 'new_pattern_url_0': 'url',
602 'new_pattern_prefix_0': 'prefix',
619 'new_pattern_prefix_0': 'prefix',
603 'new_pattern_description_0': 'description',
620 'new_pattern_description_0': 'description',
604 'uid': old_uid,
621 'uid': old_uid,
605 'csrf_token': csrf_token
622 'csrf_token': csrf_token
606 }
623 }
607 self.app.post(post_url, post_data, status=302)
624 self.app.post(post_url, post_data, status=302)
608 settings = SettingsModel().get_all_settings()
625 settings = SettingsModel().get_all_settings()
609 assert settings[self.PATTERN_KEY+self.new_uid] == pattern
626 assert settings[self.PATTERN_KEY+self.new_uid] == pattern
610 assert self.PATTERN_KEY+old_uid not in settings
627 assert self.PATTERN_KEY+old_uid not in settings
611
628
612 @request.addfinalizer
629 @request.addfinalizer
613 def cleanup():
630 def cleanup():
614 IssueTrackerSettingsModel().delete_entries(self.new_uid)
631 IssueTrackerSettingsModel().delete_entries(self.new_uid)
615
632
616 def test_replace_issuetracker_pattern_description(
633 def test_replace_issuetracker_pattern_description(
617 self, autologin_user, csrf_token, request, settings_util):
634 self, autologin_user, csrf_token, request, settings_util):
618 prefix = 'issuetracker'
635 prefix = 'issuetracker'
619 pattern = 'issuetracker_pat'
636 pattern = 'issuetracker_pat'
620 self.uid = md5(pattern)
637 self.uid = md5(pattern)
621 pattern_key = '_'.join([prefix, 'pat', self.uid])
638 pattern_key = '_'.join([prefix, 'pat', self.uid])
622 rc_pattern_key = '_'.join(['rhodecode', pattern_key])
639 rc_pattern_key = '_'.join(['rhodecode', pattern_key])
623 desc_key = '_'.join([prefix, 'desc', self.uid])
640 desc_key = '_'.join([prefix, 'desc', self.uid])
624 rc_desc_key = '_'.join(['rhodecode', desc_key])
641 rc_desc_key = '_'.join(['rhodecode', desc_key])
625 new_description = 'new_description'
642 new_description = 'new_description'
626
643
627 settings_util.create_rhodecode_setting(
644 settings_util.create_rhodecode_setting(
628 pattern_key, pattern, 'unicode', cleanup=False)
645 pattern_key, pattern, 'unicode', cleanup=False)
629 settings_util.create_rhodecode_setting(
646 settings_util.create_rhodecode_setting(
630 desc_key, 'old description', 'unicode', cleanup=False)
647 desc_key, 'old description', 'unicode', cleanup=False)
631
648
632 post_url = url('admin_settings_issuetracker_save')
649 post_url = url('admin_settings_issuetracker_save')
633 post_data = {
650 post_data = {
634 'new_pattern_pattern_0': pattern,
651 'new_pattern_pattern_0': pattern,
635 'new_pattern_url_0': 'url',
652 'new_pattern_url_0': 'url',
636 'new_pattern_prefix_0': 'prefix',
653 'new_pattern_prefix_0': 'prefix',
637 'new_pattern_description_0': new_description,
654 'new_pattern_description_0': new_description,
638 'uid': self.uid,
655 'uid': self.uid,
639 'csrf_token': csrf_token
656 'csrf_token': csrf_token
640 }
657 }
641 self.app.post(post_url, post_data, status=302)
658 self.app.post(post_url, post_data, status=302)
642 settings = SettingsModel().get_all_settings()
659 settings = SettingsModel().get_all_settings()
643 assert settings[rc_pattern_key] == pattern
660 assert settings[rc_pattern_key] == pattern
644 assert settings[rc_desc_key] == new_description
661 assert settings[rc_desc_key] == new_description
645
662
646 @request.addfinalizer
663 @request.addfinalizer
647 def cleanup():
664 def cleanup():
648 IssueTrackerSettingsModel().delete_entries(self.uid)
665 IssueTrackerSettingsModel().delete_entries(self.uid)
649
666
650 def test_delete_issuetracker_pattern(
667 def test_delete_issuetracker_pattern(
651 self, autologin_user, backend, csrf_token, settings_util):
668 self, autologin_user, backend, csrf_token, settings_util):
652 pattern = 'issuetracker_pat'
669 pattern = 'issuetracker_pat'
653 uid = md5(pattern)
670 uid = md5(pattern)
654 settings_util.create_rhodecode_setting(
671 settings_util.create_rhodecode_setting(
655 self.SHORT_PATTERN_KEY+uid, pattern, 'unicode', cleanup=False)
672 self.SHORT_PATTERN_KEY+uid, pattern, 'unicode', cleanup=False)
656
673
657 post_url = url('admin_issuetracker_delete')
674 post_url = url('admin_issuetracker_delete')
658 post_data = {
675 post_data = {
659 '_method': 'delete',
676 '_method': 'delete',
660 'uid': uid,
677 'uid': uid,
661 'csrf_token': csrf_token
678 'csrf_token': csrf_token
662 }
679 }
663 self.app.post(post_url, post_data, status=302)
680 self.app.post(post_url, post_data, status=302)
664 settings = SettingsModel().get_all_settings()
681 settings = SettingsModel().get_all_settings()
665 assert 'rhodecode_%s%s' % (self.SHORT_PATTERN_KEY, uid) not in settings
682 assert 'rhodecode_%s%s' % (self.SHORT_PATTERN_KEY, uid) not in settings
@@ -1,1061 +1,1069 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.lib.utils2 import str2bool
24 from rhodecode.lib.utils2 import str2bool
25 from rhodecode.model.meta import Session
25 from rhodecode.model.meta import Session
26 from rhodecode.model.settings import VcsSettingsModel, UiSetting
26 from rhodecode.model.settings import VcsSettingsModel, UiSetting
27
27
28
28
29 HOOKS_FORM_DATA = {
29 HOOKS_FORM_DATA = {
30 'hooks_changegroup_repo_size': True,
30 'hooks_changegroup_repo_size': True,
31 'hooks_changegroup_push_logger': True,
31 'hooks_changegroup_push_logger': True,
32 'hooks_outgoing_pull_logger': True
32 'hooks_outgoing_pull_logger': True
33 }
33 }
34
34
35 SVN_FORM_DATA = {
35 SVN_FORM_DATA = {
36 'new_svn_branch': 'test-branch',
36 'new_svn_branch': 'test-branch',
37 'new_svn_tag': 'test-tag'
37 'new_svn_tag': 'test-tag'
38 }
38 }
39
39
40 GENERAL_FORM_DATA = {
40 GENERAL_FORM_DATA = {
41 'rhodecode_pr_merge_enabled': True,
41 'rhodecode_pr_merge_enabled': True,
42 'rhodecode_use_outdated_comments': True,
42 'rhodecode_use_outdated_comments': True,
43 'rhodecode_hg_use_rebase_for_merging': True,
43 'rhodecode_hg_use_rebase_for_merging': True,
44 }
44 }
45
45
46
46
47 class TestInheritGlobalSettingsProperty(object):
47 class TestInheritGlobalSettingsProperty(object):
48 def test_get_raises_exception_when_repository_not_specified(self):
48 def test_get_raises_exception_when_repository_not_specified(self):
49 model = VcsSettingsModel()
49 model = VcsSettingsModel()
50 with pytest.raises(Exception) as exc_info:
50 with pytest.raises(Exception) as exc_info:
51 model.inherit_global_settings
51 model.inherit_global_settings
52 assert exc_info.value.message == 'Repository is not specified'
52 assert exc_info.value.message == 'Repository is not specified'
53
53
54 def test_true_is_returned_when_value_is_not_found(self, repo_stub):
54 def test_true_is_returned_when_value_is_not_found(self, repo_stub):
55 model = VcsSettingsModel(repo=repo_stub.repo_name)
55 model = VcsSettingsModel(repo=repo_stub.repo_name)
56 assert model.inherit_global_settings is True
56 assert model.inherit_global_settings is True
57
57
58 def test_value_is_returned(self, repo_stub, settings_util):
58 def test_value_is_returned(self, repo_stub, settings_util):
59 model = VcsSettingsModel(repo=repo_stub.repo_name)
59 model = VcsSettingsModel(repo=repo_stub.repo_name)
60 settings_util.create_repo_rhodecode_setting(
60 settings_util.create_repo_rhodecode_setting(
61 repo_stub, VcsSettingsModel.INHERIT_SETTINGS, False, 'bool')
61 repo_stub, VcsSettingsModel.INHERIT_SETTINGS, False, 'bool')
62 assert model.inherit_global_settings is False
62 assert model.inherit_global_settings is False
63
63
64 def test_value_is_set(self, repo_stub):
64 def test_value_is_set(self, repo_stub):
65 model = VcsSettingsModel(repo=repo_stub.repo_name)
65 model = VcsSettingsModel(repo=repo_stub.repo_name)
66 model.inherit_global_settings = False
66 model.inherit_global_settings = False
67 setting = model.repo_settings.get_setting_by_name(
67 setting = model.repo_settings.get_setting_by_name(
68 VcsSettingsModel.INHERIT_SETTINGS)
68 VcsSettingsModel.INHERIT_SETTINGS)
69 try:
69 try:
70 assert setting.app_settings_type == 'bool'
70 assert setting.app_settings_type == 'bool'
71 assert setting.app_settings_value is False
71 assert setting.app_settings_value is False
72 finally:
72 finally:
73 Session().delete(setting)
73 Session().delete(setting)
74 Session().commit()
74 Session().commit()
75
75
76 def test_set_raises_exception_when_repository_not_specified(self):
76 def test_set_raises_exception_when_repository_not_specified(self):
77 model = VcsSettingsModel()
77 model = VcsSettingsModel()
78 with pytest.raises(Exception) as exc_info:
78 with pytest.raises(Exception) as exc_info:
79 model.inherit_global_settings = False
79 model.inherit_global_settings = False
80 assert exc_info.value.message == 'Repository is not specified'
80 assert exc_info.value.message == 'Repository is not specified'
81
81
82
82
83 class TestVcsSettingsModel(object):
83 class TestVcsSettingsModel(object):
84 def test_global_svn_branch_patterns(self):
84 def test_global_svn_branch_patterns(self):
85 model = VcsSettingsModel()
85 model = VcsSettingsModel()
86 expected_result = {'test': 'test'}
86 expected_result = {'test': 'test'}
87 with mock.patch.object(model, 'global_settings') as settings_mock:
87 with mock.patch.object(model, 'global_settings') as settings_mock:
88 get_settings = settings_mock.get_ui_by_section
88 get_settings = settings_mock.get_ui_by_section
89 get_settings.return_value = expected_result
89 get_settings.return_value = expected_result
90 settings_mock.return_value = expected_result
90 settings_mock.return_value = expected_result
91 result = model.get_global_svn_branch_patterns()
91 result = model.get_global_svn_branch_patterns()
92
92
93 get_settings.assert_called_once_with(model.SVN_BRANCH_SECTION)
93 get_settings.assert_called_once_with(model.SVN_BRANCH_SECTION)
94 assert expected_result == result
94 assert expected_result == result
95
95
96 def test_repo_svn_branch_patterns(self):
96 def test_repo_svn_branch_patterns(self):
97 model = VcsSettingsModel()
97 model = VcsSettingsModel()
98 expected_result = {'test': 'test'}
98 expected_result = {'test': 'test'}
99 with mock.patch.object(model, 'repo_settings') as settings_mock:
99 with mock.patch.object(model, 'repo_settings') as settings_mock:
100 get_settings = settings_mock.get_ui_by_section
100 get_settings = settings_mock.get_ui_by_section
101 get_settings.return_value = expected_result
101 get_settings.return_value = expected_result
102 settings_mock.return_value = expected_result
102 settings_mock.return_value = expected_result
103 result = model.get_repo_svn_branch_patterns()
103 result = model.get_repo_svn_branch_patterns()
104
104
105 get_settings.assert_called_once_with(model.SVN_BRANCH_SECTION)
105 get_settings.assert_called_once_with(model.SVN_BRANCH_SECTION)
106 assert expected_result == result
106 assert expected_result == result
107
107
108 def test_repo_svn_branch_patterns_raises_exception_when_repo_is_not_set(
108 def test_repo_svn_branch_patterns_raises_exception_when_repo_is_not_set(
109 self):
109 self):
110 model = VcsSettingsModel()
110 model = VcsSettingsModel()
111 with pytest.raises(Exception) as exc_info:
111 with pytest.raises(Exception) as exc_info:
112 model.get_repo_svn_branch_patterns()
112 model.get_repo_svn_branch_patterns()
113 assert exc_info.value.message == 'Repository is not specified'
113 assert exc_info.value.message == 'Repository is not specified'
114
114
115 def test_global_svn_tag_patterns(self):
115 def test_global_svn_tag_patterns(self):
116 model = VcsSettingsModel()
116 model = VcsSettingsModel()
117 expected_result = {'test': 'test'}
117 expected_result = {'test': 'test'}
118 with mock.patch.object(model, 'global_settings') as settings_mock:
118 with mock.patch.object(model, 'global_settings') as settings_mock:
119 get_settings = settings_mock.get_ui_by_section
119 get_settings = settings_mock.get_ui_by_section
120 get_settings.return_value = expected_result
120 get_settings.return_value = expected_result
121 settings_mock.return_value = expected_result
121 settings_mock.return_value = expected_result
122 result = model.get_global_svn_tag_patterns()
122 result = model.get_global_svn_tag_patterns()
123
123
124 get_settings.assert_called_once_with(model.SVN_TAG_SECTION)
124 get_settings.assert_called_once_with(model.SVN_TAG_SECTION)
125 assert expected_result == result
125 assert expected_result == result
126
126
127 def test_repo_svn_tag_patterns(self):
127 def test_repo_svn_tag_patterns(self):
128 model = VcsSettingsModel()
128 model = VcsSettingsModel()
129 expected_result = {'test': 'test'}
129 expected_result = {'test': 'test'}
130 with mock.patch.object(model, 'repo_settings') as settings_mock:
130 with mock.patch.object(model, 'repo_settings') as settings_mock:
131 get_settings = settings_mock.get_ui_by_section
131 get_settings = settings_mock.get_ui_by_section
132 get_settings.return_value = expected_result
132 get_settings.return_value = expected_result
133 settings_mock.return_value = expected_result
133 settings_mock.return_value = expected_result
134 result = model.get_repo_svn_tag_patterns()
134 result = model.get_repo_svn_tag_patterns()
135
135
136 get_settings.assert_called_once_with(model.SVN_TAG_SECTION)
136 get_settings.assert_called_once_with(model.SVN_TAG_SECTION)
137 assert expected_result == result
137 assert expected_result == result
138
138
139 def test_repo_svn_tag_patterns_raises_exception_when_repo_is_not_set(self):
139 def test_repo_svn_tag_patterns_raises_exception_when_repo_is_not_set(self):
140 model = VcsSettingsModel()
140 model = VcsSettingsModel()
141 with pytest.raises(Exception) as exc_info:
141 with pytest.raises(Exception) as exc_info:
142 model.get_repo_svn_tag_patterns()
142 model.get_repo_svn_tag_patterns()
143 assert exc_info.value.message == 'Repository is not specified'
143 assert exc_info.value.message == 'Repository is not specified'
144
144
145 def test_get_global_settings(self):
145 def test_get_global_settings(self):
146 expected_result = {'test': 'test'}
146 expected_result = {'test': 'test'}
147 model = VcsSettingsModel()
147 model = VcsSettingsModel()
148 with mock.patch.object(model, '_collect_all_settings') as collect_mock:
148 with mock.patch.object(model, '_collect_all_settings') as collect_mock:
149 collect_mock.return_value = expected_result
149 collect_mock.return_value = expected_result
150 result = model.get_global_settings()
150 result = model.get_global_settings()
151
151
152 collect_mock.assert_called_once_with(global_=True)
152 collect_mock.assert_called_once_with(global_=True)
153 assert result == expected_result
153 assert result == expected_result
154
154
155 def test_get_repo_settings(self, repo_stub):
155 def test_get_repo_settings(self, repo_stub):
156 model = VcsSettingsModel(repo=repo_stub.repo_name)
156 model = VcsSettingsModel(repo=repo_stub.repo_name)
157 expected_result = {'test': 'test'}
157 expected_result = {'test': 'test'}
158 with mock.patch.object(model, '_collect_all_settings') as collect_mock:
158 with mock.patch.object(model, '_collect_all_settings') as collect_mock:
159 collect_mock.return_value = expected_result
159 collect_mock.return_value = expected_result
160 result = model.get_repo_settings()
160 result = model.get_repo_settings()
161
161
162 collect_mock.assert_called_once_with(global_=False)
162 collect_mock.assert_called_once_with(global_=False)
163 assert result == expected_result
163 assert result == expected_result
164
164
165 @pytest.mark.parametrize('settings, global_', [
165 @pytest.mark.parametrize('settings, global_', [
166 ('global_settings', True),
166 ('global_settings', True),
167 ('repo_settings', False)
167 ('repo_settings', False)
168 ])
168 ])
169 def test_collect_all_settings(self, settings, global_):
169 def test_collect_all_settings(self, settings, global_):
170 model = VcsSettingsModel()
170 model = VcsSettingsModel()
171 result_mock = self._mock_result()
171 result_mock = self._mock_result()
172
172
173 settings_patch = mock.patch.object(model, settings)
173 settings_patch = mock.patch.object(model, settings)
174 with settings_patch as settings_mock:
174 with settings_patch as settings_mock:
175 settings_mock.get_ui_by_section_and_key.return_value = result_mock
175 settings_mock.get_ui_by_section_and_key.return_value = result_mock
176 settings_mock.get_setting_by_name.return_value = result_mock
176 settings_mock.get_setting_by_name.return_value = result_mock
177 result = model._collect_all_settings(global_=global_)
177 result = model._collect_all_settings(global_=global_)
178
178
179 ui_settings = model.HG_SETTINGS + model.GIT_SETTINGS + model.HOOKS_SETTINGS
179 ui_settings = model.HG_SETTINGS + model.GIT_SETTINGS + model.HOOKS_SETTINGS
180 self._assert_get_settings_calls(
180 self._assert_get_settings_calls(
181 settings_mock, ui_settings, model.GENERAL_SETTINGS)
181 settings_mock, ui_settings, model.GENERAL_SETTINGS)
182 self._assert_collect_all_settings_result(
182 self._assert_collect_all_settings_result(
183 ui_settings, model.GENERAL_SETTINGS, result)
183 ui_settings, model.GENERAL_SETTINGS, result)
184
184
185 @pytest.mark.parametrize('settings, global_', [
185 @pytest.mark.parametrize('settings, global_', [
186 ('global_settings', True),
186 ('global_settings', True),
187 ('repo_settings', False)
187 ('repo_settings', False)
188 ])
188 ])
189 def test_collect_all_settings_without_empty_value(self, settings, global_):
189 def test_collect_all_settings_without_empty_value(self, settings, global_):
190 model = VcsSettingsModel()
190 model = VcsSettingsModel()
191
191
192 settings_patch = mock.patch.object(model, settings)
192 settings_patch = mock.patch.object(model, settings)
193 with settings_patch as settings_mock:
193 with settings_patch as settings_mock:
194 settings_mock.get_ui_by_section_and_key.return_value = None
194 settings_mock.get_ui_by_section_and_key.return_value = None
195 settings_mock.get_setting_by_name.return_value = None
195 settings_mock.get_setting_by_name.return_value = None
196 result = model._collect_all_settings(global_=global_)
196 result = model._collect_all_settings(global_=global_)
197
197
198 assert result == {}
198 assert result == {}
199
199
200 def _mock_result(self):
200 def _mock_result(self):
201 result_mock = mock.Mock()
201 result_mock = mock.Mock()
202 result_mock.ui_value = 'ui_value'
202 result_mock.ui_value = 'ui_value'
203 result_mock.ui_active = True
203 result_mock.ui_active = True
204 result_mock.app_settings_value = 'setting_value'
204 result_mock.app_settings_value = 'setting_value'
205 return result_mock
205 return result_mock
206
206
207 def _assert_get_settings_calls(
207 def _assert_get_settings_calls(
208 self, settings_mock, ui_settings, general_settings):
208 self, settings_mock, ui_settings, general_settings):
209 assert (
209 assert (
210 settings_mock.get_ui_by_section_and_key.call_count ==
210 settings_mock.get_ui_by_section_and_key.call_count ==
211 len(ui_settings))
211 len(ui_settings))
212 assert (
212 assert (
213 settings_mock.get_setting_by_name.call_count ==
213 settings_mock.get_setting_by_name.call_count ==
214 len(general_settings))
214 len(general_settings))
215
215
216 for section, key in ui_settings:
216 for section, key in ui_settings:
217 expected_call = mock.call(section, key)
217 expected_call = mock.call(section, key)
218 assert (
218 assert (
219 expected_call in
219 expected_call in
220 settings_mock.get_ui_by_section_and_key.call_args_list)
220 settings_mock.get_ui_by_section_and_key.call_args_list)
221
221
222 for name in general_settings:
222 for name in general_settings:
223 expected_call = mock.call(name)
223 expected_call = mock.call(name)
224 assert (
224 assert (
225 expected_call in
225 expected_call in
226 settings_mock.get_setting_by_name.call_args_list)
226 settings_mock.get_setting_by_name.call_args_list)
227
227
228 def _assert_collect_all_settings_result(
228 def _assert_collect_all_settings_result(
229 self, ui_settings, general_settings, result):
229 self, ui_settings, general_settings, result):
230 expected_result = {}
230 expected_result = {}
231 for section, key in ui_settings:
231 for section, key in ui_settings:
232 key = '{}_{}'.format(section, key.replace('.', '_'))
232 key = '{}_{}'.format(section, key.replace('.', '_'))
233
233
234 if section in ('extensions', 'hooks'):
234 if section in ('extensions', 'hooks'):
235 value = True
235 value = True
236 elif key in ['vcs_git_lfs_enabled']:
236 elif key in ['vcs_git_lfs_enabled']:
237 value = True
237 value = True
238 else:
238 else:
239 value = 'ui_value'
239 value = 'ui_value'
240 expected_result[key] = value
240 expected_result[key] = value
241
241
242 for name in general_settings:
242 for name in general_settings:
243 key = 'rhodecode_' + name
243 key = 'rhodecode_' + name
244 expected_result[key] = 'setting_value'
244 expected_result[key] = 'setting_value'
245
245
246 assert expected_result == result
246 assert expected_result == result
247
247
248
248
249 class TestCreateOrUpdateRepoHookSettings(object):
249 class TestCreateOrUpdateRepoHookSettings(object):
250 def test_create_when_no_repo_object_found(self, repo_stub):
250 def test_create_when_no_repo_object_found(self, repo_stub):
251 model = VcsSettingsModel(repo=repo_stub.repo_name)
251 model = VcsSettingsModel(repo=repo_stub.repo_name)
252
252
253 self._create_settings(model, HOOKS_FORM_DATA)
253 self._create_settings(model, HOOKS_FORM_DATA)
254
254
255 cleanup = []
255 cleanup = []
256 try:
256 try:
257 for section, key in model.HOOKS_SETTINGS:
257 for section, key in model.HOOKS_SETTINGS:
258 ui = model.repo_settings.get_ui_by_section_and_key(
258 ui = model.repo_settings.get_ui_by_section_and_key(
259 section, key)
259 section, key)
260 assert ui.ui_active is True
260 assert ui.ui_active is True
261 cleanup.append(ui)
261 cleanup.append(ui)
262 finally:
262 finally:
263 for ui in cleanup:
263 for ui in cleanup:
264 Session().delete(ui)
264 Session().delete(ui)
265 Session().commit()
265 Session().commit()
266
266
267 def test_create_raises_exception_when_data_incomplete(self, repo_stub):
267 def test_create_raises_exception_when_data_incomplete(self, repo_stub):
268 model = VcsSettingsModel(repo=repo_stub.repo_name)
268 model = VcsSettingsModel(repo=repo_stub.repo_name)
269
269
270 deleted_key = 'hooks_changegroup_repo_size'
270 deleted_key = 'hooks_changegroup_repo_size'
271 data = HOOKS_FORM_DATA.copy()
271 data = HOOKS_FORM_DATA.copy()
272 data.pop(deleted_key)
272 data.pop(deleted_key)
273
273
274 with pytest.raises(ValueError) as exc_info:
274 with pytest.raises(ValueError) as exc_info:
275 model.create_or_update_repo_hook_settings(data)
275 model.create_or_update_repo_hook_settings(data)
276 assert (
276 assert (
277 exc_info.value.message ==
277 exc_info.value.message ==
278 'The given data does not contain {} key'.format(deleted_key))
278 'The given data does not contain {} key'.format(deleted_key))
279
279
280 def test_update_when_repo_object_found(self, repo_stub, settings_util):
280 def test_update_when_repo_object_found(self, repo_stub, settings_util):
281 model = VcsSettingsModel(repo=repo_stub.repo_name)
281 model = VcsSettingsModel(repo=repo_stub.repo_name)
282 for section, key in model.HOOKS_SETTINGS:
282 for section, key in model.HOOKS_SETTINGS:
283 settings_util.create_repo_rhodecode_ui(
283 settings_util.create_repo_rhodecode_ui(
284 repo_stub, section, None, key=key, active=False)
284 repo_stub, section, None, key=key, active=False)
285 model.create_or_update_repo_hook_settings(HOOKS_FORM_DATA)
285 model.create_or_update_repo_hook_settings(HOOKS_FORM_DATA)
286 for section, key in model.HOOKS_SETTINGS:
286 for section, key in model.HOOKS_SETTINGS:
287 ui = model.repo_settings.get_ui_by_section_and_key(section, key)
287 ui = model.repo_settings.get_ui_by_section_and_key(section, key)
288 assert ui.ui_active is True
288 assert ui.ui_active is True
289
289
290 def _create_settings(self, model, data):
290 def _create_settings(self, model, data):
291 global_patch = mock.patch.object(model, 'global_settings')
291 global_patch = mock.patch.object(model, 'global_settings')
292 global_setting = mock.Mock()
292 global_setting = mock.Mock()
293 global_setting.ui_value = 'Test value'
293 global_setting.ui_value = 'Test value'
294 with global_patch as global_mock:
294 with global_patch as global_mock:
295 global_mock.get_ui_by_section_and_key.return_value = global_setting
295 global_mock.get_ui_by_section_and_key.return_value = global_setting
296 model.create_or_update_repo_hook_settings(HOOKS_FORM_DATA)
296 model.create_or_update_repo_hook_settings(HOOKS_FORM_DATA)
297
297
298
298
299 class TestUpdateGlobalHookSettings(object):
299 class TestUpdateGlobalHookSettings(object):
300 def test_update_raises_exception_when_data_incomplete(self):
300 def test_update_raises_exception_when_data_incomplete(self):
301 model = VcsSettingsModel()
301 model = VcsSettingsModel()
302
302
303 deleted_key = 'hooks_changegroup_repo_size'
303 deleted_key = 'hooks_changegroup_repo_size'
304 data = HOOKS_FORM_DATA.copy()
304 data = HOOKS_FORM_DATA.copy()
305 data.pop(deleted_key)
305 data.pop(deleted_key)
306
306
307 with pytest.raises(ValueError) as exc_info:
307 with pytest.raises(ValueError) as exc_info:
308 model.update_global_hook_settings(data)
308 model.update_global_hook_settings(data)
309 assert (
309 assert (
310 exc_info.value.message ==
310 exc_info.value.message ==
311 'The given data does not contain {} key'.format(deleted_key))
311 'The given data does not contain {} key'.format(deleted_key))
312
312
313 def test_update_global_hook_settings(self, settings_util):
313 def test_update_global_hook_settings(self, settings_util):
314 model = VcsSettingsModel()
314 model = VcsSettingsModel()
315 setting_mock = mock.MagicMock()
315 setting_mock = mock.MagicMock()
316 setting_mock.ui_active = False
316 setting_mock.ui_active = False
317 get_settings_patcher = mock.patch.object(
317 get_settings_patcher = mock.patch.object(
318 model.global_settings, 'get_ui_by_section_and_key',
318 model.global_settings, 'get_ui_by_section_and_key',
319 return_value=setting_mock)
319 return_value=setting_mock)
320 session_patcher = mock.patch('rhodecode.model.settings.Session')
320 session_patcher = mock.patch('rhodecode.model.settings.Session')
321 with get_settings_patcher as get_settings_mock, session_patcher:
321 with get_settings_patcher as get_settings_mock, session_patcher:
322 model.update_global_hook_settings(HOOKS_FORM_DATA)
322 model.update_global_hook_settings(HOOKS_FORM_DATA)
323 assert setting_mock.ui_active is True
323 assert setting_mock.ui_active is True
324 assert get_settings_mock.call_count == 3
324 assert get_settings_mock.call_count == 3
325
325
326
326
327 class TestCreateOrUpdateRepoGeneralSettings(object):
327 class TestCreateOrUpdateRepoGeneralSettings(object):
328 def test_calls_create_or_update_general_settings(self, repo_stub):
328 def test_calls_create_or_update_general_settings(self, repo_stub):
329 model = VcsSettingsModel(repo=repo_stub.repo_name)
329 model = VcsSettingsModel(repo=repo_stub.repo_name)
330 create_patch = mock.patch.object(
330 create_patch = mock.patch.object(
331 model, '_create_or_update_general_settings')
331 model, '_create_or_update_general_settings')
332 with create_patch as create_mock:
332 with create_patch as create_mock:
333 model.create_or_update_repo_pr_settings(GENERAL_FORM_DATA)
333 model.create_or_update_repo_pr_settings(GENERAL_FORM_DATA)
334 create_mock.assert_called_once_with(
334 create_mock.assert_called_once_with(
335 model.repo_settings, GENERAL_FORM_DATA)
335 model.repo_settings, GENERAL_FORM_DATA)
336
336
337 def test_raises_exception_when_repository_is_not_specified(self):
337 def test_raises_exception_when_repository_is_not_specified(self):
338 model = VcsSettingsModel()
338 model = VcsSettingsModel()
339 with pytest.raises(Exception) as exc_info:
339 with pytest.raises(Exception) as exc_info:
340 model.create_or_update_repo_pr_settings(GENERAL_FORM_DATA)
340 model.create_or_update_repo_pr_settings(GENERAL_FORM_DATA)
341 assert exc_info.value.message == 'Repository is not specified'
341 assert exc_info.value.message == 'Repository is not specified'
342
342
343
343
344 class TestCreateOrUpdatGlobalGeneralSettings(object):
344 class TestCreateOrUpdatGlobalGeneralSettings(object):
345 def test_calls_create_or_update_general_settings(self):
345 def test_calls_create_or_update_general_settings(self):
346 model = VcsSettingsModel()
346 model = VcsSettingsModel()
347 create_patch = mock.patch.object(
347 create_patch = mock.patch.object(
348 model, '_create_or_update_general_settings')
348 model, '_create_or_update_general_settings')
349 with create_patch as create_mock:
349 with create_patch as create_mock:
350 model.create_or_update_global_pr_settings(GENERAL_FORM_DATA)
350 model.create_or_update_global_pr_settings(GENERAL_FORM_DATA)
351 create_mock.assert_called_once_with(
351 create_mock.assert_called_once_with(
352 model.global_settings, GENERAL_FORM_DATA)
352 model.global_settings, GENERAL_FORM_DATA)
353
353
354
354
355 class TestCreateOrUpdateGeneralSettings(object):
355 class TestCreateOrUpdateGeneralSettings(object):
356 def test_create_when_no_repo_settings_found(self, repo_stub):
356 def test_create_when_no_repo_settings_found(self, repo_stub):
357 model = VcsSettingsModel(repo=repo_stub.repo_name)
357 model = VcsSettingsModel(repo=repo_stub.repo_name)
358 model._create_or_update_general_settings(
358 model._create_or_update_general_settings(
359 model.repo_settings, GENERAL_FORM_DATA)
359 model.repo_settings, GENERAL_FORM_DATA)
360
360
361 cleanup = []
361 cleanup = []
362 try:
362 try:
363 for name in model.GENERAL_SETTINGS:
363 for name in model.GENERAL_SETTINGS:
364 setting = model.repo_settings.get_setting_by_name(name)
364 setting = model.repo_settings.get_setting_by_name(name)
365 assert setting.app_settings_value is True
365 assert setting.app_settings_value is True
366 cleanup.append(setting)
366 cleanup.append(setting)
367 finally:
367 finally:
368 for setting in cleanup:
368 for setting in cleanup:
369 Session().delete(setting)
369 Session().delete(setting)
370 Session().commit()
370 Session().commit()
371
371
372 def test_create_raises_exception_when_data_incomplete(self, repo_stub):
372 def test_create_raises_exception_when_data_incomplete(self, repo_stub):
373 model = VcsSettingsModel(repo=repo_stub.repo_name)
373 model = VcsSettingsModel(repo=repo_stub.repo_name)
374
374
375 deleted_key = 'rhodecode_pr_merge_enabled'
375 deleted_key = 'rhodecode_pr_merge_enabled'
376 data = GENERAL_FORM_DATA.copy()
376 data = GENERAL_FORM_DATA.copy()
377 data.pop(deleted_key)
377 data.pop(deleted_key)
378
378
379 with pytest.raises(ValueError) as exc_info:
379 with pytest.raises(ValueError) as exc_info:
380 model._create_or_update_general_settings(model.repo_settings, data)
380 model._create_or_update_general_settings(model.repo_settings, data)
381 assert (
381 assert (
382 exc_info.value.message ==
382 exc_info.value.message ==
383 'The given data does not contain {} key'.format(deleted_key))
383 'The given data does not contain {} key'.format(deleted_key))
384
384
385 def test_update_when_repo_setting_found(self, repo_stub, settings_util):
385 def test_update_when_repo_setting_found(self, repo_stub, settings_util):
386 model = VcsSettingsModel(repo=repo_stub.repo_name)
386 model = VcsSettingsModel(repo=repo_stub.repo_name)
387 for name in model.GENERAL_SETTINGS:
387 for name in model.GENERAL_SETTINGS:
388 settings_util.create_repo_rhodecode_setting(
388 settings_util.create_repo_rhodecode_setting(
389 repo_stub, name, False, 'bool')
389 repo_stub, name, False, 'bool')
390
390
391 model._create_or_update_general_settings(
391 model._create_or_update_general_settings(
392 model.repo_settings, GENERAL_FORM_DATA)
392 model.repo_settings, GENERAL_FORM_DATA)
393
393
394 for name in model.GENERAL_SETTINGS:
394 for name in model.GENERAL_SETTINGS:
395 setting = model.repo_settings.get_setting_by_name(name)
395 setting = model.repo_settings.get_setting_by_name(name)
396 assert setting.app_settings_value is True
396 assert setting.app_settings_value is True
397
397
398
398
399 class TestCreateRepoSvnSettings(object):
399 class TestCreateRepoSvnSettings(object):
400 def test_calls_create_svn_settings(self, repo_stub):
400 def test_calls_create_svn_settings(self, repo_stub):
401 model = VcsSettingsModel(repo=repo_stub.repo_name)
401 model = VcsSettingsModel(repo=repo_stub.repo_name)
402 with mock.patch.object(model, '_create_svn_settings') as create_mock:
402 with mock.patch.object(model, '_create_svn_settings') as create_mock:
403 model.create_repo_svn_settings(SVN_FORM_DATA)
403 model.create_repo_svn_settings(SVN_FORM_DATA)
404 create_mock.assert_called_once_with(model.repo_settings, SVN_FORM_DATA)
404 create_mock.assert_called_once_with(model.repo_settings, SVN_FORM_DATA)
405
405
406 def test_raises_exception_when_repository_is_not_specified(self):
406 def test_raises_exception_when_repository_is_not_specified(self):
407 model = VcsSettingsModel()
407 model = VcsSettingsModel()
408 with pytest.raises(Exception) as exc_info:
408 with pytest.raises(Exception) as exc_info:
409 model.create_repo_svn_settings(SVN_FORM_DATA)
409 model.create_repo_svn_settings(SVN_FORM_DATA)
410 assert exc_info.value.message == 'Repository is not specified'
410 assert exc_info.value.message == 'Repository is not specified'
411
411
412
412
413 class TestCreateSvnSettings(object):
413 class TestCreateSvnSettings(object):
414 def test_create(self, repo_stub):
414 def test_create(self, repo_stub):
415 model = VcsSettingsModel(repo=repo_stub.repo_name)
415 model = VcsSettingsModel(repo=repo_stub.repo_name)
416 model._create_svn_settings(model.repo_settings, SVN_FORM_DATA)
416 model._create_svn_settings(model.repo_settings, SVN_FORM_DATA)
417 Session().commit()
417 Session().commit()
418
418
419 branch_ui = model.repo_settings.get_ui_by_section(
419 branch_ui = model.repo_settings.get_ui_by_section(
420 model.SVN_BRANCH_SECTION)
420 model.SVN_BRANCH_SECTION)
421 tag_ui = model.repo_settings.get_ui_by_section(
421 tag_ui = model.repo_settings.get_ui_by_section(
422 model.SVN_TAG_SECTION)
422 model.SVN_TAG_SECTION)
423
423
424 try:
424 try:
425 assert len(branch_ui) == 1
425 assert len(branch_ui) == 1
426 assert len(tag_ui) == 1
426 assert len(tag_ui) == 1
427 finally:
427 finally:
428 Session().delete(branch_ui[0])
428 Session().delete(branch_ui[0])
429 Session().delete(tag_ui[0])
429 Session().delete(tag_ui[0])
430 Session().commit()
430 Session().commit()
431
431
432 def test_create_tag(self, repo_stub):
432 def test_create_tag(self, repo_stub):
433 model = VcsSettingsModel(repo=repo_stub.repo_name)
433 model = VcsSettingsModel(repo=repo_stub.repo_name)
434 data = SVN_FORM_DATA.copy()
434 data = SVN_FORM_DATA.copy()
435 data.pop('new_svn_branch')
435 data.pop('new_svn_branch')
436 model._create_svn_settings(model.repo_settings, data)
436 model._create_svn_settings(model.repo_settings, data)
437 Session().commit()
437 Session().commit()
438
438
439 branch_ui = model.repo_settings.get_ui_by_section(
439 branch_ui = model.repo_settings.get_ui_by_section(
440 model.SVN_BRANCH_SECTION)
440 model.SVN_BRANCH_SECTION)
441 tag_ui = model.repo_settings.get_ui_by_section(
441 tag_ui = model.repo_settings.get_ui_by_section(
442 model.SVN_TAG_SECTION)
442 model.SVN_TAG_SECTION)
443
443
444 try:
444 try:
445 assert len(branch_ui) == 0
445 assert len(branch_ui) == 0
446 assert len(tag_ui) == 1
446 assert len(tag_ui) == 1
447 finally:
447 finally:
448 Session().delete(tag_ui[0])
448 Session().delete(tag_ui[0])
449 Session().commit()
449 Session().commit()
450
450
451 def test_create_nothing_when_no_svn_settings_specified(self, repo_stub):
451 def test_create_nothing_when_no_svn_settings_specified(self, repo_stub):
452 model = VcsSettingsModel(repo=repo_stub.repo_name)
452 model = VcsSettingsModel(repo=repo_stub.repo_name)
453 model._create_svn_settings(model.repo_settings, {})
453 model._create_svn_settings(model.repo_settings, {})
454 Session().commit()
454 Session().commit()
455
455
456 branch_ui = model.repo_settings.get_ui_by_section(
456 branch_ui = model.repo_settings.get_ui_by_section(
457 model.SVN_BRANCH_SECTION)
457 model.SVN_BRANCH_SECTION)
458 tag_ui = model.repo_settings.get_ui_by_section(
458 tag_ui = model.repo_settings.get_ui_by_section(
459 model.SVN_TAG_SECTION)
459 model.SVN_TAG_SECTION)
460
460
461 assert len(branch_ui) == 0
461 assert len(branch_ui) == 0
462 assert len(tag_ui) == 0
462 assert len(tag_ui) == 0
463
463
464 def test_create_nothing_when_empty_settings_specified(self, repo_stub):
464 def test_create_nothing_when_empty_settings_specified(self, repo_stub):
465 model = VcsSettingsModel(repo=repo_stub.repo_name)
465 model = VcsSettingsModel(repo=repo_stub.repo_name)
466 data = {
466 data = {
467 'new_svn_branch': '',
467 'new_svn_branch': '',
468 'new_svn_tag': ''
468 'new_svn_tag': ''
469 }
469 }
470 model._create_svn_settings(model.repo_settings, data)
470 model._create_svn_settings(model.repo_settings, data)
471 Session().commit()
471 Session().commit()
472
472
473 branch_ui = model.repo_settings.get_ui_by_section(
473 branch_ui = model.repo_settings.get_ui_by_section(
474 model.SVN_BRANCH_SECTION)
474 model.SVN_BRANCH_SECTION)
475 tag_ui = model.repo_settings.get_ui_by_section(
475 tag_ui = model.repo_settings.get_ui_by_section(
476 model.SVN_TAG_SECTION)
476 model.SVN_TAG_SECTION)
477
477
478 assert len(branch_ui) == 0
478 assert len(branch_ui) == 0
479 assert len(tag_ui) == 0
479 assert len(tag_ui) == 0
480
480
481
481
482 class TestCreateOrUpdateUi(object):
482 class TestCreateOrUpdateUi(object):
483 def test_create(self, repo_stub):
483 def test_create(self, repo_stub):
484 model = VcsSettingsModel(repo=repo_stub.repo_name)
484 model = VcsSettingsModel(repo=repo_stub.repo_name)
485 model._create_or_update_ui(
485 model._create_or_update_ui(
486 model.repo_settings, 'test-section', 'test-key', active=False,
486 model.repo_settings, 'test-section', 'test-key', active=False,
487 value='False')
487 value='False')
488 Session().commit()
488 Session().commit()
489
489
490 created_ui = model.repo_settings.get_ui_by_section_and_key(
490 created_ui = model.repo_settings.get_ui_by_section_and_key(
491 'test-section', 'test-key')
491 'test-section', 'test-key')
492
492
493 try:
493 try:
494 assert created_ui.ui_active is False
494 assert created_ui.ui_active is False
495 assert str2bool(created_ui.ui_value) is False
495 assert str2bool(created_ui.ui_value) is False
496 finally:
496 finally:
497 Session().delete(created_ui)
497 Session().delete(created_ui)
498 Session().commit()
498 Session().commit()
499
499
500 def test_update(self, repo_stub, settings_util):
500 def test_update(self, repo_stub, settings_util):
501 model = VcsSettingsModel(repo=repo_stub.repo_name)
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 section = 'test-section'
505 section = 'test-section'
505 key = 'test-key'
506 key = 'test-key'
506 settings_util.create_repo_rhodecode_ui(
507 settings_util.create_repo_rhodecode_ui(
507 repo_stub, section, 'True', key=key, active=True)
508 repo_stub, section, 'True', key=key, active=True)
508
509
509 model._create_or_update_ui(
510 model._create_or_update_ui(
510 model.repo_settings, section, key, active=False, value='False')
511 model.repo_settings, section, key, active=False, value='False')
511 Session().commit()
512 Session().commit()
512
513
513 created_ui = model.repo_settings.get_ui_by_section_and_key(
514 created_ui = model.repo_settings.get_ui_by_section_and_key(
514 section, key)
515 section, key)
515 assert created_ui.ui_active is False
516 assert created_ui.ui_active is False
516 assert str2bool(created_ui.ui_value) is False
517 assert str2bool(created_ui.ui_value) is False
517
518
518
519
519 class TestCreateOrUpdateRepoHgSettings(object):
520 class TestCreateOrUpdateRepoHgSettings(object):
520 FORM_DATA = {
521 FORM_DATA = {
521 'extensions_largefiles': False,
522 'extensions_largefiles': False,
523 'extensions_evolve': False,
522 'phases_publish': False
524 'phases_publish': False
523 }
525 }
524
526
525 def test_creates_repo_hg_settings_when_data_is_correct(self, repo_stub):
527 def test_creates_repo_hg_settings_when_data_is_correct(self, repo_stub):
526 model = VcsSettingsModel(repo=repo_stub.repo_name)
528 model = VcsSettingsModel(repo=repo_stub.repo_name)
527 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
529 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
528 model.create_or_update_repo_hg_settings(self.FORM_DATA)
530 model.create_or_update_repo_hg_settings(self.FORM_DATA)
529 expected_calls = [
531 expected_calls = [
530 mock.call(model.repo_settings, 'extensions', 'largefiles',
532 mock.call(model.repo_settings, 'extensions', 'largefiles',
531 active=False, value=''),
533 active=False, value=''),
534 mock.call(model.repo_settings, 'extensions', 'evolve',
535 active=False, value=''),
532 mock.call(model.repo_settings, 'phases', 'publish', value='False'),
536 mock.call(model.repo_settings, 'phases', 'publish', value='False'),
533 ]
537 ]
534 assert expected_calls == create_mock.call_args_list
538 assert expected_calls == create_mock.call_args_list
535
539
536 @pytest.mark.parametrize('field_to_remove', FORM_DATA.keys())
540 @pytest.mark.parametrize('field_to_remove', FORM_DATA.keys())
537 def test_key_is_not_found(self, repo_stub, field_to_remove):
541 def test_key_is_not_found(self, repo_stub, field_to_remove):
538 model = VcsSettingsModel(repo=repo_stub.repo_name)
542 model = VcsSettingsModel(repo=repo_stub.repo_name)
539 data = self.FORM_DATA.copy()
543 data = self.FORM_DATA.copy()
540 data.pop(field_to_remove)
544 data.pop(field_to_remove)
541 with pytest.raises(ValueError) as exc_info:
545 with pytest.raises(ValueError) as exc_info:
542 model.create_or_update_repo_hg_settings(data)
546 model.create_or_update_repo_hg_settings(data)
543 expected_message = 'The given data does not contain {} key'.format(
547 expected_message = 'The given data does not contain {} key'.format(
544 field_to_remove)
548 field_to_remove)
545 assert exc_info.value.message == expected_message
549 assert exc_info.value.message == expected_message
546
550
547 def test_create_raises_exception_when_repository_not_specified(self):
551 def test_create_raises_exception_when_repository_not_specified(self):
548 model = VcsSettingsModel()
552 model = VcsSettingsModel()
549 with pytest.raises(Exception) as exc_info:
553 with pytest.raises(Exception) as exc_info:
550 model.create_or_update_repo_hg_settings(self.FORM_DATA)
554 model.create_or_update_repo_hg_settings(self.FORM_DATA)
551 assert exc_info.value.message == 'Repository is not specified'
555 assert exc_info.value.message == 'Repository is not specified'
552
556
553
557
554 class TestUpdateGlobalSslSetting(object):
558 class TestUpdateGlobalSslSetting(object):
555 def test_updates_global_hg_settings(self):
559 def test_updates_global_hg_settings(self):
556 model = VcsSettingsModel()
560 model = VcsSettingsModel()
557 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
561 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
558 model.update_global_ssl_setting('False')
562 model.update_global_ssl_setting('False')
559 create_mock.assert_called_once_with(
563 create_mock.assert_called_once_with(
560 model.global_settings, 'web', 'push_ssl', value='False')
564 model.global_settings, 'web', 'push_ssl', value='False')
561
565
562
566
563 class TestUpdateGlobalPathSetting(object):
567 class TestUpdateGlobalPathSetting(object):
564 def test_updates_global_path_settings(self):
568 def test_updates_global_path_settings(self):
565 model = VcsSettingsModel()
569 model = VcsSettingsModel()
566 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
570 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
567 model.update_global_path_setting('False')
571 model.update_global_path_setting('False')
568 create_mock.assert_called_once_with(
572 create_mock.assert_called_once_with(
569 model.global_settings, 'paths', '/', value='False')
573 model.global_settings, 'paths', '/', value='False')
570
574
571
575
572 class TestCreateOrUpdateGlobalHgSettings(object):
576 class TestCreateOrUpdateGlobalHgSettings(object):
573 FORM_DATA = {
577 FORM_DATA = {
574 'extensions_largefiles': False,
578 'extensions_largefiles': False,
575 'largefiles_usercache': '/example/largefiles-store',
579 'largefiles_usercache': '/example/largefiles-store',
576 'phases_publish': False,
580 'phases_publish': False,
577 'extensions_hgsubversion': False
581 'extensions_hgsubversion': False,
582 'extensions_evolve': False
578 }
583 }
579
584
580 def test_creates_repo_hg_settings_when_data_is_correct(self):
585 def test_creates_repo_hg_settings_when_data_is_correct(self):
581 model = VcsSettingsModel()
586 model = VcsSettingsModel()
582 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
587 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
583 model.create_or_update_global_hg_settings(self.FORM_DATA)
588 model.create_or_update_global_hg_settings(self.FORM_DATA)
584 expected_calls = [
589 expected_calls = [
585 mock.call(model.global_settings, 'extensions', 'largefiles',
590 mock.call(model.global_settings, 'extensions', 'largefiles',
586 active=False, value=''),
591 active=False, value=''),
587 mock.call(model.global_settings, 'largefiles', 'usercache',
592 mock.call(model.global_settings, 'largefiles', 'usercache',
588 value='/example/largefiles-store'),
593 value='/example/largefiles-store'),
589 mock.call(model.global_settings, 'phases', 'publish',
594 mock.call(model.global_settings, 'phases', 'publish',
590 value='False'),
595 value='False'),
591 mock.call(model.global_settings, 'extensions', 'hgsubversion',
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 assert expected_calls == create_mock.call_args_list
601 assert expected_calls == create_mock.call_args_list
595
602
596 @pytest.mark.parametrize('field_to_remove', FORM_DATA.keys())
603 @pytest.mark.parametrize('field_to_remove', FORM_DATA.keys())
597 def test_key_is_not_found(self, repo_stub, field_to_remove):
604 def test_key_is_not_found(self, repo_stub, field_to_remove):
598 model = VcsSettingsModel(repo=repo_stub.repo_name)
605 model = VcsSettingsModel(repo=repo_stub.repo_name)
599 data = self.FORM_DATA.copy()
606 data = self.FORM_DATA.copy()
600 data.pop(field_to_remove)
607 data.pop(field_to_remove)
601 with pytest.raises(Exception) as exc_info:
608 with pytest.raises(Exception) as exc_info:
602 model.create_or_update_global_hg_settings(data)
609 model.create_or_update_global_hg_settings(data)
603 expected_message = 'The given data does not contain {} key'.format(
610 expected_message = 'The given data does not contain {} key'.format(
604 field_to_remove)
611 field_to_remove)
605 assert exc_info.value.message == expected_message
612 assert exc_info.value.message == expected_message
606
613
607
614
608 class TestCreateOrUpdateGlobalGitSettings(object):
615 class TestCreateOrUpdateGlobalGitSettings(object):
609 FORM_DATA = {
616 FORM_DATA = {
610 'vcs_git_lfs_enabled': False,
617 'vcs_git_lfs_enabled': False,
611 'vcs_git_lfs_store_location': '/example/lfs-store',
618 'vcs_git_lfs_store_location': '/example/lfs-store',
612 }
619 }
613
620
614 def test_creates_repo_hg_settings_when_data_is_correct(self):
621 def test_creates_repo_hg_settings_when_data_is_correct(self):
615 model = VcsSettingsModel()
622 model = VcsSettingsModel()
616 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
623 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
617 model.create_or_update_global_git_settings(self.FORM_DATA)
624 model.create_or_update_global_git_settings(self.FORM_DATA)
618 expected_calls = [
625 expected_calls = [
619 mock.call(model.global_settings, 'vcs_git_lfs', 'enabled',
626 mock.call(model.global_settings, 'vcs_git_lfs', 'enabled',
620 active=False, value=False),
627 active=False, value=False),
621 mock.call(model.global_settings, 'vcs_git_lfs', 'store_location',
628 mock.call(model.global_settings, 'vcs_git_lfs', 'store_location',
622 value='/example/lfs-store'),
629 value='/example/lfs-store'),
623 ]
630 ]
624 assert expected_calls == create_mock.call_args_list
631 assert expected_calls == create_mock.call_args_list
625
632
626
633
627 class TestDeleteRepoSvnPattern(object):
634 class TestDeleteRepoSvnPattern(object):
628 def test_success_when_repo_is_set(self, backend_svn):
635 def test_success_when_repo_is_set(self, backend_svn):
629 repo_name = backend_svn.repo_name
636 repo_name = backend_svn.repo_name
630 model = VcsSettingsModel(repo=repo_name)
637 model = VcsSettingsModel(repo=repo_name)
631 delete_ui_patch = mock.patch.object(model.repo_settings, 'delete_ui')
638 delete_ui_patch = mock.patch.object(model.repo_settings, 'delete_ui')
632 with delete_ui_patch as delete_ui_mock:
639 with delete_ui_patch as delete_ui_mock:
633 model.delete_repo_svn_pattern(123)
640 model.delete_repo_svn_pattern(123)
634 delete_ui_mock.assert_called_once_with(123)
641 delete_ui_mock.assert_called_once_with(123)
635
642
636 def test_raises_exception_when_repository_is_not_specified(self):
643 def test_raises_exception_when_repository_is_not_specified(self):
637 model = VcsSettingsModel()
644 model = VcsSettingsModel()
638 with pytest.raises(Exception) as exc_info:
645 with pytest.raises(Exception) as exc_info:
639 model.delete_repo_svn_pattern(123)
646 model.delete_repo_svn_pattern(123)
640 assert exc_info.value.message == 'Repository is not specified'
647 assert exc_info.value.message == 'Repository is not specified'
641
648
642
649
643 class TestDeleteGlobalSvnPattern(object):
650 class TestDeleteGlobalSvnPattern(object):
644 def test_delete_global_svn_pattern_calls_delete_ui(self):
651 def test_delete_global_svn_pattern_calls_delete_ui(self):
645 model = VcsSettingsModel()
652 model = VcsSettingsModel()
646 delete_ui_patch = mock.patch.object(model.global_settings, 'delete_ui')
653 delete_ui_patch = mock.patch.object(model.global_settings, 'delete_ui')
647 with delete_ui_patch as delete_ui_mock:
654 with delete_ui_patch as delete_ui_mock:
648 model.delete_global_svn_pattern(123)
655 model.delete_global_svn_pattern(123)
649 delete_ui_mock.assert_called_once_with(123)
656 delete_ui_mock.assert_called_once_with(123)
650
657
651
658
652 class TestFilterUiSettings(object):
659 class TestFilterUiSettings(object):
653 def test_settings_are_filtered(self):
660 def test_settings_are_filtered(self):
654 model = VcsSettingsModel()
661 model = VcsSettingsModel()
655 repo_settings = [
662 repo_settings = [
656 UiSetting('extensions', 'largefiles', '', True),
663 UiSetting('extensions', 'largefiles', '', True),
657 UiSetting('phases', 'publish', 'True', True),
664 UiSetting('phases', 'publish', 'True', True),
658 UiSetting('hooks', 'changegroup.repo_size', 'hook', True),
665 UiSetting('hooks', 'changegroup.repo_size', 'hook', True),
659 UiSetting('hooks', 'changegroup.push_logger', 'hook', True),
666 UiSetting('hooks', 'changegroup.push_logger', 'hook', True),
660 UiSetting('hooks', 'outgoing.pull_logger', 'hook', True),
667 UiSetting('hooks', 'outgoing.pull_logger', 'hook', True),
661 UiSetting(
668 UiSetting(
662 'vcs_svn_branch', '84223c972204fa545ca1b22dac7bef5b68d7442d',
669 'vcs_svn_branch', '84223c972204fa545ca1b22dac7bef5b68d7442d',
663 'test_branch', True),
670 'test_branch', True),
664 UiSetting(
671 UiSetting(
665 'vcs_svn_tag', '84229c972204fa545ca1b22dac7bef5b68d7442d',
672 'vcs_svn_tag', '84229c972204fa545ca1b22dac7bef5b68d7442d',
666 'test_tag', True),
673 'test_tag', True),
667 ]
674 ]
668 non_repo_settings = [
675 non_repo_settings = [
669 UiSetting('largefiles', 'usercache', '/example/largefiles-store', True),
676 UiSetting('largefiles', 'usercache', '/example/largefiles-store', True),
670 UiSetting('test', 'outgoing.pull_logger', 'hook', True),
677 UiSetting('test', 'outgoing.pull_logger', 'hook', True),
671 UiSetting('hooks', 'test2', 'hook', True),
678 UiSetting('hooks', 'test2', 'hook', True),
672 UiSetting(
679 UiSetting(
673 'vcs_svn_repo', '84229c972204fa545ca1b22dac7bef5b68d7442d',
680 'vcs_svn_repo', '84229c972204fa545ca1b22dac7bef5b68d7442d',
674 'test_tag', True),
681 'test_tag', True),
675 ]
682 ]
676 settings = repo_settings + non_repo_settings
683 settings = repo_settings + non_repo_settings
677 filtered_settings = model._filter_ui_settings(settings)
684 filtered_settings = model._filter_ui_settings(settings)
678 assert sorted(filtered_settings) == sorted(repo_settings)
685 assert sorted(filtered_settings) == sorted(repo_settings)
679
686
680
687
681 class TestFilterGeneralSettings(object):
688 class TestFilterGeneralSettings(object):
682 def test_settings_are_filtered(self):
689 def test_settings_are_filtered(self):
683 model = VcsSettingsModel()
690 model = VcsSettingsModel()
684 settings = {
691 settings = {
685 'rhodecode_abcde': 'value1',
692 'rhodecode_abcde': 'value1',
686 'rhodecode_vwxyz': 'value2',
693 'rhodecode_vwxyz': 'value2',
687 }
694 }
688 general_settings = {
695 general_settings = {
689 'rhodecode_{}'.format(key): 'value'
696 'rhodecode_{}'.format(key): 'value'
690 for key in VcsSettingsModel.GENERAL_SETTINGS
697 for key in VcsSettingsModel.GENERAL_SETTINGS
691 }
698 }
692 settings.update(general_settings)
699 settings.update(general_settings)
693
700
694 filtered_settings = model._filter_general_settings(general_settings)
701 filtered_settings = model._filter_general_settings(general_settings)
695 assert sorted(filtered_settings) == sorted(general_settings)
702 assert sorted(filtered_settings) == sorted(general_settings)
696
703
697
704
698 class TestGetRepoUiSettings(object):
705 class TestGetRepoUiSettings(object):
699 def test_global_uis_are_returned_when_no_repo_uis_found(
706 def test_global_uis_are_returned_when_no_repo_uis_found(
700 self, repo_stub):
707 self, repo_stub):
701 model = VcsSettingsModel(repo=repo_stub.repo_name)
708 model = VcsSettingsModel(repo=repo_stub.repo_name)
702 result = model.get_repo_ui_settings()
709 result = model.get_repo_ui_settings()
703 svn_sections = (
710 svn_sections = (
704 VcsSettingsModel.SVN_TAG_SECTION,
711 VcsSettingsModel.SVN_TAG_SECTION,
705 VcsSettingsModel.SVN_BRANCH_SECTION)
712 VcsSettingsModel.SVN_BRANCH_SECTION)
706 expected_result = [
713 expected_result = [
707 s for s in model.global_settings.get_ui()
714 s for s in model.global_settings.get_ui()
708 if s.section not in svn_sections]
715 if s.section not in svn_sections]
709 assert sorted(result) == sorted(expected_result)
716 assert sorted(result) == sorted(expected_result)
710
717
711 def test_repo_uis_are_overriding_global_uis(
718 def test_repo_uis_are_overriding_global_uis(
712 self, repo_stub, settings_util):
719 self, repo_stub, settings_util):
713 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
720 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
714 settings_util.create_repo_rhodecode_ui(
721 settings_util.create_repo_rhodecode_ui(
715 repo_stub, section, 'repo', key=key, active=False)
722 repo_stub, section, 'repo', key=key, active=False)
716 model = VcsSettingsModel(repo=repo_stub.repo_name)
723 model = VcsSettingsModel(repo=repo_stub.repo_name)
717 result = model.get_repo_ui_settings()
724 result = model.get_repo_ui_settings()
718 for setting in result:
725 for setting in result:
719 locator = (setting.section, setting.key)
726 locator = (setting.section, setting.key)
720 if locator in VcsSettingsModel.HOOKS_SETTINGS:
727 if locator in VcsSettingsModel.HOOKS_SETTINGS:
721 assert setting.value == 'repo'
728 assert setting.value == 'repo'
722
729
723 assert setting.active is False
730 assert setting.active is False
724
731
725 def test_global_svn_patterns_are_not_in_list(
732 def test_global_svn_patterns_are_not_in_list(
726 self, repo_stub, settings_util):
733 self, repo_stub, settings_util):
727 svn_sections = (
734 svn_sections = (
728 VcsSettingsModel.SVN_TAG_SECTION,
735 VcsSettingsModel.SVN_TAG_SECTION,
729 VcsSettingsModel.SVN_BRANCH_SECTION)
736 VcsSettingsModel.SVN_BRANCH_SECTION)
730 for section in svn_sections:
737 for section in svn_sections:
731 settings_util.create_rhodecode_ui(
738 settings_util.create_rhodecode_ui(
732 section, 'repo', key='deadbeef' + section, active=False)
739 section, 'repo', key='deadbeef' + section, active=False)
733 model = VcsSettingsModel(repo=repo_stub.repo_name)
740 model = VcsSettingsModel(repo=repo_stub.repo_name)
734 result = model.get_repo_ui_settings()
741 result = model.get_repo_ui_settings()
735 for setting in result:
742 for setting in result:
736 assert setting.section not in svn_sections
743 assert setting.section not in svn_sections
737
744
738 def test_repo_uis_filtered_by_section_are_returned(
745 def test_repo_uis_filtered_by_section_are_returned(
739 self, repo_stub, settings_util):
746 self, repo_stub, settings_util):
740 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
747 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
741 settings_util.create_repo_rhodecode_ui(
748 settings_util.create_repo_rhodecode_ui(
742 repo_stub, section, 'repo', key=key, active=False)
749 repo_stub, section, 'repo', key=key, active=False)
743 model = VcsSettingsModel(repo=repo_stub.repo_name)
750 model = VcsSettingsModel(repo=repo_stub.repo_name)
744 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
751 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
745 result = model.get_repo_ui_settings(section=section)
752 result = model.get_repo_ui_settings(section=section)
746 for setting in result:
753 for setting in result:
747 assert setting.section == section
754 assert setting.section == section
748
755
749 def test_repo_uis_filtered_by_key_are_returned(
756 def test_repo_uis_filtered_by_key_are_returned(
750 self, repo_stub, settings_util):
757 self, repo_stub, settings_util):
751 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
758 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
752 settings_util.create_repo_rhodecode_ui(
759 settings_util.create_repo_rhodecode_ui(
753 repo_stub, section, 'repo', key=key, active=False)
760 repo_stub, section, 'repo', key=key, active=False)
754 model = VcsSettingsModel(repo=repo_stub.repo_name)
761 model = VcsSettingsModel(repo=repo_stub.repo_name)
755 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
762 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
756 result = model.get_repo_ui_settings(key=key)
763 result = model.get_repo_ui_settings(key=key)
757 for setting in result:
764 for setting in result:
758 assert setting.key == key
765 assert setting.key == key
759
766
760 def test_raises_exception_when_repository_is_not_specified(self):
767 def test_raises_exception_when_repository_is_not_specified(self):
761 model = VcsSettingsModel()
768 model = VcsSettingsModel()
762 with pytest.raises(Exception) as exc_info:
769 with pytest.raises(Exception) as exc_info:
763 model.get_repo_ui_settings()
770 model.get_repo_ui_settings()
764 assert exc_info.value.message == 'Repository is not specified'
771 assert exc_info.value.message == 'Repository is not specified'
765
772
766
773
767 class TestGetRepoGeneralSettings(object):
774 class TestGetRepoGeneralSettings(object):
768 def test_global_settings_are_returned_when_no_repo_settings_found(
775 def test_global_settings_are_returned_when_no_repo_settings_found(
769 self, repo_stub):
776 self, repo_stub):
770 model = VcsSettingsModel(repo=repo_stub.repo_name)
777 model = VcsSettingsModel(repo=repo_stub.repo_name)
771 result = model.get_repo_general_settings()
778 result = model.get_repo_general_settings()
772 expected_result = model.global_settings.get_all_settings()
779 expected_result = model.global_settings.get_all_settings()
773 assert sorted(result) == sorted(expected_result)
780 assert sorted(result) == sorted(expected_result)
774
781
775 def test_repo_uis_are_overriding_global_uis(
782 def test_repo_uis_are_overriding_global_uis(
776 self, repo_stub, settings_util):
783 self, repo_stub, settings_util):
777 for key in VcsSettingsModel.GENERAL_SETTINGS:
784 for key in VcsSettingsModel.GENERAL_SETTINGS:
778 settings_util.create_repo_rhodecode_setting(
785 settings_util.create_repo_rhodecode_setting(
779 repo_stub, key, 'abcde', type_='unicode')
786 repo_stub, key, 'abcde', type_='unicode')
780 model = VcsSettingsModel(repo=repo_stub.repo_name)
787 model = VcsSettingsModel(repo=repo_stub.repo_name)
781 result = model.get_repo_ui_settings()
788 result = model.get_repo_ui_settings()
782 for key in result:
789 for key in result:
783 if key in VcsSettingsModel.GENERAL_SETTINGS:
790 if key in VcsSettingsModel.GENERAL_SETTINGS:
784 assert result[key] == 'abcde'
791 assert result[key] == 'abcde'
785
792
786 def test_raises_exception_when_repository_is_not_specified(self):
793 def test_raises_exception_when_repository_is_not_specified(self):
787 model = VcsSettingsModel()
794 model = VcsSettingsModel()
788 with pytest.raises(Exception) as exc_info:
795 with pytest.raises(Exception) as exc_info:
789 model.get_repo_general_settings()
796 model.get_repo_general_settings()
790 assert exc_info.value.message == 'Repository is not specified'
797 assert exc_info.value.message == 'Repository is not specified'
791
798
792
799
793 class TestGetGlobalGeneralSettings(object):
800 class TestGetGlobalGeneralSettings(object):
794 def test_global_settings_are_returned(self, repo_stub):
801 def test_global_settings_are_returned(self, repo_stub):
795 model = VcsSettingsModel()
802 model = VcsSettingsModel()
796 result = model.get_global_general_settings()
803 result = model.get_global_general_settings()
797 expected_result = model.global_settings.get_all_settings()
804 expected_result = model.global_settings.get_all_settings()
798 assert sorted(result) == sorted(expected_result)
805 assert sorted(result) == sorted(expected_result)
799
806
800 def test_repo_uis_are_not_overriding_global_uis(
807 def test_repo_uis_are_not_overriding_global_uis(
801 self, repo_stub, settings_util):
808 self, repo_stub, settings_util):
802 for key in VcsSettingsModel.GENERAL_SETTINGS:
809 for key in VcsSettingsModel.GENERAL_SETTINGS:
803 settings_util.create_repo_rhodecode_setting(
810 settings_util.create_repo_rhodecode_setting(
804 repo_stub, key, 'abcde', type_='unicode')
811 repo_stub, key, 'abcde', type_='unicode')
805 model = VcsSettingsModel(repo=repo_stub.repo_name)
812 model = VcsSettingsModel(repo=repo_stub.repo_name)
806 result = model.get_global_general_settings()
813 result = model.get_global_general_settings()
807 expected_result = model.global_settings.get_all_settings()
814 expected_result = model.global_settings.get_all_settings()
808 assert sorted(result) == sorted(expected_result)
815 assert sorted(result) == sorted(expected_result)
809
816
810
817
811 class TestGetGlobalUiSettings(object):
818 class TestGetGlobalUiSettings(object):
812 def test_global_uis_are_returned(self, repo_stub):
819 def test_global_uis_are_returned(self, repo_stub):
813 model = VcsSettingsModel()
820 model = VcsSettingsModel()
814 result = model.get_global_ui_settings()
821 result = model.get_global_ui_settings()
815 expected_result = model.global_settings.get_ui()
822 expected_result = model.global_settings.get_ui()
816 assert sorted(result) == sorted(expected_result)
823 assert sorted(result) == sorted(expected_result)
817
824
818 def test_repo_uis_are_not_overriding_global_uis(
825 def test_repo_uis_are_not_overriding_global_uis(
819 self, repo_stub, settings_util):
826 self, repo_stub, settings_util):
820 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
827 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
821 settings_util.create_repo_rhodecode_ui(
828 settings_util.create_repo_rhodecode_ui(
822 repo_stub, section, 'repo', key=key, active=False)
829 repo_stub, section, 'repo', key=key, active=False)
823 model = VcsSettingsModel(repo=repo_stub.repo_name)
830 model = VcsSettingsModel(repo=repo_stub.repo_name)
824 result = model.get_global_ui_settings()
831 result = model.get_global_ui_settings()
825 expected_result = model.global_settings.get_ui()
832 expected_result = model.global_settings.get_ui()
826 assert sorted(result) == sorted(expected_result)
833 assert sorted(result) == sorted(expected_result)
827
834
828 def test_ui_settings_filtered_by_section(
835 def test_ui_settings_filtered_by_section(
829 self, repo_stub, settings_util):
836 self, repo_stub, settings_util):
830 model = VcsSettingsModel(repo=repo_stub.repo_name)
837 model = VcsSettingsModel(repo=repo_stub.repo_name)
831 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
838 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
832 result = model.get_global_ui_settings(section=section)
839 result = model.get_global_ui_settings(section=section)
833 expected_result = model.global_settings.get_ui(section=section)
840 expected_result = model.global_settings.get_ui(section=section)
834 assert sorted(result) == sorted(expected_result)
841 assert sorted(result) == sorted(expected_result)
835
842
836 def test_ui_settings_filtered_by_key(
843 def test_ui_settings_filtered_by_key(
837 self, repo_stub, settings_util):
844 self, repo_stub, settings_util):
838 model = VcsSettingsModel(repo=repo_stub.repo_name)
845 model = VcsSettingsModel(repo=repo_stub.repo_name)
839 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
846 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
840 result = model.get_global_ui_settings(key=key)
847 result = model.get_global_ui_settings(key=key)
841 expected_result = model.global_settings.get_ui(key=key)
848 expected_result = model.global_settings.get_ui(key=key)
842 assert sorted(result) == sorted(expected_result)
849 assert sorted(result) == sorted(expected_result)
843
850
844
851
845 class TestGetGeneralSettings(object):
852 class TestGetGeneralSettings(object):
846 def test_global_settings_are_returned_when_inherited_is_true(
853 def test_global_settings_are_returned_when_inherited_is_true(
847 self, repo_stub, settings_util):
854 self, repo_stub, settings_util):
848 model = VcsSettingsModel(repo=repo_stub.repo_name)
855 model = VcsSettingsModel(repo=repo_stub.repo_name)
849 model.inherit_global_settings = True
856 model.inherit_global_settings = True
850 for key in VcsSettingsModel.GENERAL_SETTINGS:
857 for key in VcsSettingsModel.GENERAL_SETTINGS:
851 settings_util.create_repo_rhodecode_setting(
858 settings_util.create_repo_rhodecode_setting(
852 repo_stub, key, 'abcde', type_='unicode')
859 repo_stub, key, 'abcde', type_='unicode')
853 result = model.get_general_settings()
860 result = model.get_general_settings()
854 expected_result = model.get_global_general_settings()
861 expected_result = model.get_global_general_settings()
855 assert sorted(result) == sorted(expected_result)
862 assert sorted(result) == sorted(expected_result)
856
863
857 def test_repo_settings_are_returned_when_inherited_is_false(
864 def test_repo_settings_are_returned_when_inherited_is_false(
858 self, repo_stub, settings_util):
865 self, repo_stub, settings_util):
859 model = VcsSettingsModel(repo=repo_stub.repo_name)
866 model = VcsSettingsModel(repo=repo_stub.repo_name)
860 model.inherit_global_settings = False
867 model.inherit_global_settings = False
861 for key in VcsSettingsModel.GENERAL_SETTINGS:
868 for key in VcsSettingsModel.GENERAL_SETTINGS:
862 settings_util.create_repo_rhodecode_setting(
869 settings_util.create_repo_rhodecode_setting(
863 repo_stub, key, 'abcde', type_='unicode')
870 repo_stub, key, 'abcde', type_='unicode')
864 result = model.get_general_settings()
871 result = model.get_general_settings()
865 expected_result = model.get_repo_general_settings()
872 expected_result = model.get_repo_general_settings()
866 assert sorted(result) == sorted(expected_result)
873 assert sorted(result) == sorted(expected_result)
867
874
868 def test_global_settings_are_returned_when_no_repository_specified(self):
875 def test_global_settings_are_returned_when_no_repository_specified(self):
869 model = VcsSettingsModel()
876 model = VcsSettingsModel()
870 result = model.get_general_settings()
877 result = model.get_general_settings()
871 expected_result = model.get_global_general_settings()
878 expected_result = model.get_global_general_settings()
872 assert sorted(result) == sorted(expected_result)
879 assert sorted(result) == sorted(expected_result)
873
880
874
881
875 class TestGetUiSettings(object):
882 class TestGetUiSettings(object):
876 def test_global_settings_are_returned_when_inherited_is_true(
883 def test_global_settings_are_returned_when_inherited_is_true(
877 self, repo_stub, settings_util):
884 self, repo_stub, settings_util):
878 model = VcsSettingsModel(repo=repo_stub.repo_name)
885 model = VcsSettingsModel(repo=repo_stub.repo_name)
879 model.inherit_global_settings = True
886 model.inherit_global_settings = True
880 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
887 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
881 settings_util.create_repo_rhodecode_ui(
888 settings_util.create_repo_rhodecode_ui(
882 repo_stub, section, 'repo', key=key, active=True)
889 repo_stub, section, 'repo', key=key, active=True)
883 result = model.get_ui_settings()
890 result = model.get_ui_settings()
884 expected_result = model.get_global_ui_settings()
891 expected_result = model.get_global_ui_settings()
885 assert sorted(result) == sorted(expected_result)
892 assert sorted(result) == sorted(expected_result)
886
893
887 def test_repo_settings_are_returned_when_inherited_is_false(
894 def test_repo_settings_are_returned_when_inherited_is_false(
888 self, repo_stub, settings_util):
895 self, repo_stub, settings_util):
889 model = VcsSettingsModel(repo=repo_stub.repo_name)
896 model = VcsSettingsModel(repo=repo_stub.repo_name)
890 model.inherit_global_settings = False
897 model.inherit_global_settings = False
891 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
898 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
892 settings_util.create_repo_rhodecode_ui(
899 settings_util.create_repo_rhodecode_ui(
893 repo_stub, section, 'repo', key=key, active=True)
900 repo_stub, section, 'repo', key=key, active=True)
894 result = model.get_ui_settings()
901 result = model.get_ui_settings()
895 expected_result = model.get_repo_ui_settings()
902 expected_result = model.get_repo_ui_settings()
896 assert sorted(result) == sorted(expected_result)
903 assert sorted(result) == sorted(expected_result)
897
904
898 def test_repo_settings_filtered_by_section_and_key(self, repo_stub):
905 def test_repo_settings_filtered_by_section_and_key(self, repo_stub):
899 model = VcsSettingsModel(repo=repo_stub.repo_name)
906 model = VcsSettingsModel(repo=repo_stub.repo_name)
900 model.inherit_global_settings = False
907 model.inherit_global_settings = False
901 args = ('section', 'key')
908 args = ('section', 'key')
902 with mock.patch.object(model, 'get_repo_ui_settings') as settings_mock:
909 with mock.patch.object(model, 'get_repo_ui_settings') as settings_mock:
903 model.get_ui_settings(*args)
910 model.get_ui_settings(*args)
904 settings_mock.assert_called_once_with(*args)
911 settings_mock.assert_called_once_with(*args)
905
912
906 def test_global_settings_filtered_by_section_and_key(self):
913 def test_global_settings_filtered_by_section_and_key(self):
907 model = VcsSettingsModel()
914 model = VcsSettingsModel()
908 args = ('section', 'key')
915 args = ('section', 'key')
909 with mock.patch.object(model, 'get_global_ui_settings') as (
916 with mock.patch.object(model, 'get_global_ui_settings') as (
910 settings_mock):
917 settings_mock):
911 model.get_ui_settings(*args)
918 model.get_ui_settings(*args)
912 settings_mock.assert_called_once_with(*args)
919 settings_mock.assert_called_once_with(*args)
913
920
914 def test_global_settings_are_returned_when_no_repository_specified(self):
921 def test_global_settings_are_returned_when_no_repository_specified(self):
915 model = VcsSettingsModel()
922 model = VcsSettingsModel()
916 result = model.get_ui_settings()
923 result = model.get_ui_settings()
917 expected_result = model.get_global_ui_settings()
924 expected_result = model.get_global_ui_settings()
918 assert sorted(result) == sorted(expected_result)
925 assert sorted(result) == sorted(expected_result)
919
926
920
927
921 class TestGetSvnPatterns(object):
928 class TestGetSvnPatterns(object):
922 def test_repo_settings_filtered_by_section_and_key(self, repo_stub):
929 def test_repo_settings_filtered_by_section_and_key(self, repo_stub):
923 model = VcsSettingsModel(repo=repo_stub.repo_name)
930 model = VcsSettingsModel(repo=repo_stub.repo_name)
924 args = ('section', )
931 args = ('section', )
925 with mock.patch.object(model, 'get_repo_ui_settings') as settings_mock:
932 with mock.patch.object(model, 'get_repo_ui_settings') as settings_mock:
926 model.get_svn_patterns(*args)
933 model.get_svn_patterns(*args)
927 settings_mock.assert_called_once_with(*args)
934 settings_mock.assert_called_once_with(*args)
928
935
929 def test_global_settings_filtered_by_section_and_key(self):
936 def test_global_settings_filtered_by_section_and_key(self):
930 model = VcsSettingsModel()
937 model = VcsSettingsModel()
931 args = ('section', )
938 args = ('section', )
932 with mock.patch.object(model, 'get_global_ui_settings') as (
939 with mock.patch.object(model, 'get_global_ui_settings') as (
933 settings_mock):
940 settings_mock):
934 model.get_svn_patterns(*args)
941 model.get_svn_patterns(*args)
935 settings_mock.assert_called_once_with(*args)
942 settings_mock.assert_called_once_with(*args)
936
943
937
944
938 class TestGetReposLocation(object):
945 class TestGetReposLocation(object):
939 def test_returns_repos_location(self, repo_stub):
946 def test_returns_repos_location(self, repo_stub):
940 model = VcsSettingsModel()
947 model = VcsSettingsModel()
941
948
942 result_mock = mock.Mock()
949 result_mock = mock.Mock()
943 result_mock.ui_value = '/tmp'
950 result_mock.ui_value = '/tmp'
944
951
945 with mock.patch.object(model, 'global_settings') as settings_mock:
952 with mock.patch.object(model, 'global_settings') as settings_mock:
946 settings_mock.get_ui_by_key.return_value = result_mock
953 settings_mock.get_ui_by_key.return_value = result_mock
947 result = model.get_repos_location()
954 result = model.get_repos_location()
948
955
949 settings_mock.get_ui_by_key.assert_called_once_with('/')
956 settings_mock.get_ui_by_key.assert_called_once_with('/')
950 assert result == '/tmp'
957 assert result == '/tmp'
951
958
952
959
953 class TestCreateOrUpdateRepoSettings(object):
960 class TestCreateOrUpdateRepoSettings(object):
954 FORM_DATA = {
961 FORM_DATA = {
955 'inherit_global_settings': False,
962 'inherit_global_settings': False,
956 'hooks_changegroup_repo_size': False,
963 'hooks_changegroup_repo_size': False,
957 'hooks_changegroup_push_logger': False,
964 'hooks_changegroup_push_logger': False,
958 'hooks_outgoing_pull_logger': False,
965 'hooks_outgoing_pull_logger': False,
959 'extensions_largefiles': False,
966 'extensions_largefiles': False,
967 'extensions_evolve': False,
960 'largefiles_usercache': '/example/largefiles-store',
968 'largefiles_usercache': '/example/largefiles-store',
961 'vcs_git_lfs_enabled': False,
969 'vcs_git_lfs_enabled': False,
962 'vcs_git_lfs_store_location': '/',
970 'vcs_git_lfs_store_location': '/',
963 'phases_publish': 'False',
971 'phases_publish': 'False',
964 'rhodecode_pr_merge_enabled': False,
972 'rhodecode_pr_merge_enabled': False,
965 'rhodecode_use_outdated_comments': False,
973 'rhodecode_use_outdated_comments': False,
966 'new_svn_branch': '',
974 'new_svn_branch': '',
967 'new_svn_tag': ''
975 'new_svn_tag': ''
968 }
976 }
969
977
970 def test_get_raises_exception_when_repository_not_specified(self):
978 def test_get_raises_exception_when_repository_not_specified(self):
971 model = VcsSettingsModel()
979 model = VcsSettingsModel()
972 with pytest.raises(Exception) as exc_info:
980 with pytest.raises(Exception) as exc_info:
973 model.create_or_update_repo_settings(data=self.FORM_DATA)
981 model.create_or_update_repo_settings(data=self.FORM_DATA)
974 assert exc_info.value.message == 'Repository is not specified'
982 assert exc_info.value.message == 'Repository is not specified'
975
983
976 def test_only_svn_settings_are_updated_when_type_is_svn(self, backend_svn):
984 def test_only_svn_settings_are_updated_when_type_is_svn(self, backend_svn):
977 repo = backend_svn.create_repo()
985 repo = backend_svn.create_repo()
978 model = VcsSettingsModel(repo=repo)
986 model = VcsSettingsModel(repo=repo)
979 with self._patch_model(model) as mocks:
987 with self._patch_model(model) as mocks:
980 model.create_or_update_repo_settings(
988 model.create_or_update_repo_settings(
981 data=self.FORM_DATA, inherit_global_settings=False)
989 data=self.FORM_DATA, inherit_global_settings=False)
982 mocks['create_repo_svn_settings'].assert_called_once_with(
990 mocks['create_repo_svn_settings'].assert_called_once_with(
983 self.FORM_DATA)
991 self.FORM_DATA)
984 non_called_methods = (
992 non_called_methods = (
985 'create_or_update_repo_hook_settings',
993 'create_or_update_repo_hook_settings',
986 'create_or_update_repo_pr_settings',
994 'create_or_update_repo_pr_settings',
987 'create_or_update_repo_hg_settings')
995 'create_or_update_repo_hg_settings')
988 for method in non_called_methods:
996 for method in non_called_methods:
989 assert mocks[method].call_count == 0
997 assert mocks[method].call_count == 0
990
998
991 def test_non_svn_settings_are_updated_when_type_is_hg(self, backend_hg):
999 def test_non_svn_settings_are_updated_when_type_is_hg(self, backend_hg):
992 repo = backend_hg.create_repo()
1000 repo = backend_hg.create_repo()
993 model = VcsSettingsModel(repo=repo)
1001 model = VcsSettingsModel(repo=repo)
994 with self._patch_model(model) as mocks:
1002 with self._patch_model(model) as mocks:
995 model.create_or_update_repo_settings(
1003 model.create_or_update_repo_settings(
996 data=self.FORM_DATA, inherit_global_settings=False)
1004 data=self.FORM_DATA, inherit_global_settings=False)
997
1005
998 assert mocks['create_repo_svn_settings'].call_count == 0
1006 assert mocks['create_repo_svn_settings'].call_count == 0
999 called_methods = (
1007 called_methods = (
1000 'create_or_update_repo_hook_settings',
1008 'create_or_update_repo_hook_settings',
1001 'create_or_update_repo_pr_settings',
1009 'create_or_update_repo_pr_settings',
1002 'create_or_update_repo_hg_settings')
1010 'create_or_update_repo_hg_settings')
1003 for method in called_methods:
1011 for method in called_methods:
1004 mocks[method].assert_called_once_with(self.FORM_DATA)
1012 mocks[method].assert_called_once_with(self.FORM_DATA)
1005
1013
1006 def test_non_svn_and_hg_settings_are_updated_when_type_is_git(
1014 def test_non_svn_and_hg_settings_are_updated_when_type_is_git(
1007 self, backend_git):
1015 self, backend_git):
1008 repo = backend_git.create_repo()
1016 repo = backend_git.create_repo()
1009 model = VcsSettingsModel(repo=repo)
1017 model = VcsSettingsModel(repo=repo)
1010 with self._patch_model(model) as mocks:
1018 with self._patch_model(model) as mocks:
1011 model.create_or_update_repo_settings(
1019 model.create_or_update_repo_settings(
1012 data=self.FORM_DATA, inherit_global_settings=False)
1020 data=self.FORM_DATA, inherit_global_settings=False)
1013
1021
1014 assert mocks['create_repo_svn_settings'].call_count == 0
1022 assert mocks['create_repo_svn_settings'].call_count == 0
1015 called_methods = (
1023 called_methods = (
1016 'create_or_update_repo_hook_settings',
1024 'create_or_update_repo_hook_settings',
1017 'create_or_update_repo_pr_settings')
1025 'create_or_update_repo_pr_settings')
1018 non_called_methods = (
1026 non_called_methods = (
1019 'create_repo_svn_settings',
1027 'create_repo_svn_settings',
1020 'create_or_update_repo_hg_settings'
1028 'create_or_update_repo_hg_settings'
1021 )
1029 )
1022 for method in called_methods:
1030 for method in called_methods:
1023 mocks[method].assert_called_once_with(self.FORM_DATA)
1031 mocks[method].assert_called_once_with(self.FORM_DATA)
1024 for method in non_called_methods:
1032 for method in non_called_methods:
1025 assert mocks[method].call_count == 0
1033 assert mocks[method].call_count == 0
1026
1034
1027 def test_no_methods_are_called_when_settings_are_inherited(
1035 def test_no_methods_are_called_when_settings_are_inherited(
1028 self, backend):
1036 self, backend):
1029 repo = backend.create_repo()
1037 repo = backend.create_repo()
1030 model = VcsSettingsModel(repo=repo)
1038 model = VcsSettingsModel(repo=repo)
1031 with self._patch_model(model) as mocks:
1039 with self._patch_model(model) as mocks:
1032 model.create_or_update_repo_settings(
1040 model.create_or_update_repo_settings(
1033 data=self.FORM_DATA, inherit_global_settings=True)
1041 data=self.FORM_DATA, inherit_global_settings=True)
1034 for method_name in mocks:
1042 for method_name in mocks:
1035 assert mocks[method_name].call_count == 0
1043 assert mocks[method_name].call_count == 0
1036
1044
1037 def test_cache_is_marked_for_invalidation(self, repo_stub):
1045 def test_cache_is_marked_for_invalidation(self, repo_stub):
1038 model = VcsSettingsModel(repo=repo_stub)
1046 model = VcsSettingsModel(repo=repo_stub)
1039 invalidation_patcher = mock.patch(
1047 invalidation_patcher = mock.patch(
1040 'rhodecode.controllers.admin.repos.ScmModel.mark_for_invalidation')
1048 'rhodecode.controllers.admin.repos.ScmModel.mark_for_invalidation')
1041 with invalidation_patcher as invalidation_mock:
1049 with invalidation_patcher as invalidation_mock:
1042 model.create_or_update_repo_settings(
1050 model.create_or_update_repo_settings(
1043 data=self.FORM_DATA, inherit_global_settings=True)
1051 data=self.FORM_DATA, inherit_global_settings=True)
1044 invalidation_mock.assert_called_once_with(
1052 invalidation_mock.assert_called_once_with(
1045 repo_stub.repo_name, delete=True)
1053 repo_stub.repo_name, delete=True)
1046
1054
1047 def test_inherit_flag_is_saved(self, repo_stub):
1055 def test_inherit_flag_is_saved(self, repo_stub):
1048 model = VcsSettingsModel(repo=repo_stub)
1056 model = VcsSettingsModel(repo=repo_stub)
1049 model.inherit_global_settings = True
1057 model.inherit_global_settings = True
1050 with self._patch_model(model):
1058 with self._patch_model(model):
1051 model.create_or_update_repo_settings(
1059 model.create_or_update_repo_settings(
1052 data=self.FORM_DATA, inherit_global_settings=False)
1060 data=self.FORM_DATA, inherit_global_settings=False)
1053 assert model.inherit_global_settings is False
1061 assert model.inherit_global_settings is False
1054
1062
1055 def _patch_model(self, model):
1063 def _patch_model(self, model):
1056 return mock.patch.multiple(
1064 return mock.patch.multiple(
1057 model,
1065 model,
1058 create_repo_svn_settings=mock.DEFAULT,
1066 create_repo_svn_settings=mock.DEFAULT,
1059 create_or_update_repo_hook_settings=mock.DEFAULT,
1067 create_or_update_repo_hook_settings=mock.DEFAULT,
1060 create_or_update_repo_pr_settings=mock.DEFAULT,
1068 create_or_update_repo_pr_settings=mock.DEFAULT,
1061 create_or_update_repo_hg_settings=mock.DEFAULT)
1069 create_or_update_repo_hg_settings=mock.DEFAULT)
General Comments 0
You need to be logged in to leave comments. Login now