##// END OF EJS Templates
exceptions: use python3 compatible exception handling
marcink -
r3104:4de59e0f default
parent child Browse files
Show More
@@ -1,39 +1,42 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2018 RhodeCode GmbH
3 # Copyright (C) 2011-2018 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 class JSONRPCBaseError(Exception):
22 class JSONRPCBaseError(Exception):
23 pass
23 def __init__(self, message='', *args):
24 self.message = message
25 super(JSONRPCBaseError, self).__init__(message, *args)
24
26
25
27
26 class JSONRPCError(JSONRPCBaseError):
28 class JSONRPCError(JSONRPCBaseError):
27 pass
29 pass
28
30
29
31
30 class JSONRPCValidationError(JSONRPCBaseError):
32 class JSONRPCValidationError(JSONRPCBaseError):
31
33
32 def __init__(self, *args, **kwargs):
34 def __init__(self, *args, **kwargs):
33 self.colander_exception = kwargs.pop('colander_exc')
35 self.colander_exception = kwargs.pop('colander_exc')
34 super(JSONRPCValidationError, self).__init__(*args, **kwargs)
36 super(JSONRPCValidationError, self).__init__(
37 message=self.colander_exception, *args)
35
38
36
39
37 class JSONRPCForbidden(JSONRPCBaseError):
40 class JSONRPCForbidden(JSONRPCBaseError):
38 pass
41 pass
39
42
@@ -1,2065 +1,2065 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2018 RhodeCode GmbH
3 # Copyright (C) 2011-2018 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 import audit_logger
32 from rhodecode.lib import audit_logger
33 from rhodecode.lib import repo_maintenance
33 from rhodecode.lib import repo_maintenance
34 from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi
34 from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi
35 from rhodecode.lib.celerylib.utils import get_task_id
35 from rhodecode.lib.celerylib.utils import get_task_id
36 from rhodecode.lib.utils2 import str2bool, time_to_datetime
36 from rhodecode.lib.utils2 import str2bool, time_to_datetime, safe_str
37 from rhodecode.lib.ext_json import json
37 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
38 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
39 from rhodecode.model.changeset_status import ChangesetStatusModel
39 from rhodecode.model.changeset_status import ChangesetStatusModel
40 from rhodecode.model.comment import CommentsModel
40 from rhodecode.model.comment import CommentsModel
41 from rhodecode.model.db import (
41 from rhodecode.model.db import (
42 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup,
42 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup,
43 ChangesetComment)
43 ChangesetComment)
44 from rhodecode.model.repo import RepoModel
44 from rhodecode.model.repo import RepoModel
45 from rhodecode.model.scm import ScmModel, RepoList
45 from rhodecode.model.scm import ScmModel, RepoList
46 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
46 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
47 from rhodecode.model import validation_schema
47 from rhodecode.model import validation_schema
48 from rhodecode.model.validation_schema.schemas import repo_schema
48 from rhodecode.model.validation_schema.schemas import repo_schema
49
49
50 log = logging.getLogger(__name__)
50 log = logging.getLogger(__name__)
51
51
52
52
53 @jsonrpc_method()
53 @jsonrpc_method()
54 def get_repo(request, apiuser, repoid, cache=Optional(True)):
54 def get_repo(request, apiuser, repoid, cache=Optional(True)):
55 """
55 """
56 Gets an existing repository by its name or repository_id.
56 Gets an existing repository by its name or repository_id.
57
57
58 The members section so the output returns users groups or users
58 The members section so the output returns users groups or users
59 associated with that repository.
59 associated with that repository.
60
60
61 This command can only be run using an |authtoken| with admin rights,
61 This command can only be run using an |authtoken| with admin rights,
62 or users with at least read rights to the |repo|.
62 or users with at least read rights to the |repo|.
63
63
64 :param apiuser: This is filled automatically from the |authtoken|.
64 :param apiuser: This is filled automatically from the |authtoken|.
65 :type apiuser: AuthUser
65 :type apiuser: AuthUser
66 :param repoid: The repository name or repository id.
66 :param repoid: The repository name or repository id.
67 :type repoid: str or int
67 :type repoid: str or int
68 :param cache: use the cached value for last changeset
68 :param cache: use the cached value for last changeset
69 :type: cache: Optional(bool)
69 :type: cache: Optional(bool)
70
70
71 Example output:
71 Example output:
72
72
73 .. code-block:: bash
73 .. code-block:: bash
74
74
75 {
75 {
76 "error": null,
76 "error": null,
77 "id": <repo_id>,
77 "id": <repo_id>,
78 "result": {
78 "result": {
79 "clone_uri": null,
79 "clone_uri": null,
80 "created_on": "timestamp",
80 "created_on": "timestamp",
81 "description": "repo description",
81 "description": "repo description",
82 "enable_downloads": false,
82 "enable_downloads": false,
83 "enable_locking": false,
83 "enable_locking": false,
84 "enable_statistics": false,
84 "enable_statistics": false,
85 "followers": [
85 "followers": [
86 {
86 {
87 "active": true,
87 "active": true,
88 "admin": false,
88 "admin": false,
89 "api_key": "****************************************",
89 "api_key": "****************************************",
90 "api_keys": [
90 "api_keys": [
91 "****************************************"
91 "****************************************"
92 ],
92 ],
93 "email": "user@example.com",
93 "email": "user@example.com",
94 "emails": [
94 "emails": [
95 "user@example.com"
95 "user@example.com"
96 ],
96 ],
97 "extern_name": "rhodecode",
97 "extern_name": "rhodecode",
98 "extern_type": "rhodecode",
98 "extern_type": "rhodecode",
99 "firstname": "username",
99 "firstname": "username",
100 "ip_addresses": [],
100 "ip_addresses": [],
101 "language": null,
101 "language": null,
102 "last_login": "2015-09-16T17:16:35.854",
102 "last_login": "2015-09-16T17:16:35.854",
103 "lastname": "surname",
103 "lastname": "surname",
104 "user_id": <user_id>,
104 "user_id": <user_id>,
105 "username": "name"
105 "username": "name"
106 }
106 }
107 ],
107 ],
108 "fork_of": "parent-repo",
108 "fork_of": "parent-repo",
109 "landing_rev": [
109 "landing_rev": [
110 "rev",
110 "rev",
111 "tip"
111 "tip"
112 ],
112 ],
113 "last_changeset": {
113 "last_changeset": {
114 "author": "User <user@example.com>",
114 "author": "User <user@example.com>",
115 "branch": "default",
115 "branch": "default",
116 "date": "timestamp",
116 "date": "timestamp",
117 "message": "last commit message",
117 "message": "last commit message",
118 "parents": [
118 "parents": [
119 {
119 {
120 "raw_id": "commit-id"
120 "raw_id": "commit-id"
121 }
121 }
122 ],
122 ],
123 "raw_id": "commit-id",
123 "raw_id": "commit-id",
124 "revision": <revision number>,
124 "revision": <revision number>,
125 "short_id": "short id"
125 "short_id": "short id"
126 },
126 },
127 "lock_reason": null,
127 "lock_reason": null,
128 "locked_by": null,
128 "locked_by": null,
129 "locked_date": null,
129 "locked_date": null,
130 "owner": "owner-name",
130 "owner": "owner-name",
131 "permissions": [
131 "permissions": [
132 {
132 {
133 "name": "super-admin-name",
133 "name": "super-admin-name",
134 "origin": "super-admin",
134 "origin": "super-admin",
135 "permission": "repository.admin",
135 "permission": "repository.admin",
136 "type": "user"
136 "type": "user"
137 },
137 },
138 {
138 {
139 "name": "owner-name",
139 "name": "owner-name",
140 "origin": "owner",
140 "origin": "owner",
141 "permission": "repository.admin",
141 "permission": "repository.admin",
142 "type": "user"
142 "type": "user"
143 },
143 },
144 {
144 {
145 "name": "user-group-name",
145 "name": "user-group-name",
146 "origin": "permission",
146 "origin": "permission",
147 "permission": "repository.write",
147 "permission": "repository.write",
148 "type": "user_group"
148 "type": "user_group"
149 }
149 }
150 ],
150 ],
151 "private": true,
151 "private": true,
152 "repo_id": 676,
152 "repo_id": 676,
153 "repo_name": "user-group/repo-name",
153 "repo_name": "user-group/repo-name",
154 "repo_type": "hg"
154 "repo_type": "hg"
155 }
155 }
156 }
156 }
157 """
157 """
158
158
159 repo = get_repo_or_error(repoid)
159 repo = get_repo_or_error(repoid)
160 cache = Optional.extract(cache)
160 cache = Optional.extract(cache)
161
161
162 include_secrets = False
162 include_secrets = False
163 if has_superadmin_permission(apiuser):
163 if has_superadmin_permission(apiuser):
164 include_secrets = True
164 include_secrets = True
165 else:
165 else:
166 # check if we have at least read permission for this repo !
166 # check if we have at least read permission for this repo !
167 _perms = (
167 _perms = (
168 'repository.admin', 'repository.write', 'repository.read',)
168 'repository.admin', 'repository.write', 'repository.read',)
169 validate_repo_permissions(apiuser, repoid, repo, _perms)
169 validate_repo_permissions(apiuser, repoid, repo, _perms)
170
170
171 permissions = []
171 permissions = []
172 for _user in repo.permissions():
172 for _user in repo.permissions():
173 user_data = {
173 user_data = {
174 'name': _user.username,
174 'name': _user.username,
175 'permission': _user.permission,
175 'permission': _user.permission,
176 'origin': get_origin(_user),
176 'origin': get_origin(_user),
177 'type': "user",
177 'type': "user",
178 }
178 }
179 permissions.append(user_data)
179 permissions.append(user_data)
180
180
181 for _user_group in repo.permission_user_groups():
181 for _user_group in repo.permission_user_groups():
182 user_group_data = {
182 user_group_data = {
183 'name': _user_group.users_group_name,
183 'name': _user_group.users_group_name,
184 'permission': _user_group.permission,
184 'permission': _user_group.permission,
185 'origin': get_origin(_user_group),
185 'origin': get_origin(_user_group),
186 'type': "user_group",
186 'type': "user_group",
187 }
187 }
188 permissions.append(user_group_data)
188 permissions.append(user_group_data)
189
189
190 following_users = [
190 following_users = [
191 user.user.get_api_data(include_secrets=include_secrets)
191 user.user.get_api_data(include_secrets=include_secrets)
192 for user in repo.followers]
192 for user in repo.followers]
193
193
194 if not cache:
194 if not cache:
195 repo.update_commit_cache()
195 repo.update_commit_cache()
196 data = repo.get_api_data(include_secrets=include_secrets)
196 data = repo.get_api_data(include_secrets=include_secrets)
197 data['permissions'] = permissions
197 data['permissions'] = permissions
198 data['followers'] = following_users
198 data['followers'] = following_users
199 return data
199 return data
200
200
201
201
202 @jsonrpc_method()
202 @jsonrpc_method()
203 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
203 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
204 """
204 """
205 Lists all existing repositories.
205 Lists all existing repositories.
206
206
207 This command can only be run using an |authtoken| with admin rights,
207 This command can only be run using an |authtoken| with admin rights,
208 or users with at least read rights to |repos|.
208 or users with at least read rights to |repos|.
209
209
210 :param apiuser: This is filled automatically from the |authtoken|.
210 :param apiuser: This is filled automatically from the |authtoken|.
211 :type apiuser: AuthUser
211 :type apiuser: AuthUser
212 :param root: specify root repository group to fetch repositories.
212 :param root: specify root repository group to fetch repositories.
213 filters the returned repositories to be members of given root group.
213 filters the returned repositories to be members of given root group.
214 :type root: Optional(None)
214 :type root: Optional(None)
215 :param traverse: traverse given root into subrepositories. With this flag
215 :param traverse: traverse given root into subrepositories. With this flag
216 set to False, it will only return top-level repositories from `root`.
216 set to False, it will only return top-level repositories from `root`.
217 if root is empty it will return just top-level repositories.
217 if root is empty it will return just top-level repositories.
218 :type traverse: Optional(True)
218 :type traverse: Optional(True)
219
219
220
220
221 Example output:
221 Example output:
222
222
223 .. code-block:: bash
223 .. code-block:: bash
224
224
225 id : <id_given_in_input>
225 id : <id_given_in_input>
226 result: [
226 result: [
227 {
227 {
228 "repo_id" : "<repo_id>",
228 "repo_id" : "<repo_id>",
229 "repo_name" : "<reponame>"
229 "repo_name" : "<reponame>"
230 "repo_type" : "<repo_type>",
230 "repo_type" : "<repo_type>",
231 "clone_uri" : "<clone_uri>",
231 "clone_uri" : "<clone_uri>",
232 "private": : "<bool>",
232 "private": : "<bool>",
233 "created_on" : "<datetimecreated>",
233 "created_on" : "<datetimecreated>",
234 "description" : "<description>",
234 "description" : "<description>",
235 "landing_rev": "<landing_rev>",
235 "landing_rev": "<landing_rev>",
236 "owner": "<repo_owner>",
236 "owner": "<repo_owner>",
237 "fork_of": "<name_of_fork_parent>",
237 "fork_of": "<name_of_fork_parent>",
238 "enable_downloads": "<bool>",
238 "enable_downloads": "<bool>",
239 "enable_locking": "<bool>",
239 "enable_locking": "<bool>",
240 "enable_statistics": "<bool>",
240 "enable_statistics": "<bool>",
241 },
241 },
242 ...
242 ...
243 ]
243 ]
244 error: null
244 error: null
245 """
245 """
246
246
247 include_secrets = has_superadmin_permission(apiuser)
247 include_secrets = has_superadmin_permission(apiuser)
248 _perms = ('repository.read', 'repository.write', 'repository.admin',)
248 _perms = ('repository.read', 'repository.write', 'repository.admin',)
249 extras = {'user': apiuser}
249 extras = {'user': apiuser}
250
250
251 root = Optional.extract(root)
251 root = Optional.extract(root)
252 traverse = Optional.extract(traverse, binary=True)
252 traverse = Optional.extract(traverse, binary=True)
253
253
254 if root:
254 if root:
255 # verify parent existance, if it's empty return an error
255 # verify parent existance, if it's empty return an error
256 parent = RepoGroup.get_by_group_name(root)
256 parent = RepoGroup.get_by_group_name(root)
257 if not parent:
257 if not parent:
258 raise JSONRPCError(
258 raise JSONRPCError(
259 'Root repository group `{}` does not exist'.format(root))
259 'Root repository group `{}` does not exist'.format(root))
260
260
261 if traverse:
261 if traverse:
262 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
262 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
263 else:
263 else:
264 repos = RepoModel().get_repos_for_root(root=parent)
264 repos = RepoModel().get_repos_for_root(root=parent)
265 else:
265 else:
266 if traverse:
266 if traverse:
267 repos = RepoModel().get_all()
267 repos = RepoModel().get_all()
268 else:
268 else:
269 # return just top-level
269 # return just top-level
270 repos = RepoModel().get_repos_for_root(root=None)
270 repos = RepoModel().get_repos_for_root(root=None)
271
271
272 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
272 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
273 return [repo.get_api_data(include_secrets=include_secrets)
273 return [repo.get_api_data(include_secrets=include_secrets)
274 for repo in repo_list]
274 for repo in repo_list]
275
275
276
276
277 @jsonrpc_method()
277 @jsonrpc_method()
278 def get_repo_changeset(request, apiuser, repoid, revision,
278 def get_repo_changeset(request, apiuser, repoid, revision,
279 details=Optional('basic')):
279 details=Optional('basic')):
280 """
280 """
281 Returns information about a changeset.
281 Returns information about a changeset.
282
282
283 Additionally parameters define the amount of details returned by
283 Additionally parameters define the amount of details returned by
284 this function.
284 this function.
285
285
286 This command can only be run using an |authtoken| with admin rights,
286 This command can only be run using an |authtoken| with admin rights,
287 or users with at least read rights to the |repo|.
287 or users with at least read rights to the |repo|.
288
288
289 :param apiuser: This is filled automatically from the |authtoken|.
289 :param apiuser: This is filled automatically from the |authtoken|.
290 :type apiuser: AuthUser
290 :type apiuser: AuthUser
291 :param repoid: The repository name or repository id
291 :param repoid: The repository name or repository id
292 :type repoid: str or int
292 :type repoid: str or int
293 :param revision: revision for which listing should be done
293 :param revision: revision for which listing should be done
294 :type revision: str
294 :type revision: str
295 :param details: details can be 'basic|extended|full' full gives diff
295 :param details: details can be 'basic|extended|full' full gives diff
296 info details like the diff itself, and number of changed files etc.
296 info details like the diff itself, and number of changed files etc.
297 :type details: Optional(str)
297 :type details: Optional(str)
298
298
299 """
299 """
300 repo = get_repo_or_error(repoid)
300 repo = get_repo_or_error(repoid)
301 if not has_superadmin_permission(apiuser):
301 if not has_superadmin_permission(apiuser):
302 _perms = (
302 _perms = (
303 'repository.admin', 'repository.write', 'repository.read',)
303 'repository.admin', 'repository.write', 'repository.read',)
304 validate_repo_permissions(apiuser, repoid, repo, _perms)
304 validate_repo_permissions(apiuser, repoid, repo, _perms)
305
305
306 changes_details = Optional.extract(details)
306 changes_details = Optional.extract(details)
307 _changes_details_types = ['basic', 'extended', 'full']
307 _changes_details_types = ['basic', 'extended', 'full']
308 if changes_details not in _changes_details_types:
308 if changes_details not in _changes_details_types:
309 raise JSONRPCError(
309 raise JSONRPCError(
310 'ret_type must be one of %s' % (
310 'ret_type must be one of %s' % (
311 ','.join(_changes_details_types)))
311 ','.join(_changes_details_types)))
312
312
313 pre_load = ['author', 'branch', 'date', 'message', 'parents',
313 pre_load = ['author', 'branch', 'date', 'message', 'parents',
314 'status', '_commit', '_file_paths']
314 'status', '_commit', '_file_paths']
315
315
316 try:
316 try:
317 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
317 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
318 except TypeError as e:
318 except TypeError as e:
319 raise JSONRPCError(e.message)
319 raise JSONRPCError(safe_str(e))
320 _cs_json = cs.__json__()
320 _cs_json = cs.__json__()
321 _cs_json['diff'] = build_commit_data(cs, changes_details)
321 _cs_json['diff'] = build_commit_data(cs, changes_details)
322 if changes_details == 'full':
322 if changes_details == 'full':
323 _cs_json['refs'] = cs._get_refs()
323 _cs_json['refs'] = cs._get_refs()
324 return _cs_json
324 return _cs_json
325
325
326
326
327 @jsonrpc_method()
327 @jsonrpc_method()
328 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
328 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
329 details=Optional('basic')):
329 details=Optional('basic')):
330 """
330 """
331 Returns a set of commits limited by the number starting
331 Returns a set of commits limited by the number starting
332 from the `start_rev` option.
332 from the `start_rev` option.
333
333
334 Additional parameters define the amount of details returned by this
334 Additional parameters define the amount of details returned by this
335 function.
335 function.
336
336
337 This command can only be run using an |authtoken| with admin rights,
337 This command can only be run using an |authtoken| with admin rights,
338 or users with at least read rights to |repos|.
338 or users with at least read rights to |repos|.
339
339
340 :param apiuser: This is filled automatically from the |authtoken|.
340 :param apiuser: This is filled automatically from the |authtoken|.
341 :type apiuser: AuthUser
341 :type apiuser: AuthUser
342 :param repoid: The repository name or repository ID.
342 :param repoid: The repository name or repository ID.
343 :type repoid: str or int
343 :type repoid: str or int
344 :param start_rev: The starting revision from where to get changesets.
344 :param start_rev: The starting revision from where to get changesets.
345 :type start_rev: str
345 :type start_rev: str
346 :param limit: Limit the number of commits to this amount
346 :param limit: Limit the number of commits to this amount
347 :type limit: str or int
347 :type limit: str or int
348 :param details: Set the level of detail returned. Valid option are:
348 :param details: Set the level of detail returned. Valid option are:
349 ``basic``, ``extended`` and ``full``.
349 ``basic``, ``extended`` and ``full``.
350 :type details: Optional(str)
350 :type details: Optional(str)
351
351
352 .. note::
352 .. note::
353
353
354 Setting the parameter `details` to the value ``full`` is extensive
354 Setting the parameter `details` to the value ``full`` is extensive
355 and returns details like the diff itself, and the number
355 and returns details like the diff itself, and the number
356 of changed files.
356 of changed files.
357
357
358 """
358 """
359 repo = get_repo_or_error(repoid)
359 repo = get_repo_or_error(repoid)
360 if not has_superadmin_permission(apiuser):
360 if not has_superadmin_permission(apiuser):
361 _perms = (
361 _perms = (
362 'repository.admin', 'repository.write', 'repository.read',)
362 'repository.admin', 'repository.write', 'repository.read',)
363 validate_repo_permissions(apiuser, repoid, repo, _perms)
363 validate_repo_permissions(apiuser, repoid, repo, _perms)
364
364
365 changes_details = Optional.extract(details)
365 changes_details = Optional.extract(details)
366 _changes_details_types = ['basic', 'extended', 'full']
366 _changes_details_types = ['basic', 'extended', 'full']
367 if changes_details not in _changes_details_types:
367 if changes_details not in _changes_details_types:
368 raise JSONRPCError(
368 raise JSONRPCError(
369 'ret_type must be one of %s' % (
369 'ret_type must be one of %s' % (
370 ','.join(_changes_details_types)))
370 ','.join(_changes_details_types)))
371
371
372 limit = int(limit)
372 limit = int(limit)
373 pre_load = ['author', 'branch', 'date', 'message', 'parents',
373 pre_load = ['author', 'branch', 'date', 'message', 'parents',
374 'status', '_commit', '_file_paths']
374 'status', '_commit', '_file_paths']
375
375
376 vcs_repo = repo.scm_instance()
376 vcs_repo = repo.scm_instance()
377 # SVN needs a special case to distinguish its index and commit id
377 # SVN needs a special case to distinguish its index and commit id
378 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
378 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
379 start_rev = vcs_repo.commit_ids[0]
379 start_rev = vcs_repo.commit_ids[0]
380
380
381 try:
381 try:
382 commits = vcs_repo.get_commits(
382 commits = vcs_repo.get_commits(
383 start_id=start_rev, pre_load=pre_load)
383 start_id=start_rev, pre_load=pre_load)
384 except TypeError as e:
384 except TypeError as e:
385 raise JSONRPCError(e.message)
385 raise JSONRPCError(safe_str(e))
386 except Exception:
386 except Exception:
387 log.exception('Fetching of commits failed')
387 log.exception('Fetching of commits failed')
388 raise JSONRPCError('Error occurred during commit fetching')
388 raise JSONRPCError('Error occurred during commit fetching')
389
389
390 ret = []
390 ret = []
391 for cnt, commit in enumerate(commits):
391 for cnt, commit in enumerate(commits):
392 if cnt >= limit != -1:
392 if cnt >= limit != -1:
393 break
393 break
394 _cs_json = commit.__json__()
394 _cs_json = commit.__json__()
395 _cs_json['diff'] = build_commit_data(commit, changes_details)
395 _cs_json['diff'] = build_commit_data(commit, changes_details)
396 if changes_details == 'full':
396 if changes_details == 'full':
397 _cs_json['refs'] = {
397 _cs_json['refs'] = {
398 'branches': [commit.branch],
398 'branches': [commit.branch],
399 'bookmarks': getattr(commit, 'bookmarks', []),
399 'bookmarks': getattr(commit, 'bookmarks', []),
400 'tags': commit.tags
400 'tags': commit.tags
401 }
401 }
402 ret.append(_cs_json)
402 ret.append(_cs_json)
403 return ret
403 return ret
404
404
405
405
406 @jsonrpc_method()
406 @jsonrpc_method()
407 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
407 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
408 ret_type=Optional('all'), details=Optional('basic'),
408 ret_type=Optional('all'), details=Optional('basic'),
409 max_file_bytes=Optional(None)):
409 max_file_bytes=Optional(None)):
410 """
410 """
411 Returns a list of nodes and children in a flat list for a given
411 Returns a list of nodes and children in a flat list for a given
412 path at given revision.
412 path at given revision.
413
413
414 It's possible to specify ret_type to show only `files` or `dirs`.
414 It's possible to specify ret_type to show only `files` or `dirs`.
415
415
416 This command can only be run using an |authtoken| with admin rights,
416 This command can only be run using an |authtoken| with admin rights,
417 or users with at least read rights to |repos|.
417 or users with at least read rights to |repos|.
418
418
419 :param apiuser: This is filled automatically from the |authtoken|.
419 :param apiuser: This is filled automatically from the |authtoken|.
420 :type apiuser: AuthUser
420 :type apiuser: AuthUser
421 :param repoid: The repository name or repository ID.
421 :param repoid: The repository name or repository ID.
422 :type repoid: str or int
422 :type repoid: str or int
423 :param revision: The revision for which listing should be done.
423 :param revision: The revision for which listing should be done.
424 :type revision: str
424 :type revision: str
425 :param root_path: The path from which to start displaying.
425 :param root_path: The path from which to start displaying.
426 :type root_path: str
426 :type root_path: str
427 :param ret_type: Set the return type. Valid options are
427 :param ret_type: Set the return type. Valid options are
428 ``all`` (default), ``files`` and ``dirs``.
428 ``all`` (default), ``files`` and ``dirs``.
429 :type ret_type: Optional(str)
429 :type ret_type: Optional(str)
430 :param details: Returns extended information about nodes, such as
430 :param details: Returns extended information about nodes, such as
431 md5, binary, and or content. The valid options are ``basic`` and
431 md5, binary, and or content. The valid options are ``basic`` and
432 ``full``.
432 ``full``.
433 :type details: Optional(str)
433 :type details: Optional(str)
434 :param max_file_bytes: Only return file content under this file size bytes
434 :param max_file_bytes: Only return file content under this file size bytes
435 :type details: Optional(int)
435 :type details: Optional(int)
436
436
437 Example output:
437 Example output:
438
438
439 .. code-block:: bash
439 .. code-block:: bash
440
440
441 id : <id_given_in_input>
441 id : <id_given_in_input>
442 result: [
442 result: [
443 {
443 {
444 "name" : "<name>"
444 "name" : "<name>"
445 "type" : "<type>",
445 "type" : "<type>",
446 "binary": "<true|false>" (only in extended mode)
446 "binary": "<true|false>" (only in extended mode)
447 "md5" : "<md5 of file content>" (only in extended mode)
447 "md5" : "<md5 of file content>" (only in extended mode)
448 },
448 },
449 ...
449 ...
450 ]
450 ]
451 error: null
451 error: null
452 """
452 """
453
453
454 repo = get_repo_or_error(repoid)
454 repo = get_repo_or_error(repoid)
455 if not has_superadmin_permission(apiuser):
455 if not has_superadmin_permission(apiuser):
456 _perms = (
456 _perms = (
457 'repository.admin', 'repository.write', 'repository.read',)
457 'repository.admin', 'repository.write', 'repository.read',)
458 validate_repo_permissions(apiuser, repoid, repo, _perms)
458 validate_repo_permissions(apiuser, repoid, repo, _perms)
459
459
460 ret_type = Optional.extract(ret_type)
460 ret_type = Optional.extract(ret_type)
461 details = Optional.extract(details)
461 details = Optional.extract(details)
462 _extended_types = ['basic', 'full']
462 _extended_types = ['basic', 'full']
463 if details not in _extended_types:
463 if details not in _extended_types:
464 raise JSONRPCError(
464 raise JSONRPCError(
465 'ret_type must be one of %s' % (','.join(_extended_types)))
465 'ret_type must be one of %s' % (','.join(_extended_types)))
466 extended_info = False
466 extended_info = False
467 content = False
467 content = False
468 if details == 'basic':
468 if details == 'basic':
469 extended_info = True
469 extended_info = True
470
470
471 if details == 'full':
471 if details == 'full':
472 extended_info = content = True
472 extended_info = content = True
473
473
474 _map = {}
474 _map = {}
475 try:
475 try:
476 # check if repo is not empty by any chance, skip quicker if it is.
476 # check if repo is not empty by any chance, skip quicker if it is.
477 _scm = repo.scm_instance()
477 _scm = repo.scm_instance()
478 if _scm.is_empty():
478 if _scm.is_empty():
479 return []
479 return []
480
480
481 _d, _f = ScmModel().get_nodes(
481 _d, _f = ScmModel().get_nodes(
482 repo, revision, root_path, flat=False,
482 repo, revision, root_path, flat=False,
483 extended_info=extended_info, content=content,
483 extended_info=extended_info, content=content,
484 max_file_bytes=max_file_bytes)
484 max_file_bytes=max_file_bytes)
485 _map = {
485 _map = {
486 'all': _d + _f,
486 'all': _d + _f,
487 'files': _f,
487 'files': _f,
488 'dirs': _d,
488 'dirs': _d,
489 }
489 }
490 return _map[ret_type]
490 return _map[ret_type]
491 except KeyError:
491 except KeyError:
492 raise JSONRPCError(
492 raise JSONRPCError(
493 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
493 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
494 except Exception:
494 except Exception:
495 log.exception("Exception occurred while trying to get repo nodes")
495 log.exception("Exception occurred while trying to get repo nodes")
496 raise JSONRPCError(
496 raise JSONRPCError(
497 'failed to get repo: `%s` nodes' % repo.repo_name
497 'failed to get repo: `%s` nodes' % repo.repo_name
498 )
498 )
499
499
500
500
501 @jsonrpc_method()
501 @jsonrpc_method()
502 def get_repo_refs(request, apiuser, repoid):
502 def get_repo_refs(request, apiuser, repoid):
503 """
503 """
504 Returns a dictionary of current references. It returns
504 Returns a dictionary of current references. It returns
505 bookmarks, branches, closed_branches, and tags for given repository
505 bookmarks, branches, closed_branches, and tags for given repository
506
506
507 It's possible to specify ret_type to show only `files` or `dirs`.
507 It's possible to specify ret_type to show only `files` or `dirs`.
508
508
509 This command can only be run using an |authtoken| with admin rights,
509 This command can only be run using an |authtoken| with admin rights,
510 or users with at least read rights to |repos|.
510 or users with at least read rights to |repos|.
511
511
512 :param apiuser: This is filled automatically from the |authtoken|.
512 :param apiuser: This is filled automatically from the |authtoken|.
513 :type apiuser: AuthUser
513 :type apiuser: AuthUser
514 :param repoid: The repository name or repository ID.
514 :param repoid: The repository name or repository ID.
515 :type repoid: str or int
515 :type repoid: str or int
516
516
517 Example output:
517 Example output:
518
518
519 .. code-block:: bash
519 .. code-block:: bash
520
520
521 id : <id_given_in_input>
521 id : <id_given_in_input>
522 "result": {
522 "result": {
523 "bookmarks": {
523 "bookmarks": {
524 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
524 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
525 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
525 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
526 },
526 },
527 "branches": {
527 "branches": {
528 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
528 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
529 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
529 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
530 },
530 },
531 "branches_closed": {},
531 "branches_closed": {},
532 "tags": {
532 "tags": {
533 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
533 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
534 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
534 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
535 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
535 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
536 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
536 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
537 }
537 }
538 }
538 }
539 error: null
539 error: null
540 """
540 """
541
541
542 repo = get_repo_or_error(repoid)
542 repo = get_repo_or_error(repoid)
543 if not has_superadmin_permission(apiuser):
543 if not has_superadmin_permission(apiuser):
544 _perms = ('repository.admin', 'repository.write', 'repository.read',)
544 _perms = ('repository.admin', 'repository.write', 'repository.read',)
545 validate_repo_permissions(apiuser, repoid, repo, _perms)
545 validate_repo_permissions(apiuser, repoid, repo, _perms)
546
546
547 try:
547 try:
548 # check if repo is not empty by any chance, skip quicker if it is.
548 # check if repo is not empty by any chance, skip quicker if it is.
549 vcs_instance = repo.scm_instance()
549 vcs_instance = repo.scm_instance()
550 refs = vcs_instance.refs()
550 refs = vcs_instance.refs()
551 return refs
551 return refs
552 except Exception:
552 except Exception:
553 log.exception("Exception occurred while trying to get repo refs")
553 log.exception("Exception occurred while trying to get repo refs")
554 raise JSONRPCError(
554 raise JSONRPCError(
555 'failed to get repo: `%s` references' % repo.repo_name
555 'failed to get repo: `%s` references' % repo.repo_name
556 )
556 )
557
557
558
558
559 @jsonrpc_method()
559 @jsonrpc_method()
560 def create_repo(
560 def create_repo(
561 request, apiuser, repo_name, repo_type,
561 request, apiuser, repo_name, repo_type,
562 owner=Optional(OAttr('apiuser')),
562 owner=Optional(OAttr('apiuser')),
563 description=Optional(''),
563 description=Optional(''),
564 private=Optional(False),
564 private=Optional(False),
565 clone_uri=Optional(None),
565 clone_uri=Optional(None),
566 push_uri=Optional(None),
566 push_uri=Optional(None),
567 landing_rev=Optional('rev:tip'),
567 landing_rev=Optional('rev:tip'),
568 enable_statistics=Optional(False),
568 enable_statistics=Optional(False),
569 enable_locking=Optional(False),
569 enable_locking=Optional(False),
570 enable_downloads=Optional(False),
570 enable_downloads=Optional(False),
571 copy_permissions=Optional(False)):
571 copy_permissions=Optional(False)):
572 """
572 """
573 Creates a repository.
573 Creates a repository.
574
574
575 * If the repository name contains "/", repository will be created inside
575 * If the repository name contains "/", repository will be created inside
576 a repository group or nested repository groups
576 a repository group or nested repository groups
577
577
578 For example "foo/bar/repo1" will create |repo| called "repo1" inside
578 For example "foo/bar/repo1" will create |repo| called "repo1" inside
579 group "foo/bar". You have to have permissions to access and write to
579 group "foo/bar". You have to have permissions to access and write to
580 the last repository group ("bar" in this example)
580 the last repository group ("bar" in this example)
581
581
582 This command can only be run using an |authtoken| with at least
582 This command can only be run using an |authtoken| with at least
583 permissions to create repositories, or write permissions to
583 permissions to create repositories, or write permissions to
584 parent repository groups.
584 parent repository groups.
585
585
586 :param apiuser: This is filled automatically from the |authtoken|.
586 :param apiuser: This is filled automatically from the |authtoken|.
587 :type apiuser: AuthUser
587 :type apiuser: AuthUser
588 :param repo_name: Set the repository name.
588 :param repo_name: Set the repository name.
589 :type repo_name: str
589 :type repo_name: str
590 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
590 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
591 :type repo_type: str
591 :type repo_type: str
592 :param owner: user_id or username
592 :param owner: user_id or username
593 :type owner: Optional(str)
593 :type owner: Optional(str)
594 :param description: Set the repository description.
594 :param description: Set the repository description.
595 :type description: Optional(str)
595 :type description: Optional(str)
596 :param private: set repository as private
596 :param private: set repository as private
597 :type private: bool
597 :type private: bool
598 :param clone_uri: set clone_uri
598 :param clone_uri: set clone_uri
599 :type clone_uri: str
599 :type clone_uri: str
600 :param push_uri: set push_uri
600 :param push_uri: set push_uri
601 :type push_uri: str
601 :type push_uri: str
602 :param landing_rev: <rev_type>:<rev>
602 :param landing_rev: <rev_type>:<rev>
603 :type landing_rev: str
603 :type landing_rev: str
604 :param enable_locking:
604 :param enable_locking:
605 :type enable_locking: bool
605 :type enable_locking: bool
606 :param enable_downloads:
606 :param enable_downloads:
607 :type enable_downloads: bool
607 :type enable_downloads: bool
608 :param enable_statistics:
608 :param enable_statistics:
609 :type enable_statistics: bool
609 :type enable_statistics: bool
610 :param copy_permissions: Copy permission from group in which the
610 :param copy_permissions: Copy permission from group in which the
611 repository is being created.
611 repository is being created.
612 :type copy_permissions: bool
612 :type copy_permissions: bool
613
613
614
614
615 Example output:
615 Example output:
616
616
617 .. code-block:: bash
617 .. code-block:: bash
618
618
619 id : <id_given_in_input>
619 id : <id_given_in_input>
620 result: {
620 result: {
621 "msg": "Created new repository `<reponame>`",
621 "msg": "Created new repository `<reponame>`",
622 "success": true,
622 "success": true,
623 "task": "<celery task id or None if done sync>"
623 "task": "<celery task id or None if done sync>"
624 }
624 }
625 error: null
625 error: null
626
626
627
627
628 Example error output:
628 Example error output:
629
629
630 .. code-block:: bash
630 .. code-block:: bash
631
631
632 id : <id_given_in_input>
632 id : <id_given_in_input>
633 result : null
633 result : null
634 error : {
634 error : {
635 'failed to create repository `<repo_name>`'
635 'failed to create repository `<repo_name>`'
636 }
636 }
637
637
638 """
638 """
639
639
640 owner = validate_set_owner_permissions(apiuser, owner)
640 owner = validate_set_owner_permissions(apiuser, owner)
641
641
642 description = Optional.extract(description)
642 description = Optional.extract(description)
643 copy_permissions = Optional.extract(copy_permissions)
643 copy_permissions = Optional.extract(copy_permissions)
644 clone_uri = Optional.extract(clone_uri)
644 clone_uri = Optional.extract(clone_uri)
645 push_uri = Optional.extract(push_uri)
645 push_uri = Optional.extract(push_uri)
646 landing_commit_ref = Optional.extract(landing_rev)
646 landing_commit_ref = Optional.extract(landing_rev)
647
647
648 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
648 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
649 if isinstance(private, Optional):
649 if isinstance(private, Optional):
650 private = defs.get('repo_private') or Optional.extract(private)
650 private = defs.get('repo_private') or Optional.extract(private)
651 if isinstance(repo_type, Optional):
651 if isinstance(repo_type, Optional):
652 repo_type = defs.get('repo_type')
652 repo_type = defs.get('repo_type')
653 if isinstance(enable_statistics, Optional):
653 if isinstance(enable_statistics, Optional):
654 enable_statistics = defs.get('repo_enable_statistics')
654 enable_statistics = defs.get('repo_enable_statistics')
655 if isinstance(enable_locking, Optional):
655 if isinstance(enable_locking, Optional):
656 enable_locking = defs.get('repo_enable_locking')
656 enable_locking = defs.get('repo_enable_locking')
657 if isinstance(enable_downloads, Optional):
657 if isinstance(enable_downloads, Optional):
658 enable_downloads = defs.get('repo_enable_downloads')
658 enable_downloads = defs.get('repo_enable_downloads')
659
659
660 schema = repo_schema.RepoSchema().bind(
660 schema = repo_schema.RepoSchema().bind(
661 repo_type_options=rhodecode.BACKENDS.keys(),
661 repo_type_options=rhodecode.BACKENDS.keys(),
662 repo_type=repo_type,
662 repo_type=repo_type,
663 # user caller
663 # user caller
664 user=apiuser)
664 user=apiuser)
665
665
666 try:
666 try:
667 schema_data = schema.deserialize(dict(
667 schema_data = schema.deserialize(dict(
668 repo_name=repo_name,
668 repo_name=repo_name,
669 repo_type=repo_type,
669 repo_type=repo_type,
670 repo_owner=owner.username,
670 repo_owner=owner.username,
671 repo_description=description,
671 repo_description=description,
672 repo_landing_commit_ref=landing_commit_ref,
672 repo_landing_commit_ref=landing_commit_ref,
673 repo_clone_uri=clone_uri,
673 repo_clone_uri=clone_uri,
674 repo_push_uri=push_uri,
674 repo_push_uri=push_uri,
675 repo_private=private,
675 repo_private=private,
676 repo_copy_permissions=copy_permissions,
676 repo_copy_permissions=copy_permissions,
677 repo_enable_statistics=enable_statistics,
677 repo_enable_statistics=enable_statistics,
678 repo_enable_downloads=enable_downloads,
678 repo_enable_downloads=enable_downloads,
679 repo_enable_locking=enable_locking))
679 repo_enable_locking=enable_locking))
680 except validation_schema.Invalid as err:
680 except validation_schema.Invalid as err:
681 raise JSONRPCValidationError(colander_exc=err)
681 raise JSONRPCValidationError(colander_exc=err)
682
682
683 try:
683 try:
684 data = {
684 data = {
685 'owner': owner,
685 'owner': owner,
686 'repo_name': schema_data['repo_group']['repo_name_without_group'],
686 'repo_name': schema_data['repo_group']['repo_name_without_group'],
687 'repo_name_full': schema_data['repo_name'],
687 'repo_name_full': schema_data['repo_name'],
688 'repo_group': schema_data['repo_group']['repo_group_id'],
688 'repo_group': schema_data['repo_group']['repo_group_id'],
689 'repo_type': schema_data['repo_type'],
689 'repo_type': schema_data['repo_type'],
690 'repo_description': schema_data['repo_description'],
690 'repo_description': schema_data['repo_description'],
691 'repo_private': schema_data['repo_private'],
691 'repo_private': schema_data['repo_private'],
692 'clone_uri': schema_data['repo_clone_uri'],
692 'clone_uri': schema_data['repo_clone_uri'],
693 'push_uri': schema_data['repo_push_uri'],
693 'push_uri': schema_data['repo_push_uri'],
694 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
694 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
695 'enable_statistics': schema_data['repo_enable_statistics'],
695 'enable_statistics': schema_data['repo_enable_statistics'],
696 'enable_locking': schema_data['repo_enable_locking'],
696 'enable_locking': schema_data['repo_enable_locking'],
697 'enable_downloads': schema_data['repo_enable_downloads'],
697 'enable_downloads': schema_data['repo_enable_downloads'],
698 'repo_copy_permissions': schema_data['repo_copy_permissions'],
698 'repo_copy_permissions': schema_data['repo_copy_permissions'],
699 }
699 }
700
700
701 task = RepoModel().create(form_data=data, cur_user=owner.user_id)
701 task = RepoModel().create(form_data=data, cur_user=owner.user_id)
702 task_id = get_task_id(task)
702 task_id = get_task_id(task)
703 # no commit, it's done in RepoModel, or async via celery
703 # no commit, it's done in RepoModel, or async via celery
704 return {
704 return {
705 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
705 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
706 'success': True, # cannot return the repo data here since fork
706 'success': True, # cannot return the repo data here since fork
707 # can be done async
707 # can be done async
708 'task': task_id
708 'task': task_id
709 }
709 }
710 except Exception:
710 except Exception:
711 log.exception(
711 log.exception(
712 u"Exception while trying to create the repository %s",
712 u"Exception while trying to create the repository %s",
713 schema_data['repo_name'])
713 schema_data['repo_name'])
714 raise JSONRPCError(
714 raise JSONRPCError(
715 'failed to create repository `%s`' % (schema_data['repo_name'],))
715 'failed to create repository `%s`' % (schema_data['repo_name'],))
716
716
717
717
718 @jsonrpc_method()
718 @jsonrpc_method()
719 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
719 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
720 description=Optional('')):
720 description=Optional('')):
721 """
721 """
722 Adds an extra field to a repository.
722 Adds an extra field to a repository.
723
723
724 This command can only be run using an |authtoken| with at least
724 This command can only be run using an |authtoken| with at least
725 write permissions to the |repo|.
725 write permissions to the |repo|.
726
726
727 :param apiuser: This is filled automatically from the |authtoken|.
727 :param apiuser: This is filled automatically from the |authtoken|.
728 :type apiuser: AuthUser
728 :type apiuser: AuthUser
729 :param repoid: Set the repository name or repository id.
729 :param repoid: Set the repository name or repository id.
730 :type repoid: str or int
730 :type repoid: str or int
731 :param key: Create a unique field key for this repository.
731 :param key: Create a unique field key for this repository.
732 :type key: str
732 :type key: str
733 :param label:
733 :param label:
734 :type label: Optional(str)
734 :type label: Optional(str)
735 :param description:
735 :param description:
736 :type description: Optional(str)
736 :type description: Optional(str)
737 """
737 """
738 repo = get_repo_or_error(repoid)
738 repo = get_repo_or_error(repoid)
739 if not has_superadmin_permission(apiuser):
739 if not has_superadmin_permission(apiuser):
740 _perms = ('repository.admin',)
740 _perms = ('repository.admin',)
741 validate_repo_permissions(apiuser, repoid, repo, _perms)
741 validate_repo_permissions(apiuser, repoid, repo, _perms)
742
742
743 label = Optional.extract(label) or key
743 label = Optional.extract(label) or key
744 description = Optional.extract(description)
744 description = Optional.extract(description)
745
745
746 field = RepositoryField.get_by_key_name(key, repo)
746 field = RepositoryField.get_by_key_name(key, repo)
747 if field:
747 if field:
748 raise JSONRPCError('Field with key '
748 raise JSONRPCError('Field with key '
749 '`%s` exists for repo `%s`' % (key, repoid))
749 '`%s` exists for repo `%s`' % (key, repoid))
750
750
751 try:
751 try:
752 RepoModel().add_repo_field(repo, key, field_label=label,
752 RepoModel().add_repo_field(repo, key, field_label=label,
753 field_desc=description)
753 field_desc=description)
754 Session().commit()
754 Session().commit()
755 return {
755 return {
756 'msg': "Added new repository field `%s`" % (key,),
756 'msg': "Added new repository field `%s`" % (key,),
757 'success': True,
757 'success': True,
758 }
758 }
759 except Exception:
759 except Exception:
760 log.exception("Exception occurred while trying to add field to repo")
760 log.exception("Exception occurred while trying to add field to repo")
761 raise JSONRPCError(
761 raise JSONRPCError(
762 'failed to create new field for repository `%s`' % (repoid,))
762 'failed to create new field for repository `%s`' % (repoid,))
763
763
764
764
765 @jsonrpc_method()
765 @jsonrpc_method()
766 def remove_field_from_repo(request, apiuser, repoid, key):
766 def remove_field_from_repo(request, apiuser, repoid, key):
767 """
767 """
768 Removes an extra field from a repository.
768 Removes an extra field from a repository.
769
769
770 This command can only be run using an |authtoken| with at least
770 This command can only be run using an |authtoken| with at least
771 write permissions to the |repo|.
771 write permissions to the |repo|.
772
772
773 :param apiuser: This is filled automatically from the |authtoken|.
773 :param apiuser: This is filled automatically from the |authtoken|.
774 :type apiuser: AuthUser
774 :type apiuser: AuthUser
775 :param repoid: Set the repository name or repository ID.
775 :param repoid: Set the repository name or repository ID.
776 :type repoid: str or int
776 :type repoid: str or int
777 :param key: Set the unique field key for this repository.
777 :param key: Set the unique field key for this repository.
778 :type key: str
778 :type key: str
779 """
779 """
780
780
781 repo = get_repo_or_error(repoid)
781 repo = get_repo_or_error(repoid)
782 if not has_superadmin_permission(apiuser):
782 if not has_superadmin_permission(apiuser):
783 _perms = ('repository.admin',)
783 _perms = ('repository.admin',)
784 validate_repo_permissions(apiuser, repoid, repo, _perms)
784 validate_repo_permissions(apiuser, repoid, repo, _perms)
785
785
786 field = RepositoryField.get_by_key_name(key, repo)
786 field = RepositoryField.get_by_key_name(key, repo)
787 if not field:
787 if not field:
788 raise JSONRPCError('Field with key `%s` does not '
788 raise JSONRPCError('Field with key `%s` does not '
789 'exists for repo `%s`' % (key, repoid))
789 'exists for repo `%s`' % (key, repoid))
790
790
791 try:
791 try:
792 RepoModel().delete_repo_field(repo, field_key=key)
792 RepoModel().delete_repo_field(repo, field_key=key)
793 Session().commit()
793 Session().commit()
794 return {
794 return {
795 'msg': "Deleted repository field `%s`" % (key,),
795 'msg': "Deleted repository field `%s`" % (key,),
796 'success': True,
796 'success': True,
797 }
797 }
798 except Exception:
798 except Exception:
799 log.exception(
799 log.exception(
800 "Exception occurred while trying to delete field from repo")
800 "Exception occurred while trying to delete field from repo")
801 raise JSONRPCError(
801 raise JSONRPCError(
802 'failed to delete field for repository `%s`' % (repoid,))
802 'failed to delete field for repository `%s`' % (repoid,))
803
803
804
804
805 @jsonrpc_method()
805 @jsonrpc_method()
806 def update_repo(
806 def update_repo(
807 request, apiuser, repoid, repo_name=Optional(None),
807 request, apiuser, repoid, repo_name=Optional(None),
808 owner=Optional(OAttr('apiuser')), description=Optional(''),
808 owner=Optional(OAttr('apiuser')), description=Optional(''),
809 private=Optional(False),
809 private=Optional(False),
810 clone_uri=Optional(None), push_uri=Optional(None),
810 clone_uri=Optional(None), push_uri=Optional(None),
811 landing_rev=Optional('rev:tip'), fork_of=Optional(None),
811 landing_rev=Optional('rev:tip'), fork_of=Optional(None),
812 enable_statistics=Optional(False),
812 enable_statistics=Optional(False),
813 enable_locking=Optional(False),
813 enable_locking=Optional(False),
814 enable_downloads=Optional(False), fields=Optional('')):
814 enable_downloads=Optional(False), fields=Optional('')):
815 """
815 """
816 Updates a repository with the given information.
816 Updates a repository with the given information.
817
817
818 This command can only be run using an |authtoken| with at least
818 This command can only be run using an |authtoken| with at least
819 admin permissions to the |repo|.
819 admin permissions to the |repo|.
820
820
821 * If the repository name contains "/", repository will be updated
821 * If the repository name contains "/", repository will be updated
822 accordingly with a repository group or nested repository groups
822 accordingly with a repository group or nested repository groups
823
823
824 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
824 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
825 called "repo-test" and place it inside group "foo/bar".
825 called "repo-test" and place it inside group "foo/bar".
826 You have to have permissions to access and write to the last repository
826 You have to have permissions to access and write to the last repository
827 group ("bar" in this example)
827 group ("bar" in this example)
828
828
829 :param apiuser: This is filled automatically from the |authtoken|.
829 :param apiuser: This is filled automatically from the |authtoken|.
830 :type apiuser: AuthUser
830 :type apiuser: AuthUser
831 :param repoid: repository name or repository ID.
831 :param repoid: repository name or repository ID.
832 :type repoid: str or int
832 :type repoid: str or int
833 :param repo_name: Update the |repo| name, including the
833 :param repo_name: Update the |repo| name, including the
834 repository group it's in.
834 repository group it's in.
835 :type repo_name: str
835 :type repo_name: str
836 :param owner: Set the |repo| owner.
836 :param owner: Set the |repo| owner.
837 :type owner: str
837 :type owner: str
838 :param fork_of: Set the |repo| as fork of another |repo|.
838 :param fork_of: Set the |repo| as fork of another |repo|.
839 :type fork_of: str
839 :type fork_of: str
840 :param description: Update the |repo| description.
840 :param description: Update the |repo| description.
841 :type description: str
841 :type description: str
842 :param private: Set the |repo| as private. (True | False)
842 :param private: Set the |repo| as private. (True | False)
843 :type private: bool
843 :type private: bool
844 :param clone_uri: Update the |repo| clone URI.
844 :param clone_uri: Update the |repo| clone URI.
845 :type clone_uri: str
845 :type clone_uri: str
846 :param landing_rev: Set the |repo| landing revision. Default is ``rev:tip``.
846 :param landing_rev: Set the |repo| landing revision. Default is ``rev:tip``.
847 :type landing_rev: str
847 :type landing_rev: str
848 :param enable_statistics: Enable statistics on the |repo|, (True | False).
848 :param enable_statistics: Enable statistics on the |repo|, (True | False).
849 :type enable_statistics: bool
849 :type enable_statistics: bool
850 :param enable_locking: Enable |repo| locking.
850 :param enable_locking: Enable |repo| locking.
851 :type enable_locking: bool
851 :type enable_locking: bool
852 :param enable_downloads: Enable downloads from the |repo|, (True | False).
852 :param enable_downloads: Enable downloads from the |repo|, (True | False).
853 :type enable_downloads: bool
853 :type enable_downloads: bool
854 :param fields: Add extra fields to the |repo|. Use the following
854 :param fields: Add extra fields to the |repo|. Use the following
855 example format: ``field_key=field_val,field_key2=fieldval2``.
855 example format: ``field_key=field_val,field_key2=fieldval2``.
856 Escape ', ' with \,
856 Escape ', ' with \,
857 :type fields: str
857 :type fields: str
858 """
858 """
859
859
860 repo = get_repo_or_error(repoid)
860 repo = get_repo_or_error(repoid)
861
861
862 include_secrets = False
862 include_secrets = False
863 if not has_superadmin_permission(apiuser):
863 if not has_superadmin_permission(apiuser):
864 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
864 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
865 else:
865 else:
866 include_secrets = True
866 include_secrets = True
867
867
868 updates = dict(
868 updates = dict(
869 repo_name=repo_name
869 repo_name=repo_name
870 if not isinstance(repo_name, Optional) else repo.repo_name,
870 if not isinstance(repo_name, Optional) else repo.repo_name,
871
871
872 fork_id=fork_of
872 fork_id=fork_of
873 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
873 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
874
874
875 user=owner
875 user=owner
876 if not isinstance(owner, Optional) else repo.user.username,
876 if not isinstance(owner, Optional) else repo.user.username,
877
877
878 repo_description=description
878 repo_description=description
879 if not isinstance(description, Optional) else repo.description,
879 if not isinstance(description, Optional) else repo.description,
880
880
881 repo_private=private
881 repo_private=private
882 if not isinstance(private, Optional) else repo.private,
882 if not isinstance(private, Optional) else repo.private,
883
883
884 clone_uri=clone_uri
884 clone_uri=clone_uri
885 if not isinstance(clone_uri, Optional) else repo.clone_uri,
885 if not isinstance(clone_uri, Optional) else repo.clone_uri,
886
886
887 push_uri=push_uri
887 push_uri=push_uri
888 if not isinstance(push_uri, Optional) else repo.push_uri,
888 if not isinstance(push_uri, Optional) else repo.push_uri,
889
889
890 repo_landing_rev=landing_rev
890 repo_landing_rev=landing_rev
891 if not isinstance(landing_rev, Optional) else repo._landing_revision,
891 if not isinstance(landing_rev, Optional) else repo._landing_revision,
892
892
893 repo_enable_statistics=enable_statistics
893 repo_enable_statistics=enable_statistics
894 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
894 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
895
895
896 repo_enable_locking=enable_locking
896 repo_enable_locking=enable_locking
897 if not isinstance(enable_locking, Optional) else repo.enable_locking,
897 if not isinstance(enable_locking, Optional) else repo.enable_locking,
898
898
899 repo_enable_downloads=enable_downloads
899 repo_enable_downloads=enable_downloads
900 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
900 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
901
901
902 ref_choices, _labels = ScmModel().get_repo_landing_revs(
902 ref_choices, _labels = ScmModel().get_repo_landing_revs(
903 request.translate, repo=repo)
903 request.translate, repo=repo)
904
904
905 old_values = repo.get_api_data()
905 old_values = repo.get_api_data()
906 repo_type = repo.repo_type
906 repo_type = repo.repo_type
907 schema = repo_schema.RepoSchema().bind(
907 schema = repo_schema.RepoSchema().bind(
908 repo_type_options=rhodecode.BACKENDS.keys(),
908 repo_type_options=rhodecode.BACKENDS.keys(),
909 repo_ref_options=ref_choices,
909 repo_ref_options=ref_choices,
910 repo_type=repo_type,
910 repo_type=repo_type,
911 # user caller
911 # user caller
912 user=apiuser,
912 user=apiuser,
913 old_values=old_values)
913 old_values=old_values)
914 try:
914 try:
915 schema_data = schema.deserialize(dict(
915 schema_data = schema.deserialize(dict(
916 # we save old value, users cannot change type
916 # we save old value, users cannot change type
917 repo_type=repo_type,
917 repo_type=repo_type,
918
918
919 repo_name=updates['repo_name'],
919 repo_name=updates['repo_name'],
920 repo_owner=updates['user'],
920 repo_owner=updates['user'],
921 repo_description=updates['repo_description'],
921 repo_description=updates['repo_description'],
922 repo_clone_uri=updates['clone_uri'],
922 repo_clone_uri=updates['clone_uri'],
923 repo_push_uri=updates['push_uri'],
923 repo_push_uri=updates['push_uri'],
924 repo_fork_of=updates['fork_id'],
924 repo_fork_of=updates['fork_id'],
925 repo_private=updates['repo_private'],
925 repo_private=updates['repo_private'],
926 repo_landing_commit_ref=updates['repo_landing_rev'],
926 repo_landing_commit_ref=updates['repo_landing_rev'],
927 repo_enable_statistics=updates['repo_enable_statistics'],
927 repo_enable_statistics=updates['repo_enable_statistics'],
928 repo_enable_downloads=updates['repo_enable_downloads'],
928 repo_enable_downloads=updates['repo_enable_downloads'],
929 repo_enable_locking=updates['repo_enable_locking']))
929 repo_enable_locking=updates['repo_enable_locking']))
930 except validation_schema.Invalid as err:
930 except validation_schema.Invalid as err:
931 raise JSONRPCValidationError(colander_exc=err)
931 raise JSONRPCValidationError(colander_exc=err)
932
932
933 # save validated data back into the updates dict
933 # save validated data back into the updates dict
934 validated_updates = dict(
934 validated_updates = dict(
935 repo_name=schema_data['repo_group']['repo_name_without_group'],
935 repo_name=schema_data['repo_group']['repo_name_without_group'],
936 repo_group=schema_data['repo_group']['repo_group_id'],
936 repo_group=schema_data['repo_group']['repo_group_id'],
937
937
938 user=schema_data['repo_owner'],
938 user=schema_data['repo_owner'],
939 repo_description=schema_data['repo_description'],
939 repo_description=schema_data['repo_description'],
940 repo_private=schema_data['repo_private'],
940 repo_private=schema_data['repo_private'],
941 clone_uri=schema_data['repo_clone_uri'],
941 clone_uri=schema_data['repo_clone_uri'],
942 push_uri=schema_data['repo_push_uri'],
942 push_uri=schema_data['repo_push_uri'],
943 repo_landing_rev=schema_data['repo_landing_commit_ref'],
943 repo_landing_rev=schema_data['repo_landing_commit_ref'],
944 repo_enable_statistics=schema_data['repo_enable_statistics'],
944 repo_enable_statistics=schema_data['repo_enable_statistics'],
945 repo_enable_locking=schema_data['repo_enable_locking'],
945 repo_enable_locking=schema_data['repo_enable_locking'],
946 repo_enable_downloads=schema_data['repo_enable_downloads'],
946 repo_enable_downloads=schema_data['repo_enable_downloads'],
947 )
947 )
948
948
949 if schema_data['repo_fork_of']:
949 if schema_data['repo_fork_of']:
950 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
950 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
951 validated_updates['fork_id'] = fork_repo.repo_id
951 validated_updates['fork_id'] = fork_repo.repo_id
952
952
953 # extra fields
953 # extra fields
954 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
954 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
955 if fields:
955 if fields:
956 validated_updates.update(fields)
956 validated_updates.update(fields)
957
957
958 try:
958 try:
959 RepoModel().update(repo, **validated_updates)
959 RepoModel().update(repo, **validated_updates)
960 audit_logger.store_api(
960 audit_logger.store_api(
961 'repo.edit', action_data={'old_data': old_values},
961 'repo.edit', action_data={'old_data': old_values},
962 user=apiuser, repo=repo)
962 user=apiuser, repo=repo)
963 Session().commit()
963 Session().commit()
964 return {
964 return {
965 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
965 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
966 'repository': repo.get_api_data(include_secrets=include_secrets)
966 'repository': repo.get_api_data(include_secrets=include_secrets)
967 }
967 }
968 except Exception:
968 except Exception:
969 log.exception(
969 log.exception(
970 u"Exception while trying to update the repository %s",
970 u"Exception while trying to update the repository %s",
971 repoid)
971 repoid)
972 raise JSONRPCError('failed to update repo `%s`' % repoid)
972 raise JSONRPCError('failed to update repo `%s`' % repoid)
973
973
974
974
975 @jsonrpc_method()
975 @jsonrpc_method()
976 def fork_repo(request, apiuser, repoid, fork_name,
976 def fork_repo(request, apiuser, repoid, fork_name,
977 owner=Optional(OAttr('apiuser')),
977 owner=Optional(OAttr('apiuser')),
978 description=Optional(''),
978 description=Optional(''),
979 private=Optional(False),
979 private=Optional(False),
980 clone_uri=Optional(None),
980 clone_uri=Optional(None),
981 landing_rev=Optional('rev:tip'),
981 landing_rev=Optional('rev:tip'),
982 copy_permissions=Optional(False)):
982 copy_permissions=Optional(False)):
983 """
983 """
984 Creates a fork of the specified |repo|.
984 Creates a fork of the specified |repo|.
985
985
986 * If the fork_name contains "/", fork will be created inside
986 * If the fork_name contains "/", fork will be created inside
987 a repository group or nested repository groups
987 a repository group or nested repository groups
988
988
989 For example "foo/bar/fork-repo" will create fork called "fork-repo"
989 For example "foo/bar/fork-repo" will create fork called "fork-repo"
990 inside group "foo/bar". You have to have permissions to access and
990 inside group "foo/bar". You have to have permissions to access and
991 write to the last repository group ("bar" in this example)
991 write to the last repository group ("bar" in this example)
992
992
993 This command can only be run using an |authtoken| with minimum
993 This command can only be run using an |authtoken| with minimum
994 read permissions of the forked repo, create fork permissions for an user.
994 read permissions of the forked repo, create fork permissions for an user.
995
995
996 :param apiuser: This is filled automatically from the |authtoken|.
996 :param apiuser: This is filled automatically from the |authtoken|.
997 :type apiuser: AuthUser
997 :type apiuser: AuthUser
998 :param repoid: Set repository name or repository ID.
998 :param repoid: Set repository name or repository ID.
999 :type repoid: str or int
999 :type repoid: str or int
1000 :param fork_name: Set the fork name, including it's repository group membership.
1000 :param fork_name: Set the fork name, including it's repository group membership.
1001 :type fork_name: str
1001 :type fork_name: str
1002 :param owner: Set the fork owner.
1002 :param owner: Set the fork owner.
1003 :type owner: str
1003 :type owner: str
1004 :param description: Set the fork description.
1004 :param description: Set the fork description.
1005 :type description: str
1005 :type description: str
1006 :param copy_permissions: Copy permissions from parent |repo|. The
1006 :param copy_permissions: Copy permissions from parent |repo|. The
1007 default is False.
1007 default is False.
1008 :type copy_permissions: bool
1008 :type copy_permissions: bool
1009 :param private: Make the fork private. The default is False.
1009 :param private: Make the fork private. The default is False.
1010 :type private: bool
1010 :type private: bool
1011 :param landing_rev: Set the landing revision. The default is tip.
1011 :param landing_rev: Set the landing revision. The default is tip.
1012
1012
1013 Example output:
1013 Example output:
1014
1014
1015 .. code-block:: bash
1015 .. code-block:: bash
1016
1016
1017 id : <id_for_response>
1017 id : <id_for_response>
1018 api_key : "<api_key>"
1018 api_key : "<api_key>"
1019 args: {
1019 args: {
1020 "repoid" : "<reponame or repo_id>",
1020 "repoid" : "<reponame or repo_id>",
1021 "fork_name": "<forkname>",
1021 "fork_name": "<forkname>",
1022 "owner": "<username or user_id = Optional(=apiuser)>",
1022 "owner": "<username or user_id = Optional(=apiuser)>",
1023 "description": "<description>",
1023 "description": "<description>",
1024 "copy_permissions": "<bool>",
1024 "copy_permissions": "<bool>",
1025 "private": "<bool>",
1025 "private": "<bool>",
1026 "landing_rev": "<landing_rev>"
1026 "landing_rev": "<landing_rev>"
1027 }
1027 }
1028
1028
1029 Example error output:
1029 Example error output:
1030
1030
1031 .. code-block:: bash
1031 .. code-block:: bash
1032
1032
1033 id : <id_given_in_input>
1033 id : <id_given_in_input>
1034 result: {
1034 result: {
1035 "msg": "Created fork of `<reponame>` as `<forkname>`",
1035 "msg": "Created fork of `<reponame>` as `<forkname>`",
1036 "success": true,
1036 "success": true,
1037 "task": "<celery task id or None if done sync>"
1037 "task": "<celery task id or None if done sync>"
1038 }
1038 }
1039 error: null
1039 error: null
1040
1040
1041 """
1041 """
1042
1042
1043 repo = get_repo_or_error(repoid)
1043 repo = get_repo_or_error(repoid)
1044 repo_name = repo.repo_name
1044 repo_name = repo.repo_name
1045
1045
1046 if not has_superadmin_permission(apiuser):
1046 if not has_superadmin_permission(apiuser):
1047 # check if we have at least read permission for
1047 # check if we have at least read permission for
1048 # this repo that we fork !
1048 # this repo that we fork !
1049 _perms = (
1049 _perms = (
1050 'repository.admin', 'repository.write', 'repository.read')
1050 'repository.admin', 'repository.write', 'repository.read')
1051 validate_repo_permissions(apiuser, repoid, repo, _perms)
1051 validate_repo_permissions(apiuser, repoid, repo, _perms)
1052
1052
1053 # check if the regular user has at least fork permissions as well
1053 # check if the regular user has at least fork permissions as well
1054 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1054 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1055 raise JSONRPCForbidden()
1055 raise JSONRPCForbidden()
1056
1056
1057 # check if user can set owner parameter
1057 # check if user can set owner parameter
1058 owner = validate_set_owner_permissions(apiuser, owner)
1058 owner = validate_set_owner_permissions(apiuser, owner)
1059
1059
1060 description = Optional.extract(description)
1060 description = Optional.extract(description)
1061 copy_permissions = Optional.extract(copy_permissions)
1061 copy_permissions = Optional.extract(copy_permissions)
1062 clone_uri = Optional.extract(clone_uri)
1062 clone_uri = Optional.extract(clone_uri)
1063 landing_commit_ref = Optional.extract(landing_rev)
1063 landing_commit_ref = Optional.extract(landing_rev)
1064 private = Optional.extract(private)
1064 private = Optional.extract(private)
1065
1065
1066 schema = repo_schema.RepoSchema().bind(
1066 schema = repo_schema.RepoSchema().bind(
1067 repo_type_options=rhodecode.BACKENDS.keys(),
1067 repo_type_options=rhodecode.BACKENDS.keys(),
1068 repo_type=repo.repo_type,
1068 repo_type=repo.repo_type,
1069 # user caller
1069 # user caller
1070 user=apiuser)
1070 user=apiuser)
1071
1071
1072 try:
1072 try:
1073 schema_data = schema.deserialize(dict(
1073 schema_data = schema.deserialize(dict(
1074 repo_name=fork_name,
1074 repo_name=fork_name,
1075 repo_type=repo.repo_type,
1075 repo_type=repo.repo_type,
1076 repo_owner=owner.username,
1076 repo_owner=owner.username,
1077 repo_description=description,
1077 repo_description=description,
1078 repo_landing_commit_ref=landing_commit_ref,
1078 repo_landing_commit_ref=landing_commit_ref,
1079 repo_clone_uri=clone_uri,
1079 repo_clone_uri=clone_uri,
1080 repo_private=private,
1080 repo_private=private,
1081 repo_copy_permissions=copy_permissions))
1081 repo_copy_permissions=copy_permissions))
1082 except validation_schema.Invalid as err:
1082 except validation_schema.Invalid as err:
1083 raise JSONRPCValidationError(colander_exc=err)
1083 raise JSONRPCValidationError(colander_exc=err)
1084
1084
1085 try:
1085 try:
1086 data = {
1086 data = {
1087 'fork_parent_id': repo.repo_id,
1087 'fork_parent_id': repo.repo_id,
1088
1088
1089 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1089 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1090 'repo_name_full': schema_data['repo_name'],
1090 'repo_name_full': schema_data['repo_name'],
1091 'repo_group': schema_data['repo_group']['repo_group_id'],
1091 'repo_group': schema_data['repo_group']['repo_group_id'],
1092 'repo_type': schema_data['repo_type'],
1092 'repo_type': schema_data['repo_type'],
1093 'description': schema_data['repo_description'],
1093 'description': schema_data['repo_description'],
1094 'private': schema_data['repo_private'],
1094 'private': schema_data['repo_private'],
1095 'copy_permissions': schema_data['repo_copy_permissions'],
1095 'copy_permissions': schema_data['repo_copy_permissions'],
1096 'landing_rev': schema_data['repo_landing_commit_ref'],
1096 'landing_rev': schema_data['repo_landing_commit_ref'],
1097 }
1097 }
1098
1098
1099 task = RepoModel().create_fork(data, cur_user=owner.user_id)
1099 task = RepoModel().create_fork(data, cur_user=owner.user_id)
1100 # no commit, it's done in RepoModel, or async via celery
1100 # no commit, it's done in RepoModel, or async via celery
1101 task_id = get_task_id(task)
1101 task_id = get_task_id(task)
1102
1102
1103 return {
1103 return {
1104 'msg': 'Created fork of `%s` as `%s`' % (
1104 'msg': 'Created fork of `%s` as `%s`' % (
1105 repo.repo_name, schema_data['repo_name']),
1105 repo.repo_name, schema_data['repo_name']),
1106 'success': True, # cannot return the repo data here since fork
1106 'success': True, # cannot return the repo data here since fork
1107 # can be done async
1107 # can be done async
1108 'task': task_id
1108 'task': task_id
1109 }
1109 }
1110 except Exception:
1110 except Exception:
1111 log.exception(
1111 log.exception(
1112 u"Exception while trying to create fork %s",
1112 u"Exception while trying to create fork %s",
1113 schema_data['repo_name'])
1113 schema_data['repo_name'])
1114 raise JSONRPCError(
1114 raise JSONRPCError(
1115 'failed to fork repository `%s` as `%s`' % (
1115 'failed to fork repository `%s` as `%s`' % (
1116 repo_name, schema_data['repo_name']))
1116 repo_name, schema_data['repo_name']))
1117
1117
1118
1118
1119 @jsonrpc_method()
1119 @jsonrpc_method()
1120 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1120 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1121 """
1121 """
1122 Deletes a repository.
1122 Deletes a repository.
1123
1123
1124 * When the `forks` parameter is set it's possible to detach or delete
1124 * When the `forks` parameter is set it's possible to detach or delete
1125 forks of deleted repository.
1125 forks of deleted repository.
1126
1126
1127 This command can only be run using an |authtoken| with admin
1127 This command can only be run using an |authtoken| with admin
1128 permissions on the |repo|.
1128 permissions on the |repo|.
1129
1129
1130 :param apiuser: This is filled automatically from the |authtoken|.
1130 :param apiuser: This is filled automatically from the |authtoken|.
1131 :type apiuser: AuthUser
1131 :type apiuser: AuthUser
1132 :param repoid: Set the repository name or repository ID.
1132 :param repoid: Set the repository name or repository ID.
1133 :type repoid: str or int
1133 :type repoid: str or int
1134 :param forks: Set to `detach` or `delete` forks from the |repo|.
1134 :param forks: Set to `detach` or `delete` forks from the |repo|.
1135 :type forks: Optional(str)
1135 :type forks: Optional(str)
1136
1136
1137 Example error output:
1137 Example error output:
1138
1138
1139 .. code-block:: bash
1139 .. code-block:: bash
1140
1140
1141 id : <id_given_in_input>
1141 id : <id_given_in_input>
1142 result: {
1142 result: {
1143 "msg": "Deleted repository `<reponame>`",
1143 "msg": "Deleted repository `<reponame>`",
1144 "success": true
1144 "success": true
1145 }
1145 }
1146 error: null
1146 error: null
1147 """
1147 """
1148
1148
1149 repo = get_repo_or_error(repoid)
1149 repo = get_repo_or_error(repoid)
1150 repo_name = repo.repo_name
1150 repo_name = repo.repo_name
1151 if not has_superadmin_permission(apiuser):
1151 if not has_superadmin_permission(apiuser):
1152 _perms = ('repository.admin',)
1152 _perms = ('repository.admin',)
1153 validate_repo_permissions(apiuser, repoid, repo, _perms)
1153 validate_repo_permissions(apiuser, repoid, repo, _perms)
1154
1154
1155 try:
1155 try:
1156 handle_forks = Optional.extract(forks)
1156 handle_forks = Optional.extract(forks)
1157 _forks_msg = ''
1157 _forks_msg = ''
1158 _forks = [f for f in repo.forks]
1158 _forks = [f for f in repo.forks]
1159 if handle_forks == 'detach':
1159 if handle_forks == 'detach':
1160 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1160 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1161 elif handle_forks == 'delete':
1161 elif handle_forks == 'delete':
1162 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1162 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1163 elif _forks:
1163 elif _forks:
1164 raise JSONRPCError(
1164 raise JSONRPCError(
1165 'Cannot delete `%s` it still contains attached forks' %
1165 'Cannot delete `%s` it still contains attached forks' %
1166 (repo.repo_name,)
1166 (repo.repo_name,)
1167 )
1167 )
1168 old_data = repo.get_api_data()
1168 old_data = repo.get_api_data()
1169 RepoModel().delete(repo, forks=forks)
1169 RepoModel().delete(repo, forks=forks)
1170
1170
1171 repo = audit_logger.RepoWrap(repo_id=None,
1171 repo = audit_logger.RepoWrap(repo_id=None,
1172 repo_name=repo.repo_name)
1172 repo_name=repo.repo_name)
1173
1173
1174 audit_logger.store_api(
1174 audit_logger.store_api(
1175 'repo.delete', action_data={'old_data': old_data},
1175 'repo.delete', action_data={'old_data': old_data},
1176 user=apiuser, repo=repo)
1176 user=apiuser, repo=repo)
1177
1177
1178 ScmModel().mark_for_invalidation(repo_name, delete=True)
1178 ScmModel().mark_for_invalidation(repo_name, delete=True)
1179 Session().commit()
1179 Session().commit()
1180 return {
1180 return {
1181 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg),
1181 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg),
1182 'success': True
1182 'success': True
1183 }
1183 }
1184 except Exception:
1184 except Exception:
1185 log.exception("Exception occurred while trying to delete repo")
1185 log.exception("Exception occurred while trying to delete repo")
1186 raise JSONRPCError(
1186 raise JSONRPCError(
1187 'failed to delete repository `%s`' % (repo_name,)
1187 'failed to delete repository `%s`' % (repo_name,)
1188 )
1188 )
1189
1189
1190
1190
1191 #TODO: marcink, change name ?
1191 #TODO: marcink, change name ?
1192 @jsonrpc_method()
1192 @jsonrpc_method()
1193 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1193 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1194 """
1194 """
1195 Invalidates the cache for the specified repository.
1195 Invalidates the cache for the specified repository.
1196
1196
1197 This command can only be run using an |authtoken| with admin rights to
1197 This command can only be run using an |authtoken| with admin rights to
1198 the specified repository.
1198 the specified repository.
1199
1199
1200 This command takes the following options:
1200 This command takes the following options:
1201
1201
1202 :param apiuser: This is filled automatically from |authtoken|.
1202 :param apiuser: This is filled automatically from |authtoken|.
1203 :type apiuser: AuthUser
1203 :type apiuser: AuthUser
1204 :param repoid: Sets the repository name or repository ID.
1204 :param repoid: Sets the repository name or repository ID.
1205 :type repoid: str or int
1205 :type repoid: str or int
1206 :param delete_keys: This deletes the invalidated keys instead of
1206 :param delete_keys: This deletes the invalidated keys instead of
1207 just flagging them.
1207 just flagging them.
1208 :type delete_keys: Optional(``True`` | ``False``)
1208 :type delete_keys: Optional(``True`` | ``False``)
1209
1209
1210 Example output:
1210 Example output:
1211
1211
1212 .. code-block:: bash
1212 .. code-block:: bash
1213
1213
1214 id : <id_given_in_input>
1214 id : <id_given_in_input>
1215 result : {
1215 result : {
1216 'msg': Cache for repository `<repository name>` was invalidated,
1216 'msg': Cache for repository `<repository name>` was invalidated,
1217 'repository': <repository name>
1217 'repository': <repository name>
1218 }
1218 }
1219 error : null
1219 error : null
1220
1220
1221 Example error output:
1221 Example error output:
1222
1222
1223 .. code-block:: bash
1223 .. code-block:: bash
1224
1224
1225 id : <id_given_in_input>
1225 id : <id_given_in_input>
1226 result : null
1226 result : null
1227 error : {
1227 error : {
1228 'Error occurred during cache invalidation action'
1228 'Error occurred during cache invalidation action'
1229 }
1229 }
1230
1230
1231 """
1231 """
1232
1232
1233 repo = get_repo_or_error(repoid)
1233 repo = get_repo_or_error(repoid)
1234 if not has_superadmin_permission(apiuser):
1234 if not has_superadmin_permission(apiuser):
1235 _perms = ('repository.admin', 'repository.write',)
1235 _perms = ('repository.admin', 'repository.write',)
1236 validate_repo_permissions(apiuser, repoid, repo, _perms)
1236 validate_repo_permissions(apiuser, repoid, repo, _perms)
1237
1237
1238 delete = Optional.extract(delete_keys)
1238 delete = Optional.extract(delete_keys)
1239 try:
1239 try:
1240 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1240 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1241 return {
1241 return {
1242 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1242 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1243 'repository': repo.repo_name
1243 'repository': repo.repo_name
1244 }
1244 }
1245 except Exception:
1245 except Exception:
1246 log.exception(
1246 log.exception(
1247 "Exception occurred while trying to invalidate repo cache")
1247 "Exception occurred while trying to invalidate repo cache")
1248 raise JSONRPCError(
1248 raise JSONRPCError(
1249 'Error occurred during cache invalidation action'
1249 'Error occurred during cache invalidation action'
1250 )
1250 )
1251
1251
1252
1252
1253 #TODO: marcink, change name ?
1253 #TODO: marcink, change name ?
1254 @jsonrpc_method()
1254 @jsonrpc_method()
1255 def lock(request, apiuser, repoid, locked=Optional(None),
1255 def lock(request, apiuser, repoid, locked=Optional(None),
1256 userid=Optional(OAttr('apiuser'))):
1256 userid=Optional(OAttr('apiuser'))):
1257 """
1257 """
1258 Sets the lock state of the specified |repo| by the given user.
1258 Sets the lock state of the specified |repo| by the given user.
1259 From more information, see :ref:`repo-locking`.
1259 From more information, see :ref:`repo-locking`.
1260
1260
1261 * If the ``userid`` option is not set, the repository is locked to the
1261 * If the ``userid`` option is not set, the repository is locked to the
1262 user who called the method.
1262 user who called the method.
1263 * If the ``locked`` parameter is not set, the current lock state of the
1263 * If the ``locked`` parameter is not set, the current lock state of the
1264 repository is displayed.
1264 repository is displayed.
1265
1265
1266 This command can only be run using an |authtoken| with admin rights to
1266 This command can only be run using an |authtoken| with admin rights to
1267 the specified repository.
1267 the specified repository.
1268
1268
1269 This command takes the following options:
1269 This command takes the following options:
1270
1270
1271 :param apiuser: This is filled automatically from the |authtoken|.
1271 :param apiuser: This is filled automatically from the |authtoken|.
1272 :type apiuser: AuthUser
1272 :type apiuser: AuthUser
1273 :param repoid: Sets the repository name or repository ID.
1273 :param repoid: Sets the repository name or repository ID.
1274 :type repoid: str or int
1274 :type repoid: str or int
1275 :param locked: Sets the lock state.
1275 :param locked: Sets the lock state.
1276 :type locked: Optional(``True`` | ``False``)
1276 :type locked: Optional(``True`` | ``False``)
1277 :param userid: Set the repository lock to this user.
1277 :param userid: Set the repository lock to this user.
1278 :type userid: Optional(str or int)
1278 :type userid: Optional(str or int)
1279
1279
1280 Example error output:
1280 Example error output:
1281
1281
1282 .. code-block:: bash
1282 .. code-block:: bash
1283
1283
1284 id : <id_given_in_input>
1284 id : <id_given_in_input>
1285 result : {
1285 result : {
1286 'repo': '<reponame>',
1286 'repo': '<reponame>',
1287 'locked': <bool: lock state>,
1287 'locked': <bool: lock state>,
1288 'locked_since': <int: lock timestamp>,
1288 'locked_since': <int: lock timestamp>,
1289 'locked_by': <username of person who made the lock>,
1289 'locked_by': <username of person who made the lock>,
1290 'lock_reason': <str: reason for locking>,
1290 'lock_reason': <str: reason for locking>,
1291 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1291 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1292 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1292 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1293 or
1293 or
1294 'msg': 'Repo `<repository name>` not locked.'
1294 'msg': 'Repo `<repository name>` not locked.'
1295 or
1295 or
1296 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1296 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1297 }
1297 }
1298 error : null
1298 error : null
1299
1299
1300 Example error output:
1300 Example error output:
1301
1301
1302 .. code-block:: bash
1302 .. code-block:: bash
1303
1303
1304 id : <id_given_in_input>
1304 id : <id_given_in_input>
1305 result : null
1305 result : null
1306 error : {
1306 error : {
1307 'Error occurred locking repository `<reponame>`'
1307 'Error occurred locking repository `<reponame>`'
1308 }
1308 }
1309 """
1309 """
1310
1310
1311 repo = get_repo_or_error(repoid)
1311 repo = get_repo_or_error(repoid)
1312 if not has_superadmin_permission(apiuser):
1312 if not has_superadmin_permission(apiuser):
1313 # check if we have at least write permission for this repo !
1313 # check if we have at least write permission for this repo !
1314 _perms = ('repository.admin', 'repository.write',)
1314 _perms = ('repository.admin', 'repository.write',)
1315 validate_repo_permissions(apiuser, repoid, repo, _perms)
1315 validate_repo_permissions(apiuser, repoid, repo, _perms)
1316
1316
1317 # make sure normal user does not pass someone else userid,
1317 # make sure normal user does not pass someone else userid,
1318 # he is not allowed to do that
1318 # he is not allowed to do that
1319 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1319 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1320 raise JSONRPCError('userid is not the same as your user')
1320 raise JSONRPCError('userid is not the same as your user')
1321
1321
1322 if isinstance(userid, Optional):
1322 if isinstance(userid, Optional):
1323 userid = apiuser.user_id
1323 userid = apiuser.user_id
1324
1324
1325 user = get_user_or_error(userid)
1325 user = get_user_or_error(userid)
1326
1326
1327 if isinstance(locked, Optional):
1327 if isinstance(locked, Optional):
1328 lockobj = repo.locked
1328 lockobj = repo.locked
1329
1329
1330 if lockobj[0] is None:
1330 if lockobj[0] is None:
1331 _d = {
1331 _d = {
1332 'repo': repo.repo_name,
1332 'repo': repo.repo_name,
1333 'locked': False,
1333 'locked': False,
1334 'locked_since': None,
1334 'locked_since': None,
1335 'locked_by': None,
1335 'locked_by': None,
1336 'lock_reason': None,
1336 'lock_reason': None,
1337 'lock_state_changed': False,
1337 'lock_state_changed': False,
1338 'msg': 'Repo `%s` not locked.' % repo.repo_name
1338 'msg': 'Repo `%s` not locked.' % repo.repo_name
1339 }
1339 }
1340 return _d
1340 return _d
1341 else:
1341 else:
1342 _user_id, _time, _reason = lockobj
1342 _user_id, _time, _reason = lockobj
1343 lock_user = get_user_or_error(userid)
1343 lock_user = get_user_or_error(userid)
1344 _d = {
1344 _d = {
1345 'repo': repo.repo_name,
1345 'repo': repo.repo_name,
1346 'locked': True,
1346 'locked': True,
1347 'locked_since': _time,
1347 'locked_since': _time,
1348 'locked_by': lock_user.username,
1348 'locked_by': lock_user.username,
1349 'lock_reason': _reason,
1349 'lock_reason': _reason,
1350 'lock_state_changed': False,
1350 'lock_state_changed': False,
1351 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1351 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1352 % (repo.repo_name, lock_user.username,
1352 % (repo.repo_name, lock_user.username,
1353 json.dumps(time_to_datetime(_time))))
1353 json.dumps(time_to_datetime(_time))))
1354 }
1354 }
1355 return _d
1355 return _d
1356
1356
1357 # force locked state through a flag
1357 # force locked state through a flag
1358 else:
1358 else:
1359 locked = str2bool(locked)
1359 locked = str2bool(locked)
1360 lock_reason = Repository.LOCK_API
1360 lock_reason = Repository.LOCK_API
1361 try:
1361 try:
1362 if locked:
1362 if locked:
1363 lock_time = time.time()
1363 lock_time = time.time()
1364 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1364 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1365 else:
1365 else:
1366 lock_time = None
1366 lock_time = None
1367 Repository.unlock(repo)
1367 Repository.unlock(repo)
1368 _d = {
1368 _d = {
1369 'repo': repo.repo_name,
1369 'repo': repo.repo_name,
1370 'locked': locked,
1370 'locked': locked,
1371 'locked_since': lock_time,
1371 'locked_since': lock_time,
1372 'locked_by': user.username,
1372 'locked_by': user.username,
1373 'lock_reason': lock_reason,
1373 'lock_reason': lock_reason,
1374 'lock_state_changed': True,
1374 'lock_state_changed': True,
1375 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1375 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1376 % (user.username, repo.repo_name, locked))
1376 % (user.username, repo.repo_name, locked))
1377 }
1377 }
1378 return _d
1378 return _d
1379 except Exception:
1379 except Exception:
1380 log.exception(
1380 log.exception(
1381 "Exception occurred while trying to lock repository")
1381 "Exception occurred while trying to lock repository")
1382 raise JSONRPCError(
1382 raise JSONRPCError(
1383 'Error occurred locking repository `%s`' % repo.repo_name
1383 'Error occurred locking repository `%s`' % repo.repo_name
1384 )
1384 )
1385
1385
1386
1386
1387 @jsonrpc_method()
1387 @jsonrpc_method()
1388 def comment_commit(
1388 def comment_commit(
1389 request, apiuser, repoid, commit_id, message, status=Optional(None),
1389 request, apiuser, repoid, commit_id, message, status=Optional(None),
1390 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1390 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1391 resolves_comment_id=Optional(None),
1391 resolves_comment_id=Optional(None),
1392 userid=Optional(OAttr('apiuser'))):
1392 userid=Optional(OAttr('apiuser'))):
1393 """
1393 """
1394 Set a commit comment, and optionally change the status of the commit.
1394 Set a commit comment, and optionally change the status of the commit.
1395
1395
1396 :param apiuser: This is filled automatically from the |authtoken|.
1396 :param apiuser: This is filled automatically from the |authtoken|.
1397 :type apiuser: AuthUser
1397 :type apiuser: AuthUser
1398 :param repoid: Set the repository name or repository ID.
1398 :param repoid: Set the repository name or repository ID.
1399 :type repoid: str or int
1399 :type repoid: str or int
1400 :param commit_id: Specify the commit_id for which to set a comment.
1400 :param commit_id: Specify the commit_id for which to set a comment.
1401 :type commit_id: str
1401 :type commit_id: str
1402 :param message: The comment text.
1402 :param message: The comment text.
1403 :type message: str
1403 :type message: str
1404 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1404 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1405 'approved', 'rejected', 'under_review'
1405 'approved', 'rejected', 'under_review'
1406 :type status: str
1406 :type status: str
1407 :param comment_type: Comment type, one of: 'note', 'todo'
1407 :param comment_type: Comment type, one of: 'note', 'todo'
1408 :type comment_type: Optional(str), default: 'note'
1408 :type comment_type: Optional(str), default: 'note'
1409 :param userid: Set the user name of the comment creator.
1409 :param userid: Set the user name of the comment creator.
1410 :type userid: Optional(str or int)
1410 :type userid: Optional(str or int)
1411
1411
1412 Example error output:
1412 Example error output:
1413
1413
1414 .. code-block:: bash
1414 .. code-block:: bash
1415
1415
1416 {
1416 {
1417 "id" : <id_given_in_input>,
1417 "id" : <id_given_in_input>,
1418 "result" : {
1418 "result" : {
1419 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1419 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1420 "status_change": null or <status>,
1420 "status_change": null or <status>,
1421 "success": true
1421 "success": true
1422 },
1422 },
1423 "error" : null
1423 "error" : null
1424 }
1424 }
1425
1425
1426 """
1426 """
1427 repo = get_repo_or_error(repoid)
1427 repo = get_repo_or_error(repoid)
1428 if not has_superadmin_permission(apiuser):
1428 if not has_superadmin_permission(apiuser):
1429 _perms = ('repository.read', 'repository.write', 'repository.admin')
1429 _perms = ('repository.read', 'repository.write', 'repository.admin')
1430 validate_repo_permissions(apiuser, repoid, repo, _perms)
1430 validate_repo_permissions(apiuser, repoid, repo, _perms)
1431
1431
1432 try:
1432 try:
1433 commit_id = repo.scm_instance().get_commit(commit_id=commit_id).raw_id
1433 commit_id = repo.scm_instance().get_commit(commit_id=commit_id).raw_id
1434 except Exception as e:
1434 except Exception as e:
1435 log.exception('Failed to fetch commit')
1435 log.exception('Failed to fetch commit')
1436 raise JSONRPCError(e.message)
1436 raise JSONRPCError(safe_str(e))
1437
1437
1438 if isinstance(userid, Optional):
1438 if isinstance(userid, Optional):
1439 userid = apiuser.user_id
1439 userid = apiuser.user_id
1440
1440
1441 user = get_user_or_error(userid)
1441 user = get_user_or_error(userid)
1442 status = Optional.extract(status)
1442 status = Optional.extract(status)
1443 comment_type = Optional.extract(comment_type)
1443 comment_type = Optional.extract(comment_type)
1444 resolves_comment_id = Optional.extract(resolves_comment_id)
1444 resolves_comment_id = Optional.extract(resolves_comment_id)
1445
1445
1446 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1446 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1447 if status and status not in allowed_statuses:
1447 if status and status not in allowed_statuses:
1448 raise JSONRPCError('Bad status, must be on '
1448 raise JSONRPCError('Bad status, must be on '
1449 'of %s got %s' % (allowed_statuses, status,))
1449 'of %s got %s' % (allowed_statuses, status,))
1450
1450
1451 if resolves_comment_id:
1451 if resolves_comment_id:
1452 comment = ChangesetComment.get(resolves_comment_id)
1452 comment = ChangesetComment.get(resolves_comment_id)
1453 if not comment:
1453 if not comment:
1454 raise JSONRPCError(
1454 raise JSONRPCError(
1455 'Invalid resolves_comment_id `%s` for this commit.'
1455 'Invalid resolves_comment_id `%s` for this commit.'
1456 % resolves_comment_id)
1456 % resolves_comment_id)
1457 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1457 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1458 raise JSONRPCError(
1458 raise JSONRPCError(
1459 'Comment `%s` is wrong type for setting status to resolved.'
1459 'Comment `%s` is wrong type for setting status to resolved.'
1460 % resolves_comment_id)
1460 % resolves_comment_id)
1461
1461
1462 try:
1462 try:
1463 rc_config = SettingsModel().get_all_settings()
1463 rc_config = SettingsModel().get_all_settings()
1464 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1464 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1465 status_change_label = ChangesetStatus.get_status_lbl(status)
1465 status_change_label = ChangesetStatus.get_status_lbl(status)
1466 comment = CommentsModel().create(
1466 comment = CommentsModel().create(
1467 message, repo, user, commit_id=commit_id,
1467 message, repo, user, commit_id=commit_id,
1468 status_change=status_change_label,
1468 status_change=status_change_label,
1469 status_change_type=status,
1469 status_change_type=status,
1470 renderer=renderer,
1470 renderer=renderer,
1471 comment_type=comment_type,
1471 comment_type=comment_type,
1472 resolves_comment_id=resolves_comment_id,
1472 resolves_comment_id=resolves_comment_id,
1473 auth_user=apiuser
1473 auth_user=apiuser
1474 )
1474 )
1475 if status:
1475 if status:
1476 # also do a status change
1476 # also do a status change
1477 try:
1477 try:
1478 ChangesetStatusModel().set_status(
1478 ChangesetStatusModel().set_status(
1479 repo, status, user, comment, revision=commit_id,
1479 repo, status, user, comment, revision=commit_id,
1480 dont_allow_on_closed_pull_request=True
1480 dont_allow_on_closed_pull_request=True
1481 )
1481 )
1482 except StatusChangeOnClosedPullRequestError:
1482 except StatusChangeOnClosedPullRequestError:
1483 log.exception(
1483 log.exception(
1484 "Exception occurred while trying to change repo commit status")
1484 "Exception occurred while trying to change repo commit status")
1485 msg = ('Changing status on a changeset associated with '
1485 msg = ('Changing status on a changeset associated with '
1486 'a closed pull request is not allowed')
1486 'a closed pull request is not allowed')
1487 raise JSONRPCError(msg)
1487 raise JSONRPCError(msg)
1488
1488
1489 Session().commit()
1489 Session().commit()
1490 return {
1490 return {
1491 'msg': (
1491 'msg': (
1492 'Commented on commit `%s` for repository `%s`' % (
1492 'Commented on commit `%s` for repository `%s`' % (
1493 comment.revision, repo.repo_name)),
1493 comment.revision, repo.repo_name)),
1494 'status_change': status,
1494 'status_change': status,
1495 'success': True,
1495 'success': True,
1496 }
1496 }
1497 except JSONRPCError:
1497 except JSONRPCError:
1498 # catch any inside errors, and re-raise them to prevent from
1498 # catch any inside errors, and re-raise them to prevent from
1499 # below global catch to silence them
1499 # below global catch to silence them
1500 raise
1500 raise
1501 except Exception:
1501 except Exception:
1502 log.exception("Exception occurred while trying to comment on commit")
1502 log.exception("Exception occurred while trying to comment on commit")
1503 raise JSONRPCError(
1503 raise JSONRPCError(
1504 'failed to set comment on repository `%s`' % (repo.repo_name,)
1504 'failed to set comment on repository `%s`' % (repo.repo_name,)
1505 )
1505 )
1506
1506
1507
1507
1508 @jsonrpc_method()
1508 @jsonrpc_method()
1509 def grant_user_permission(request, apiuser, repoid, userid, perm):
1509 def grant_user_permission(request, apiuser, repoid, userid, perm):
1510 """
1510 """
1511 Grant permissions for the specified user on the given repository,
1511 Grant permissions for the specified user on the given repository,
1512 or update existing permissions if found.
1512 or update existing permissions if found.
1513
1513
1514 This command can only be run using an |authtoken| with admin
1514 This command can only be run using an |authtoken| with admin
1515 permissions on the |repo|.
1515 permissions on the |repo|.
1516
1516
1517 :param apiuser: This is filled automatically from the |authtoken|.
1517 :param apiuser: This is filled automatically from the |authtoken|.
1518 :type apiuser: AuthUser
1518 :type apiuser: AuthUser
1519 :param repoid: Set the repository name or repository ID.
1519 :param repoid: Set the repository name or repository ID.
1520 :type repoid: str or int
1520 :type repoid: str or int
1521 :param userid: Set the user name.
1521 :param userid: Set the user name.
1522 :type userid: str
1522 :type userid: str
1523 :param perm: Set the user permissions, using the following format
1523 :param perm: Set the user permissions, using the following format
1524 ``(repository.(none|read|write|admin))``
1524 ``(repository.(none|read|write|admin))``
1525 :type perm: str
1525 :type perm: str
1526
1526
1527 Example output:
1527 Example output:
1528
1528
1529 .. code-block:: bash
1529 .. code-block:: bash
1530
1530
1531 id : <id_given_in_input>
1531 id : <id_given_in_input>
1532 result: {
1532 result: {
1533 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1533 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1534 "success": true
1534 "success": true
1535 }
1535 }
1536 error: null
1536 error: null
1537 """
1537 """
1538
1538
1539 repo = get_repo_or_error(repoid)
1539 repo = get_repo_or_error(repoid)
1540 user = get_user_or_error(userid)
1540 user = get_user_or_error(userid)
1541 perm = get_perm_or_error(perm)
1541 perm = get_perm_or_error(perm)
1542 if not has_superadmin_permission(apiuser):
1542 if not has_superadmin_permission(apiuser):
1543 _perms = ('repository.admin',)
1543 _perms = ('repository.admin',)
1544 validate_repo_permissions(apiuser, repoid, repo, _perms)
1544 validate_repo_permissions(apiuser, repoid, repo, _perms)
1545
1545
1546 try:
1546 try:
1547
1547
1548 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
1548 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
1549
1549
1550 Session().commit()
1550 Session().commit()
1551 return {
1551 return {
1552 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1552 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1553 perm.permission_name, user.username, repo.repo_name
1553 perm.permission_name, user.username, repo.repo_name
1554 ),
1554 ),
1555 'success': True
1555 'success': True
1556 }
1556 }
1557 except Exception:
1557 except Exception:
1558 log.exception(
1558 log.exception(
1559 "Exception occurred while trying edit permissions for repo")
1559 "Exception occurred while trying edit permissions for repo")
1560 raise JSONRPCError(
1560 raise JSONRPCError(
1561 'failed to edit permission for user: `%s` in repo: `%s`' % (
1561 'failed to edit permission for user: `%s` in repo: `%s`' % (
1562 userid, repoid
1562 userid, repoid
1563 )
1563 )
1564 )
1564 )
1565
1565
1566
1566
1567 @jsonrpc_method()
1567 @jsonrpc_method()
1568 def revoke_user_permission(request, apiuser, repoid, userid):
1568 def revoke_user_permission(request, apiuser, repoid, userid):
1569 """
1569 """
1570 Revoke permission for a user on the specified repository.
1570 Revoke permission for a user on the specified repository.
1571
1571
1572 This command can only be run using an |authtoken| with admin
1572 This command can only be run using an |authtoken| with admin
1573 permissions on the |repo|.
1573 permissions on the |repo|.
1574
1574
1575 :param apiuser: This is filled automatically from the |authtoken|.
1575 :param apiuser: This is filled automatically from the |authtoken|.
1576 :type apiuser: AuthUser
1576 :type apiuser: AuthUser
1577 :param repoid: Set the repository name or repository ID.
1577 :param repoid: Set the repository name or repository ID.
1578 :type repoid: str or int
1578 :type repoid: str or int
1579 :param userid: Set the user name of revoked user.
1579 :param userid: Set the user name of revoked user.
1580 :type userid: str or int
1580 :type userid: str or int
1581
1581
1582 Example error output:
1582 Example error output:
1583
1583
1584 .. code-block:: bash
1584 .. code-block:: bash
1585
1585
1586 id : <id_given_in_input>
1586 id : <id_given_in_input>
1587 result: {
1587 result: {
1588 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1588 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1589 "success": true
1589 "success": true
1590 }
1590 }
1591 error: null
1591 error: null
1592 """
1592 """
1593
1593
1594 repo = get_repo_or_error(repoid)
1594 repo = get_repo_or_error(repoid)
1595 user = get_user_or_error(userid)
1595 user = get_user_or_error(userid)
1596 if not has_superadmin_permission(apiuser):
1596 if not has_superadmin_permission(apiuser):
1597 _perms = ('repository.admin',)
1597 _perms = ('repository.admin',)
1598 validate_repo_permissions(apiuser, repoid, repo, _perms)
1598 validate_repo_permissions(apiuser, repoid, repo, _perms)
1599
1599
1600 try:
1600 try:
1601 RepoModel().revoke_user_permission(repo=repo, user=user)
1601 RepoModel().revoke_user_permission(repo=repo, user=user)
1602 Session().commit()
1602 Session().commit()
1603 return {
1603 return {
1604 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1604 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1605 user.username, repo.repo_name
1605 user.username, repo.repo_name
1606 ),
1606 ),
1607 'success': True
1607 'success': True
1608 }
1608 }
1609 except Exception:
1609 except Exception:
1610 log.exception(
1610 log.exception(
1611 "Exception occurred while trying revoke permissions to repo")
1611 "Exception occurred while trying revoke permissions to repo")
1612 raise JSONRPCError(
1612 raise JSONRPCError(
1613 'failed to edit permission for user: `%s` in repo: `%s`' % (
1613 'failed to edit permission for user: `%s` in repo: `%s`' % (
1614 userid, repoid
1614 userid, repoid
1615 )
1615 )
1616 )
1616 )
1617
1617
1618
1618
1619 @jsonrpc_method()
1619 @jsonrpc_method()
1620 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1620 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1621 """
1621 """
1622 Grant permission for a user group on the specified repository,
1622 Grant permission for a user group on the specified repository,
1623 or update existing permissions.
1623 or update existing permissions.
1624
1624
1625 This command can only be run using an |authtoken| with admin
1625 This command can only be run using an |authtoken| with admin
1626 permissions on the |repo|.
1626 permissions on the |repo|.
1627
1627
1628 :param apiuser: This is filled automatically from the |authtoken|.
1628 :param apiuser: This is filled automatically from the |authtoken|.
1629 :type apiuser: AuthUser
1629 :type apiuser: AuthUser
1630 :param repoid: Set the repository name or repository ID.
1630 :param repoid: Set the repository name or repository ID.
1631 :type repoid: str or int
1631 :type repoid: str or int
1632 :param usergroupid: Specify the ID of the user group.
1632 :param usergroupid: Specify the ID of the user group.
1633 :type usergroupid: str or int
1633 :type usergroupid: str or int
1634 :param perm: Set the user group permissions using the following
1634 :param perm: Set the user group permissions using the following
1635 format: (repository.(none|read|write|admin))
1635 format: (repository.(none|read|write|admin))
1636 :type perm: str
1636 :type perm: str
1637
1637
1638 Example output:
1638 Example output:
1639
1639
1640 .. code-block:: bash
1640 .. code-block:: bash
1641
1641
1642 id : <id_given_in_input>
1642 id : <id_given_in_input>
1643 result : {
1643 result : {
1644 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1644 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1645 "success": true
1645 "success": true
1646
1646
1647 }
1647 }
1648 error : null
1648 error : null
1649
1649
1650 Example error output:
1650 Example error output:
1651
1651
1652 .. code-block:: bash
1652 .. code-block:: bash
1653
1653
1654 id : <id_given_in_input>
1654 id : <id_given_in_input>
1655 result : null
1655 result : null
1656 error : {
1656 error : {
1657 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1657 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1658 }
1658 }
1659
1659
1660 """
1660 """
1661
1661
1662 repo = get_repo_or_error(repoid)
1662 repo = get_repo_or_error(repoid)
1663 perm = get_perm_or_error(perm)
1663 perm = get_perm_or_error(perm)
1664 if not has_superadmin_permission(apiuser):
1664 if not has_superadmin_permission(apiuser):
1665 _perms = ('repository.admin',)
1665 _perms = ('repository.admin',)
1666 validate_repo_permissions(apiuser, repoid, repo, _perms)
1666 validate_repo_permissions(apiuser, repoid, repo, _perms)
1667
1667
1668 user_group = get_user_group_or_error(usergroupid)
1668 user_group = get_user_group_or_error(usergroupid)
1669 if not has_superadmin_permission(apiuser):
1669 if not has_superadmin_permission(apiuser):
1670 # check if we have at least read permission for this user group !
1670 # check if we have at least read permission for this user group !
1671 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1671 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1672 if not HasUserGroupPermissionAnyApi(*_perms)(
1672 if not HasUserGroupPermissionAnyApi(*_perms)(
1673 user=apiuser, user_group_name=user_group.users_group_name):
1673 user=apiuser, user_group_name=user_group.users_group_name):
1674 raise JSONRPCError(
1674 raise JSONRPCError(
1675 'user group `%s` does not exist' % (usergroupid,))
1675 'user group `%s` does not exist' % (usergroupid,))
1676
1676
1677 try:
1677 try:
1678 RepoModel().grant_user_group_permission(
1678 RepoModel().grant_user_group_permission(
1679 repo=repo, group_name=user_group, perm=perm)
1679 repo=repo, group_name=user_group, perm=perm)
1680
1680
1681 Session().commit()
1681 Session().commit()
1682 return {
1682 return {
1683 'msg': 'Granted perm: `%s` for user group: `%s` in '
1683 'msg': 'Granted perm: `%s` for user group: `%s` in '
1684 'repo: `%s`' % (
1684 'repo: `%s`' % (
1685 perm.permission_name, user_group.users_group_name,
1685 perm.permission_name, user_group.users_group_name,
1686 repo.repo_name
1686 repo.repo_name
1687 ),
1687 ),
1688 'success': True
1688 'success': True
1689 }
1689 }
1690 except Exception:
1690 except Exception:
1691 log.exception(
1691 log.exception(
1692 "Exception occurred while trying change permission on repo")
1692 "Exception occurred while trying change permission on repo")
1693 raise JSONRPCError(
1693 raise JSONRPCError(
1694 'failed to edit permission for user group: `%s` in '
1694 'failed to edit permission for user group: `%s` in '
1695 'repo: `%s`' % (
1695 'repo: `%s`' % (
1696 usergroupid, repo.repo_name
1696 usergroupid, repo.repo_name
1697 )
1697 )
1698 )
1698 )
1699
1699
1700
1700
1701 @jsonrpc_method()
1701 @jsonrpc_method()
1702 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1702 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1703 """
1703 """
1704 Revoke the permissions of a user group on a given repository.
1704 Revoke the permissions of a user group on a given repository.
1705
1705
1706 This command can only be run using an |authtoken| with admin
1706 This command can only be run using an |authtoken| with admin
1707 permissions on the |repo|.
1707 permissions on the |repo|.
1708
1708
1709 :param apiuser: This is filled automatically from the |authtoken|.
1709 :param apiuser: This is filled automatically from the |authtoken|.
1710 :type apiuser: AuthUser
1710 :type apiuser: AuthUser
1711 :param repoid: Set the repository name or repository ID.
1711 :param repoid: Set the repository name or repository ID.
1712 :type repoid: str or int
1712 :type repoid: str or int
1713 :param usergroupid: Specify the user group ID.
1713 :param usergroupid: Specify the user group ID.
1714 :type usergroupid: str or int
1714 :type usergroupid: str or int
1715
1715
1716 Example output:
1716 Example output:
1717
1717
1718 .. code-block:: bash
1718 .. code-block:: bash
1719
1719
1720 id : <id_given_in_input>
1720 id : <id_given_in_input>
1721 result: {
1721 result: {
1722 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1722 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1723 "success": true
1723 "success": true
1724 }
1724 }
1725 error: null
1725 error: null
1726 """
1726 """
1727
1727
1728 repo = get_repo_or_error(repoid)
1728 repo = get_repo_or_error(repoid)
1729 if not has_superadmin_permission(apiuser):
1729 if not has_superadmin_permission(apiuser):
1730 _perms = ('repository.admin',)
1730 _perms = ('repository.admin',)
1731 validate_repo_permissions(apiuser, repoid, repo, _perms)
1731 validate_repo_permissions(apiuser, repoid, repo, _perms)
1732
1732
1733 user_group = get_user_group_or_error(usergroupid)
1733 user_group = get_user_group_or_error(usergroupid)
1734 if not has_superadmin_permission(apiuser):
1734 if not has_superadmin_permission(apiuser):
1735 # check if we have at least read permission for this user group !
1735 # check if we have at least read permission for this user group !
1736 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1736 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1737 if not HasUserGroupPermissionAnyApi(*_perms)(
1737 if not HasUserGroupPermissionAnyApi(*_perms)(
1738 user=apiuser, user_group_name=user_group.users_group_name):
1738 user=apiuser, user_group_name=user_group.users_group_name):
1739 raise JSONRPCError(
1739 raise JSONRPCError(
1740 'user group `%s` does not exist' % (usergroupid,))
1740 'user group `%s` does not exist' % (usergroupid,))
1741
1741
1742 try:
1742 try:
1743 RepoModel().revoke_user_group_permission(
1743 RepoModel().revoke_user_group_permission(
1744 repo=repo, group_name=user_group)
1744 repo=repo, group_name=user_group)
1745
1745
1746 Session().commit()
1746 Session().commit()
1747 return {
1747 return {
1748 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1748 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1749 user_group.users_group_name, repo.repo_name
1749 user_group.users_group_name, repo.repo_name
1750 ),
1750 ),
1751 'success': True
1751 'success': True
1752 }
1752 }
1753 except Exception:
1753 except Exception:
1754 log.exception("Exception occurred while trying revoke "
1754 log.exception("Exception occurred while trying revoke "
1755 "user group permission on repo")
1755 "user group permission on repo")
1756 raise JSONRPCError(
1756 raise JSONRPCError(
1757 'failed to edit permission for user group: `%s` in '
1757 'failed to edit permission for user group: `%s` in '
1758 'repo: `%s`' % (
1758 'repo: `%s`' % (
1759 user_group.users_group_name, repo.repo_name
1759 user_group.users_group_name, repo.repo_name
1760 )
1760 )
1761 )
1761 )
1762
1762
1763
1763
1764 @jsonrpc_method()
1764 @jsonrpc_method()
1765 def pull(request, apiuser, repoid, remote_uri=Optional(None)):
1765 def pull(request, apiuser, repoid, remote_uri=Optional(None)):
1766 """
1766 """
1767 Triggers a pull on the given repository from a remote location. You
1767 Triggers a pull on the given repository from a remote location. You
1768 can use this to keep remote repositories up-to-date.
1768 can use this to keep remote repositories up-to-date.
1769
1769
1770 This command can only be run using an |authtoken| with admin
1770 This command can only be run using an |authtoken| with admin
1771 rights to the specified repository. For more information,
1771 rights to the specified repository. For more information,
1772 see :ref:`config-token-ref`.
1772 see :ref:`config-token-ref`.
1773
1773
1774 This command takes the following options:
1774 This command takes the following options:
1775
1775
1776 :param apiuser: This is filled automatically from the |authtoken|.
1776 :param apiuser: This is filled automatically from the |authtoken|.
1777 :type apiuser: AuthUser
1777 :type apiuser: AuthUser
1778 :param repoid: The repository name or repository ID.
1778 :param repoid: The repository name or repository ID.
1779 :type repoid: str or int
1779 :type repoid: str or int
1780 :param remote_uri: Optional remote URI to pass in for pull
1780 :param remote_uri: Optional remote URI to pass in for pull
1781 :type remote_uri: str
1781 :type remote_uri: str
1782
1782
1783 Example output:
1783 Example output:
1784
1784
1785 .. code-block:: bash
1785 .. code-block:: bash
1786
1786
1787 id : <id_given_in_input>
1787 id : <id_given_in_input>
1788 result : {
1788 result : {
1789 "msg": "Pulled from url `<remote_url>` on repo `<repository name>`"
1789 "msg": "Pulled from url `<remote_url>` on repo `<repository name>`"
1790 "repository": "<repository name>"
1790 "repository": "<repository name>"
1791 }
1791 }
1792 error : null
1792 error : null
1793
1793
1794 Example error output:
1794 Example error output:
1795
1795
1796 .. code-block:: bash
1796 .. code-block:: bash
1797
1797
1798 id : <id_given_in_input>
1798 id : <id_given_in_input>
1799 result : null
1799 result : null
1800 error : {
1800 error : {
1801 "Unable to push changes from `<remote_url>`"
1801 "Unable to push changes from `<remote_url>`"
1802 }
1802 }
1803
1803
1804 """
1804 """
1805
1805
1806 repo = get_repo_or_error(repoid)
1806 repo = get_repo_or_error(repoid)
1807 remote_uri = Optional.extract(remote_uri)
1807 remote_uri = Optional.extract(remote_uri)
1808 remote_uri_display = remote_uri or repo.clone_uri_hidden
1808 remote_uri_display = remote_uri or repo.clone_uri_hidden
1809 if not has_superadmin_permission(apiuser):
1809 if not has_superadmin_permission(apiuser):
1810 _perms = ('repository.admin',)
1810 _perms = ('repository.admin',)
1811 validate_repo_permissions(apiuser, repoid, repo, _perms)
1811 validate_repo_permissions(apiuser, repoid, repo, _perms)
1812
1812
1813 try:
1813 try:
1814 ScmModel().pull_changes(
1814 ScmModel().pull_changes(
1815 repo.repo_name, apiuser.username, remote_uri=remote_uri)
1815 repo.repo_name, apiuser.username, remote_uri=remote_uri)
1816 return {
1816 return {
1817 'msg': 'Pulled from url `%s` on repo `%s`' % (
1817 'msg': 'Pulled from url `%s` on repo `%s`' % (
1818 remote_uri_display, repo.repo_name),
1818 remote_uri_display, repo.repo_name),
1819 'repository': repo.repo_name
1819 'repository': repo.repo_name
1820 }
1820 }
1821 except Exception:
1821 except Exception:
1822 log.exception("Exception occurred while trying to "
1822 log.exception("Exception occurred while trying to "
1823 "pull changes from remote location")
1823 "pull changes from remote location")
1824 raise JSONRPCError(
1824 raise JSONRPCError(
1825 'Unable to pull changes from `%s`' % remote_uri_display
1825 'Unable to pull changes from `%s`' % remote_uri_display
1826 )
1826 )
1827
1827
1828
1828
1829 @jsonrpc_method()
1829 @jsonrpc_method()
1830 def strip(request, apiuser, repoid, revision, branch):
1830 def strip(request, apiuser, repoid, revision, branch):
1831 """
1831 """
1832 Strips the given revision from the specified repository.
1832 Strips the given revision from the specified repository.
1833
1833
1834 * This will remove the revision and all of its decendants.
1834 * This will remove the revision and all of its decendants.
1835
1835
1836 This command can only be run using an |authtoken| with admin rights to
1836 This command can only be run using an |authtoken| with admin rights to
1837 the specified repository.
1837 the specified repository.
1838
1838
1839 This command takes the following options:
1839 This command takes the following options:
1840
1840
1841 :param apiuser: This is filled automatically from the |authtoken|.
1841 :param apiuser: This is filled automatically from the |authtoken|.
1842 :type apiuser: AuthUser
1842 :type apiuser: AuthUser
1843 :param repoid: The repository name or repository ID.
1843 :param repoid: The repository name or repository ID.
1844 :type repoid: str or int
1844 :type repoid: str or int
1845 :param revision: The revision you wish to strip.
1845 :param revision: The revision you wish to strip.
1846 :type revision: str
1846 :type revision: str
1847 :param branch: The branch from which to strip the revision.
1847 :param branch: The branch from which to strip the revision.
1848 :type branch: str
1848 :type branch: str
1849
1849
1850 Example output:
1850 Example output:
1851
1851
1852 .. code-block:: bash
1852 .. code-block:: bash
1853
1853
1854 id : <id_given_in_input>
1854 id : <id_given_in_input>
1855 result : {
1855 result : {
1856 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
1856 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
1857 "repository": "<repository name>"
1857 "repository": "<repository name>"
1858 }
1858 }
1859 error : null
1859 error : null
1860
1860
1861 Example error output:
1861 Example error output:
1862
1862
1863 .. code-block:: bash
1863 .. code-block:: bash
1864
1864
1865 id : <id_given_in_input>
1865 id : <id_given_in_input>
1866 result : null
1866 result : null
1867 error : {
1867 error : {
1868 "Unable to strip commit <commit_hash> from repo `<repository name>`"
1868 "Unable to strip commit <commit_hash> from repo `<repository name>`"
1869 }
1869 }
1870
1870
1871 """
1871 """
1872
1872
1873 repo = get_repo_or_error(repoid)
1873 repo = get_repo_or_error(repoid)
1874 if not has_superadmin_permission(apiuser):
1874 if not has_superadmin_permission(apiuser):
1875 _perms = ('repository.admin',)
1875 _perms = ('repository.admin',)
1876 validate_repo_permissions(apiuser, repoid, repo, _perms)
1876 validate_repo_permissions(apiuser, repoid, repo, _perms)
1877
1877
1878 try:
1878 try:
1879 ScmModel().strip(repo, revision, branch)
1879 ScmModel().strip(repo, revision, branch)
1880 audit_logger.store_api(
1880 audit_logger.store_api(
1881 'repo.commit.strip', action_data={'commit_id': revision},
1881 'repo.commit.strip', action_data={'commit_id': revision},
1882 repo=repo,
1882 repo=repo,
1883 user=apiuser, commit=True)
1883 user=apiuser, commit=True)
1884
1884
1885 return {
1885 return {
1886 'msg': 'Stripped commit %s from repo `%s`' % (
1886 'msg': 'Stripped commit %s from repo `%s`' % (
1887 revision, repo.repo_name),
1887 revision, repo.repo_name),
1888 'repository': repo.repo_name
1888 'repository': repo.repo_name
1889 }
1889 }
1890 except Exception:
1890 except Exception:
1891 log.exception("Exception while trying to strip")
1891 log.exception("Exception while trying to strip")
1892 raise JSONRPCError(
1892 raise JSONRPCError(
1893 'Unable to strip commit %s from repo `%s`' % (
1893 'Unable to strip commit %s from repo `%s`' % (
1894 revision, repo.repo_name)
1894 revision, repo.repo_name)
1895 )
1895 )
1896
1896
1897
1897
1898 @jsonrpc_method()
1898 @jsonrpc_method()
1899 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
1899 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
1900 """
1900 """
1901 Returns all settings for a repository. If key is given it only returns the
1901 Returns all settings for a repository. If key is given it only returns the
1902 setting identified by the key or null.
1902 setting identified by the key or null.
1903
1903
1904 :param apiuser: This is filled automatically from the |authtoken|.
1904 :param apiuser: This is filled automatically from the |authtoken|.
1905 :type apiuser: AuthUser
1905 :type apiuser: AuthUser
1906 :param repoid: The repository name or repository id.
1906 :param repoid: The repository name or repository id.
1907 :type repoid: str or int
1907 :type repoid: str or int
1908 :param key: Key of the setting to return.
1908 :param key: Key of the setting to return.
1909 :type: key: Optional(str)
1909 :type: key: Optional(str)
1910
1910
1911 Example output:
1911 Example output:
1912
1912
1913 .. code-block:: bash
1913 .. code-block:: bash
1914
1914
1915 {
1915 {
1916 "error": null,
1916 "error": null,
1917 "id": 237,
1917 "id": 237,
1918 "result": {
1918 "result": {
1919 "extensions_largefiles": true,
1919 "extensions_largefiles": true,
1920 "extensions_evolve": true,
1920 "extensions_evolve": true,
1921 "hooks_changegroup_push_logger": true,
1921 "hooks_changegroup_push_logger": true,
1922 "hooks_changegroup_repo_size": false,
1922 "hooks_changegroup_repo_size": false,
1923 "hooks_outgoing_pull_logger": true,
1923 "hooks_outgoing_pull_logger": true,
1924 "phases_publish": "True",
1924 "phases_publish": "True",
1925 "rhodecode_hg_use_rebase_for_merging": true,
1925 "rhodecode_hg_use_rebase_for_merging": true,
1926 "rhodecode_pr_merge_enabled": true,
1926 "rhodecode_pr_merge_enabled": true,
1927 "rhodecode_use_outdated_comments": true
1927 "rhodecode_use_outdated_comments": true
1928 }
1928 }
1929 }
1929 }
1930 """
1930 """
1931
1931
1932 # Restrict access to this api method to admins only.
1932 # Restrict access to this api method to admins only.
1933 if not has_superadmin_permission(apiuser):
1933 if not has_superadmin_permission(apiuser):
1934 raise JSONRPCForbidden()
1934 raise JSONRPCForbidden()
1935
1935
1936 try:
1936 try:
1937 repo = get_repo_or_error(repoid)
1937 repo = get_repo_or_error(repoid)
1938 settings_model = VcsSettingsModel(repo=repo)
1938 settings_model = VcsSettingsModel(repo=repo)
1939 settings = settings_model.get_global_settings()
1939 settings = settings_model.get_global_settings()
1940 settings.update(settings_model.get_repo_settings())
1940 settings.update(settings_model.get_repo_settings())
1941
1941
1942 # If only a single setting is requested fetch it from all settings.
1942 # If only a single setting is requested fetch it from all settings.
1943 key = Optional.extract(key)
1943 key = Optional.extract(key)
1944 if key is not None:
1944 if key is not None:
1945 settings = settings.get(key, None)
1945 settings = settings.get(key, None)
1946 except Exception:
1946 except Exception:
1947 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
1947 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
1948 log.exception(msg)
1948 log.exception(msg)
1949 raise JSONRPCError(msg)
1949 raise JSONRPCError(msg)
1950
1950
1951 return settings
1951 return settings
1952
1952
1953
1953
1954 @jsonrpc_method()
1954 @jsonrpc_method()
1955 def set_repo_settings(request, apiuser, repoid, settings):
1955 def set_repo_settings(request, apiuser, repoid, settings):
1956 """
1956 """
1957 Update repository settings. Returns true on success.
1957 Update repository settings. Returns true on success.
1958
1958
1959 :param apiuser: This is filled automatically from the |authtoken|.
1959 :param apiuser: This is filled automatically from the |authtoken|.
1960 :type apiuser: AuthUser
1960 :type apiuser: AuthUser
1961 :param repoid: The repository name or repository id.
1961 :param repoid: The repository name or repository id.
1962 :type repoid: str or int
1962 :type repoid: str or int
1963 :param settings: The new settings for the repository.
1963 :param settings: The new settings for the repository.
1964 :type: settings: dict
1964 :type: settings: dict
1965
1965
1966 Example output:
1966 Example output:
1967
1967
1968 .. code-block:: bash
1968 .. code-block:: bash
1969
1969
1970 {
1970 {
1971 "error": null,
1971 "error": null,
1972 "id": 237,
1972 "id": 237,
1973 "result": true
1973 "result": true
1974 }
1974 }
1975 """
1975 """
1976 # Restrict access to this api method to admins only.
1976 # Restrict access to this api method to admins only.
1977 if not has_superadmin_permission(apiuser):
1977 if not has_superadmin_permission(apiuser):
1978 raise JSONRPCForbidden()
1978 raise JSONRPCForbidden()
1979
1979
1980 if type(settings) is not dict:
1980 if type(settings) is not dict:
1981 raise JSONRPCError('Settings have to be a JSON Object.')
1981 raise JSONRPCError('Settings have to be a JSON Object.')
1982
1982
1983 try:
1983 try:
1984 settings_model = VcsSettingsModel(repo=repoid)
1984 settings_model = VcsSettingsModel(repo=repoid)
1985
1985
1986 # Merge global, repo and incoming settings.
1986 # Merge global, repo and incoming settings.
1987 new_settings = settings_model.get_global_settings()
1987 new_settings = settings_model.get_global_settings()
1988 new_settings.update(settings_model.get_repo_settings())
1988 new_settings.update(settings_model.get_repo_settings())
1989 new_settings.update(settings)
1989 new_settings.update(settings)
1990
1990
1991 # Update the settings.
1991 # Update the settings.
1992 inherit_global_settings = new_settings.get(
1992 inherit_global_settings = new_settings.get(
1993 'inherit_global_settings', False)
1993 'inherit_global_settings', False)
1994 settings_model.create_or_update_repo_settings(
1994 settings_model.create_or_update_repo_settings(
1995 new_settings, inherit_global_settings=inherit_global_settings)
1995 new_settings, inherit_global_settings=inherit_global_settings)
1996 Session().commit()
1996 Session().commit()
1997 except Exception:
1997 except Exception:
1998 msg = 'Failed to update settings for repository `{}`'.format(repoid)
1998 msg = 'Failed to update settings for repository `{}`'.format(repoid)
1999 log.exception(msg)
1999 log.exception(msg)
2000 raise JSONRPCError(msg)
2000 raise JSONRPCError(msg)
2001
2001
2002 # Indicate success.
2002 # Indicate success.
2003 return True
2003 return True
2004
2004
2005
2005
2006 @jsonrpc_method()
2006 @jsonrpc_method()
2007 def maintenance(request, apiuser, repoid):
2007 def maintenance(request, apiuser, repoid):
2008 """
2008 """
2009 Triggers a maintenance on the given repository.
2009 Triggers a maintenance on the given repository.
2010
2010
2011 This command can only be run using an |authtoken| with admin
2011 This command can only be run using an |authtoken| with admin
2012 rights to the specified repository. For more information,
2012 rights to the specified repository. For more information,
2013 see :ref:`config-token-ref`.
2013 see :ref:`config-token-ref`.
2014
2014
2015 This command takes the following options:
2015 This command takes the following options:
2016
2016
2017 :param apiuser: This is filled automatically from the |authtoken|.
2017 :param apiuser: This is filled automatically from the |authtoken|.
2018 :type apiuser: AuthUser
2018 :type apiuser: AuthUser
2019 :param repoid: The repository name or repository ID.
2019 :param repoid: The repository name or repository ID.
2020 :type repoid: str or int
2020 :type repoid: str or int
2021
2021
2022 Example output:
2022 Example output:
2023
2023
2024 .. code-block:: bash
2024 .. code-block:: bash
2025
2025
2026 id : <id_given_in_input>
2026 id : <id_given_in_input>
2027 result : {
2027 result : {
2028 "msg": "executed maintenance command",
2028 "msg": "executed maintenance command",
2029 "executed_actions": [
2029 "executed_actions": [
2030 <action_message>, <action_message2>...
2030 <action_message>, <action_message2>...
2031 ],
2031 ],
2032 "repository": "<repository name>"
2032 "repository": "<repository name>"
2033 }
2033 }
2034 error : null
2034 error : null
2035
2035
2036 Example error output:
2036 Example error output:
2037
2037
2038 .. code-block:: bash
2038 .. code-block:: bash
2039
2039
2040 id : <id_given_in_input>
2040 id : <id_given_in_input>
2041 result : null
2041 result : null
2042 error : {
2042 error : {
2043 "Unable to execute maintenance on `<reponame>`"
2043 "Unable to execute maintenance on `<reponame>`"
2044 }
2044 }
2045
2045
2046 """
2046 """
2047
2047
2048 repo = get_repo_or_error(repoid)
2048 repo = get_repo_or_error(repoid)
2049 if not has_superadmin_permission(apiuser):
2049 if not has_superadmin_permission(apiuser):
2050 _perms = ('repository.admin',)
2050 _perms = ('repository.admin',)
2051 validate_repo_permissions(apiuser, repoid, repo, _perms)
2051 validate_repo_permissions(apiuser, repoid, repo, _perms)
2052
2052
2053 try:
2053 try:
2054 maintenance = repo_maintenance.RepoMaintenance()
2054 maintenance = repo_maintenance.RepoMaintenance()
2055 executed_actions = maintenance.execute(repo)
2055 executed_actions = maintenance.execute(repo)
2056
2056
2057 return {
2057 return {
2058 'msg': 'executed maintenance command',
2058 'msg': 'executed maintenance command',
2059 'executed_actions': executed_actions,
2059 'executed_actions': executed_actions,
2060 'repository': repo.repo_name
2060 'repository': repo.repo_name
2061 }
2061 }
2062 except Exception:
2062 except Exception:
2063 log.exception("Exception occurred while trying to run maintenance")
2063 log.exception("Exception occurred while trying to run maintenance")
2064 raise JSONRPCError(
2064 raise JSONRPCError(
2065 'Unable to execute maintenance on `%s`' % repo.repo_name)
2065 'Unable to execute maintenance on `%s`' % repo.repo_name)
@@ -1,678 +1,679 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2018 RhodeCode GmbH
3 # Copyright (C) 2016-2018 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 time
21 import time
22 import logging
22 import logging
23 import operator
23 import operator
24
24
25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
26
26
27 from rhodecode.lib import helpers as h, diffs
27 from rhodecode.lib import helpers as h, diffs
28 from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time
28 from rhodecode.lib.utils2 import (
29 StrictAttributeDict, safe_int, datetime_to_time, safe_unicode)
29 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
30 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
30 from rhodecode.model import repo
31 from rhodecode.model import repo
31 from rhodecode.model import repo_group
32 from rhodecode.model import repo_group
32 from rhodecode.model import user_group
33 from rhodecode.model import user_group
33 from rhodecode.model import user
34 from rhodecode.model import user
34 from rhodecode.model.db import User
35 from rhodecode.model.db import User
35 from rhodecode.model.scm import ScmModel
36 from rhodecode.model.scm import ScmModel
36 from rhodecode.model.settings import VcsSettingsModel
37 from rhodecode.model.settings import VcsSettingsModel
37
38
38 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
39
40
40
41
41 ADMIN_PREFIX = '/_admin'
42 ADMIN_PREFIX = '/_admin'
42 STATIC_FILE_PREFIX = '/_static'
43 STATIC_FILE_PREFIX = '/_static'
43
44
44 URL_NAME_REQUIREMENTS = {
45 URL_NAME_REQUIREMENTS = {
45 # group name can have a slash in them, but they must not end with a slash
46 # group name can have a slash in them, but they must not end with a slash
46 'group_name': r'.*?[^/]',
47 'group_name': r'.*?[^/]',
47 'repo_group_name': r'.*?[^/]',
48 'repo_group_name': r'.*?[^/]',
48 # repo names can have a slash in them, but they must not end with a slash
49 # repo names can have a slash in them, but they must not end with a slash
49 'repo_name': r'.*?[^/]',
50 'repo_name': r'.*?[^/]',
50 # file path eats up everything at the end
51 # file path eats up everything at the end
51 'f_path': r'.*',
52 'f_path': r'.*',
52 # reference types
53 # reference types
53 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
54 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
54 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
55 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
55 }
56 }
56
57
57
58
58 def add_route_with_slash(config,name, pattern, **kw):
59 def add_route_with_slash(config,name, pattern, **kw):
59 config.add_route(name, pattern, **kw)
60 config.add_route(name, pattern, **kw)
60 if not pattern.endswith('/'):
61 if not pattern.endswith('/'):
61 config.add_route(name + '_slash', pattern + '/', **kw)
62 config.add_route(name + '_slash', pattern + '/', **kw)
62
63
63
64
64 def add_route_requirements(route_path, requirements=None):
65 def add_route_requirements(route_path, requirements=None):
65 """
66 """
66 Adds regex requirements to pyramid routes using a mapping dict
67 Adds regex requirements to pyramid routes using a mapping dict
67 e.g::
68 e.g::
68 add_route_requirements('{repo_name}/settings')
69 add_route_requirements('{repo_name}/settings')
69 """
70 """
70 requirements = requirements or URL_NAME_REQUIREMENTS
71 requirements = requirements or URL_NAME_REQUIREMENTS
71 for key, regex in requirements.items():
72 for key, regex in requirements.items():
72 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
73 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
73 return route_path
74 return route_path
74
75
75
76
76 def get_format_ref_id(repo):
77 def get_format_ref_id(repo):
77 """Returns a `repo` specific reference formatter function"""
78 """Returns a `repo` specific reference formatter function"""
78 if h.is_svn(repo):
79 if h.is_svn(repo):
79 return _format_ref_id_svn
80 return _format_ref_id_svn
80 else:
81 else:
81 return _format_ref_id
82 return _format_ref_id
82
83
83
84
84 def _format_ref_id(name, raw_id):
85 def _format_ref_id(name, raw_id):
85 """Default formatting of a given reference `name`"""
86 """Default formatting of a given reference `name`"""
86 return name
87 return name
87
88
88
89
89 def _format_ref_id_svn(name, raw_id):
90 def _format_ref_id_svn(name, raw_id):
90 """Special way of formatting a reference for Subversion including path"""
91 """Special way of formatting a reference for Subversion including path"""
91 return '%s@%s' % (name, raw_id)
92 return '%s@%s' % (name, raw_id)
92
93
93
94
94 class TemplateArgs(StrictAttributeDict):
95 class TemplateArgs(StrictAttributeDict):
95 pass
96 pass
96
97
97
98
98 class BaseAppView(object):
99 class BaseAppView(object):
99
100
100 def __init__(self, context, request):
101 def __init__(self, context, request):
101 self.request = request
102 self.request = request
102 self.context = context
103 self.context = context
103 self.session = request.session
104 self.session = request.session
104 if not hasattr(request, 'user'):
105 if not hasattr(request, 'user'):
105 # NOTE(marcink): edge case, we ended up in matched route
106 # NOTE(marcink): edge case, we ended up in matched route
106 # but probably of web-app context, e.g API CALL/VCS CALL
107 # but probably of web-app context, e.g API CALL/VCS CALL
107 if hasattr(request, 'vcs_call') or hasattr(request, 'rpc_method'):
108 if hasattr(request, 'vcs_call') or hasattr(request, 'rpc_method'):
108 log.warning('Unable to process request `%s` in this scope', request)
109 log.warning('Unable to process request `%s` in this scope', request)
109 raise HTTPBadRequest()
110 raise HTTPBadRequest()
110
111
111 self._rhodecode_user = request.user # auth user
112 self._rhodecode_user = request.user # auth user
112 self._rhodecode_db_user = self._rhodecode_user.get_instance()
113 self._rhodecode_db_user = self._rhodecode_user.get_instance()
113 self._maybe_needs_password_change(
114 self._maybe_needs_password_change(
114 request.matched_route.name, self._rhodecode_db_user)
115 request.matched_route.name, self._rhodecode_db_user)
115
116
116 def _maybe_needs_password_change(self, view_name, user_obj):
117 def _maybe_needs_password_change(self, view_name, user_obj):
117 log.debug('Checking if user %s needs password change on view %s',
118 log.debug('Checking if user %s needs password change on view %s',
118 user_obj, view_name)
119 user_obj, view_name)
119 skip_user_views = [
120 skip_user_views = [
120 'logout', 'login',
121 'logout', 'login',
121 'my_account_password', 'my_account_password_update'
122 'my_account_password', 'my_account_password_update'
122 ]
123 ]
123
124
124 if not user_obj:
125 if not user_obj:
125 return
126 return
126
127
127 if user_obj.username == User.DEFAULT_USER:
128 if user_obj.username == User.DEFAULT_USER:
128 return
129 return
129
130
130 now = time.time()
131 now = time.time()
131 should_change = user_obj.user_data.get('force_password_change')
132 should_change = user_obj.user_data.get('force_password_change')
132 change_after = safe_int(should_change) or 0
133 change_after = safe_int(should_change) or 0
133 if should_change and now > change_after:
134 if should_change and now > change_after:
134 log.debug('User %s requires password change', user_obj)
135 log.debug('User %s requires password change', user_obj)
135 h.flash('You are required to change your password', 'warning',
136 h.flash('You are required to change your password', 'warning',
136 ignore_duplicate=True)
137 ignore_duplicate=True)
137
138
138 if view_name not in skip_user_views:
139 if view_name not in skip_user_views:
139 raise HTTPFound(
140 raise HTTPFound(
140 self.request.route_path('my_account_password'))
141 self.request.route_path('my_account_password'))
141
142
142 def _log_creation_exception(self, e, repo_name):
143 def _log_creation_exception(self, e, repo_name):
143 _ = self.request.translate
144 _ = self.request.translate
144 reason = None
145 reason = None
145 if len(e.args) == 2:
146 if len(e.args) == 2:
146 reason = e.args[1]
147 reason = e.args[1]
147
148
148 if reason == 'INVALID_CERTIFICATE':
149 if reason == 'INVALID_CERTIFICATE':
149 log.exception(
150 log.exception(
150 'Exception creating a repository: invalid certificate')
151 'Exception creating a repository: invalid certificate')
151 msg = (_('Error creating repository %s: invalid certificate')
152 msg = (_('Error creating repository %s: invalid certificate')
152 % repo_name)
153 % repo_name)
153 else:
154 else:
154 log.exception("Exception creating a repository")
155 log.exception("Exception creating a repository")
155 msg = (_('Error creating repository %s')
156 msg = (_('Error creating repository %s')
156 % repo_name)
157 % repo_name)
157 return msg
158 return msg
158
159
159 def _get_local_tmpl_context(self, include_app_defaults=True):
160 def _get_local_tmpl_context(self, include_app_defaults=True):
160 c = TemplateArgs()
161 c = TemplateArgs()
161 c.auth_user = self.request.user
162 c.auth_user = self.request.user
162 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
163 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
163 c.rhodecode_user = self.request.user
164 c.rhodecode_user = self.request.user
164
165
165 if include_app_defaults:
166 if include_app_defaults:
166 from rhodecode.lib.base import attach_context_attributes
167 from rhodecode.lib.base import attach_context_attributes
167 attach_context_attributes(c, self.request, self.request.user.user_id)
168 attach_context_attributes(c, self.request, self.request.user.user_id)
168
169
169 return c
170 return c
170
171
171 def _get_template_context(self, tmpl_args, **kwargs):
172 def _get_template_context(self, tmpl_args, **kwargs):
172
173
173 local_tmpl_args = {
174 local_tmpl_args = {
174 'defaults': {},
175 'defaults': {},
175 'errors': {},
176 'errors': {},
176 'c': tmpl_args
177 'c': tmpl_args
177 }
178 }
178 local_tmpl_args.update(kwargs)
179 local_tmpl_args.update(kwargs)
179 return local_tmpl_args
180 return local_tmpl_args
180
181
181 def load_default_context(self):
182 def load_default_context(self):
182 """
183 """
183 example:
184 example:
184
185
185 def load_default_context(self):
186 def load_default_context(self):
186 c = self._get_local_tmpl_context()
187 c = self._get_local_tmpl_context()
187 c.custom_var = 'foobar'
188 c.custom_var = 'foobar'
188
189
189 return c
190 return c
190 """
191 """
191 raise NotImplementedError('Needs implementation in view class')
192 raise NotImplementedError('Needs implementation in view class')
192
193
193
194
194 class RepoAppView(BaseAppView):
195 class RepoAppView(BaseAppView):
195
196
196 def __init__(self, context, request):
197 def __init__(self, context, request):
197 super(RepoAppView, self).__init__(context, request)
198 super(RepoAppView, self).__init__(context, request)
198 self.db_repo = request.db_repo
199 self.db_repo = request.db_repo
199 self.db_repo_name = self.db_repo.repo_name
200 self.db_repo_name = self.db_repo.repo_name
200 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
201 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
201
202
202 def _handle_missing_requirements(self, error):
203 def _handle_missing_requirements(self, error):
203 log.error(
204 log.error(
204 'Requirements are missing for repository %s: %s',
205 'Requirements are missing for repository %s: %s',
205 self.db_repo_name, error.message)
206 self.db_repo_name, safe_unicode(error))
206
207
207 def _get_local_tmpl_context(self, include_app_defaults=True):
208 def _get_local_tmpl_context(self, include_app_defaults=True):
208 _ = self.request.translate
209 _ = self.request.translate
209 c = super(RepoAppView, self)._get_local_tmpl_context(
210 c = super(RepoAppView, self)._get_local_tmpl_context(
210 include_app_defaults=include_app_defaults)
211 include_app_defaults=include_app_defaults)
211
212
212 # register common vars for this type of view
213 # register common vars for this type of view
213 c.rhodecode_db_repo = self.db_repo
214 c.rhodecode_db_repo = self.db_repo
214 c.repo_name = self.db_repo_name
215 c.repo_name = self.db_repo_name
215 c.repository_pull_requests = self.db_repo_pull_requests
216 c.repository_pull_requests = self.db_repo_pull_requests
216 self.path_filter = PathFilter(None)
217 self.path_filter = PathFilter(None)
217
218
218 c.repository_requirements_missing = {}
219 c.repository_requirements_missing = {}
219 try:
220 try:
220 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
221 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
221 if self.rhodecode_vcs_repo:
222 if self.rhodecode_vcs_repo:
222 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
223 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
223 c.auth_user.username)
224 c.auth_user.username)
224 self.path_filter = PathFilter(path_perms)
225 self.path_filter = PathFilter(path_perms)
225 except RepositoryRequirementError as e:
226 except RepositoryRequirementError as e:
226 c.repository_requirements_missing = {'error': str(e)}
227 c.repository_requirements_missing = {'error': str(e)}
227 self._handle_missing_requirements(e)
228 self._handle_missing_requirements(e)
228 self.rhodecode_vcs_repo = None
229 self.rhodecode_vcs_repo = None
229
230
230 c.path_filter = self.path_filter # used by atom_feed_entry.mako
231 c.path_filter = self.path_filter # used by atom_feed_entry.mako
231
232
232 if self.rhodecode_vcs_repo is None:
233 if self.rhodecode_vcs_repo is None:
233 # unable to fetch this repo as vcs instance, report back to user
234 # unable to fetch this repo as vcs instance, report back to user
234 h.flash(_(
235 h.flash(_(
235 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
236 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
236 "Please check if it exist, or is not damaged.") %
237 "Please check if it exist, or is not damaged.") %
237 {'repo_name': c.repo_name},
238 {'repo_name': c.repo_name},
238 category='error', ignore_duplicate=True)
239 category='error', ignore_duplicate=True)
239 if c.repository_requirements_missing:
240 if c.repository_requirements_missing:
240 route = self.request.matched_route.name
241 route = self.request.matched_route.name
241 if route.startswith(('edit_repo', 'repo_summary')):
242 if route.startswith(('edit_repo', 'repo_summary')):
242 # allow summary and edit repo on missing requirements
243 # allow summary and edit repo on missing requirements
243 return c
244 return c
244
245
245 raise HTTPFound(
246 raise HTTPFound(
246 h.route_path('repo_summary', repo_name=self.db_repo_name))
247 h.route_path('repo_summary', repo_name=self.db_repo_name))
247
248
248 else: # redirect if we don't show missing requirements
249 else: # redirect if we don't show missing requirements
249 raise HTTPFound(h.route_path('home'))
250 raise HTTPFound(h.route_path('home'))
250
251
251 return c
252 return c
252
253
253 def _get_f_path_unchecked(self, matchdict, default=None):
254 def _get_f_path_unchecked(self, matchdict, default=None):
254 """
255 """
255 Should only be used by redirects, everything else should call _get_f_path
256 Should only be used by redirects, everything else should call _get_f_path
256 """
257 """
257 f_path = matchdict.get('f_path')
258 f_path = matchdict.get('f_path')
258 if f_path:
259 if f_path:
259 # fix for multiple initial slashes that causes errors for GIT
260 # fix for multiple initial slashes that causes errors for GIT
260 return f_path.lstrip('/')
261 return f_path.lstrip('/')
261
262
262 return default
263 return default
263
264
264 def _get_f_path(self, matchdict, default=None):
265 def _get_f_path(self, matchdict, default=None):
265 f_path_match = self._get_f_path_unchecked(matchdict, default)
266 f_path_match = self._get_f_path_unchecked(matchdict, default)
266 return self.path_filter.assert_path_permissions(f_path_match)
267 return self.path_filter.assert_path_permissions(f_path_match)
267
268
268 def _get_general_setting(self, target_repo, settings_key, default=False):
269 def _get_general_setting(self, target_repo, settings_key, default=False):
269 settings_model = VcsSettingsModel(repo=target_repo)
270 settings_model = VcsSettingsModel(repo=target_repo)
270 settings = settings_model.get_general_settings()
271 settings = settings_model.get_general_settings()
271 return settings.get(settings_key, default)
272 return settings.get(settings_key, default)
272
273
273
274
274 class PathFilter(object):
275 class PathFilter(object):
275
276
276 # Expects and instance of BasePathPermissionChecker or None
277 # Expects and instance of BasePathPermissionChecker or None
277 def __init__(self, permission_checker):
278 def __init__(self, permission_checker):
278 self.permission_checker = permission_checker
279 self.permission_checker = permission_checker
279
280
280 def assert_path_permissions(self, path):
281 def assert_path_permissions(self, path):
281 if path and self.permission_checker and not self.permission_checker.has_access(path):
282 if path and self.permission_checker and not self.permission_checker.has_access(path):
282 raise HTTPForbidden()
283 raise HTTPForbidden()
283 return path
284 return path
284
285
285 def filter_patchset(self, patchset):
286 def filter_patchset(self, patchset):
286 if not self.permission_checker or not patchset:
287 if not self.permission_checker or not patchset:
287 return patchset, False
288 return patchset, False
288 had_filtered = False
289 had_filtered = False
289 filtered_patchset = []
290 filtered_patchset = []
290 for patch in patchset:
291 for patch in patchset:
291 filename = patch.get('filename', None)
292 filename = patch.get('filename', None)
292 if not filename or self.permission_checker.has_access(filename):
293 if not filename or self.permission_checker.has_access(filename):
293 filtered_patchset.append(patch)
294 filtered_patchset.append(patch)
294 else:
295 else:
295 had_filtered = True
296 had_filtered = True
296 if had_filtered:
297 if had_filtered:
297 if isinstance(patchset, diffs.LimitedDiffContainer):
298 if isinstance(patchset, diffs.LimitedDiffContainer):
298 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
299 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
299 return filtered_patchset, True
300 return filtered_patchset, True
300 else:
301 else:
301 return patchset, False
302 return patchset, False
302
303
303 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
304 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
304 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
305 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
305 result = diffset.render_patchset(filtered_patchset, source_ref=source_ref, target_ref=target_ref)
306 result = diffset.render_patchset(filtered_patchset, source_ref=source_ref, target_ref=target_ref)
306 result.has_hidden_changes = has_hidden_changes
307 result.has_hidden_changes = has_hidden_changes
307 return result
308 return result
308
309
309 def get_raw_patch(self, diff_processor):
310 def get_raw_patch(self, diff_processor):
310 if self.permission_checker is None:
311 if self.permission_checker is None:
311 return diff_processor.as_raw()
312 return diff_processor.as_raw()
312 elif self.permission_checker.has_full_access:
313 elif self.permission_checker.has_full_access:
313 return diff_processor.as_raw()
314 return diff_processor.as_raw()
314 else:
315 else:
315 return '# Repository has user-specific filters, raw patch generation is disabled.'
316 return '# Repository has user-specific filters, raw patch generation is disabled.'
316
317
317 @property
318 @property
318 def is_enabled(self):
319 def is_enabled(self):
319 return self.permission_checker is not None
320 return self.permission_checker is not None
320
321
321
322
322 class RepoGroupAppView(BaseAppView):
323 class RepoGroupAppView(BaseAppView):
323 def __init__(self, context, request):
324 def __init__(self, context, request):
324 super(RepoGroupAppView, self).__init__(context, request)
325 super(RepoGroupAppView, self).__init__(context, request)
325 self.db_repo_group = request.db_repo_group
326 self.db_repo_group = request.db_repo_group
326 self.db_repo_group_name = self.db_repo_group.group_name
327 self.db_repo_group_name = self.db_repo_group.group_name
327
328
328 def _revoke_perms_on_yourself(self, form_result):
329 def _revoke_perms_on_yourself(self, form_result):
329 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
330 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
330 form_result['perm_updates'])
331 form_result['perm_updates'])
331 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
332 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
332 form_result['perm_additions'])
333 form_result['perm_additions'])
333 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
334 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
334 form_result['perm_deletions'])
335 form_result['perm_deletions'])
335 admin_perm = 'group.admin'
336 admin_perm = 'group.admin'
336 if _updates and _updates[0][1] != admin_perm or \
337 if _updates and _updates[0][1] != admin_perm or \
337 _additions and _additions[0][1] != admin_perm or \
338 _additions and _additions[0][1] != admin_perm or \
338 _deletions and _deletions[0][1] != admin_perm:
339 _deletions and _deletions[0][1] != admin_perm:
339 return True
340 return True
340 return False
341 return False
341
342
342
343
343 class UserGroupAppView(BaseAppView):
344 class UserGroupAppView(BaseAppView):
344 def __init__(self, context, request):
345 def __init__(self, context, request):
345 super(UserGroupAppView, self).__init__(context, request)
346 super(UserGroupAppView, self).__init__(context, request)
346 self.db_user_group = request.db_user_group
347 self.db_user_group = request.db_user_group
347 self.db_user_group_name = self.db_user_group.users_group_name
348 self.db_user_group_name = self.db_user_group.users_group_name
348
349
349
350
350 class UserAppView(BaseAppView):
351 class UserAppView(BaseAppView):
351 def __init__(self, context, request):
352 def __init__(self, context, request):
352 super(UserAppView, self).__init__(context, request)
353 super(UserAppView, self).__init__(context, request)
353 self.db_user = request.db_user
354 self.db_user = request.db_user
354 self.db_user_id = self.db_user.user_id
355 self.db_user_id = self.db_user.user_id
355
356
356 _ = self.request.translate
357 _ = self.request.translate
357 if not request.db_user_supports_default:
358 if not request.db_user_supports_default:
358 if self.db_user.username == User.DEFAULT_USER:
359 if self.db_user.username == User.DEFAULT_USER:
359 h.flash(_("Editing user `{}` is disabled.".format(
360 h.flash(_("Editing user `{}` is disabled.".format(
360 User.DEFAULT_USER)), category='warning')
361 User.DEFAULT_USER)), category='warning')
361 raise HTTPFound(h.route_path('users'))
362 raise HTTPFound(h.route_path('users'))
362
363
363
364
364 class DataGridAppView(object):
365 class DataGridAppView(object):
365 """
366 """
366 Common class to have re-usable grid rendering components
367 Common class to have re-usable grid rendering components
367 """
368 """
368
369
369 def _extract_ordering(self, request, column_map=None):
370 def _extract_ordering(self, request, column_map=None):
370 column_map = column_map or {}
371 column_map = column_map or {}
371 column_index = safe_int(request.GET.get('order[0][column]'))
372 column_index = safe_int(request.GET.get('order[0][column]'))
372 order_dir = request.GET.get(
373 order_dir = request.GET.get(
373 'order[0][dir]', 'desc')
374 'order[0][dir]', 'desc')
374 order_by = request.GET.get(
375 order_by = request.GET.get(
375 'columns[%s][data][sort]' % column_index, 'name_raw')
376 'columns[%s][data][sort]' % column_index, 'name_raw')
376
377
377 # translate datatable to DB columns
378 # translate datatable to DB columns
378 order_by = column_map.get(order_by) or order_by
379 order_by = column_map.get(order_by) or order_by
379
380
380 search_q = request.GET.get('search[value]')
381 search_q = request.GET.get('search[value]')
381 return search_q, order_by, order_dir
382 return search_q, order_by, order_dir
382
383
383 def _extract_chunk(self, request):
384 def _extract_chunk(self, request):
384 start = safe_int(request.GET.get('start'), 0)
385 start = safe_int(request.GET.get('start'), 0)
385 length = safe_int(request.GET.get('length'), 25)
386 length = safe_int(request.GET.get('length'), 25)
386 draw = safe_int(request.GET.get('draw'))
387 draw = safe_int(request.GET.get('draw'))
387 return draw, start, length
388 return draw, start, length
388
389
389 def _get_order_col(self, order_by, model):
390 def _get_order_col(self, order_by, model):
390 if isinstance(order_by, basestring):
391 if isinstance(order_by, basestring):
391 try:
392 try:
392 return operator.attrgetter(order_by)(model)
393 return operator.attrgetter(order_by)(model)
393 except AttributeError:
394 except AttributeError:
394 return None
395 return None
395 else:
396 else:
396 return order_by
397 return order_by
397
398
398
399
399 class BaseReferencesView(RepoAppView):
400 class BaseReferencesView(RepoAppView):
400 """
401 """
401 Base for reference view for branches, tags and bookmarks.
402 Base for reference view for branches, tags and bookmarks.
402 """
403 """
403 def load_default_context(self):
404 def load_default_context(self):
404 c = self._get_local_tmpl_context()
405 c = self._get_local_tmpl_context()
405
406
406
407
407 return c
408 return c
408
409
409 def load_refs_context(self, ref_items, partials_template):
410 def load_refs_context(self, ref_items, partials_template):
410 _render = self.request.get_partial_renderer(partials_template)
411 _render = self.request.get_partial_renderer(partials_template)
411 pre_load = ["author", "date", "message"]
412 pre_load = ["author", "date", "message"]
412
413
413 is_svn = h.is_svn(self.rhodecode_vcs_repo)
414 is_svn = h.is_svn(self.rhodecode_vcs_repo)
414 is_hg = h.is_hg(self.rhodecode_vcs_repo)
415 is_hg = h.is_hg(self.rhodecode_vcs_repo)
415
416
416 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
417 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
417
418
418 closed_refs = {}
419 closed_refs = {}
419 if is_hg:
420 if is_hg:
420 closed_refs = self.rhodecode_vcs_repo.branches_closed
421 closed_refs = self.rhodecode_vcs_repo.branches_closed
421
422
422 data = []
423 data = []
423 for ref_name, commit_id in ref_items:
424 for ref_name, commit_id in ref_items:
424 commit = self.rhodecode_vcs_repo.get_commit(
425 commit = self.rhodecode_vcs_repo.get_commit(
425 commit_id=commit_id, pre_load=pre_load)
426 commit_id=commit_id, pre_load=pre_load)
426 closed = ref_name in closed_refs
427 closed = ref_name in closed_refs
427
428
428 # TODO: johbo: Unify generation of reference links
429 # TODO: johbo: Unify generation of reference links
429 use_commit_id = '/' in ref_name or is_svn
430 use_commit_id = '/' in ref_name or is_svn
430
431
431 if use_commit_id:
432 if use_commit_id:
432 files_url = h.route_path(
433 files_url = h.route_path(
433 'repo_files',
434 'repo_files',
434 repo_name=self.db_repo_name,
435 repo_name=self.db_repo_name,
435 f_path=ref_name if is_svn else '',
436 f_path=ref_name if is_svn else '',
436 commit_id=commit_id)
437 commit_id=commit_id)
437
438
438 else:
439 else:
439 files_url = h.route_path(
440 files_url = h.route_path(
440 'repo_files',
441 'repo_files',
441 repo_name=self.db_repo_name,
442 repo_name=self.db_repo_name,
442 f_path=ref_name if is_svn else '',
443 f_path=ref_name if is_svn else '',
443 commit_id=ref_name,
444 commit_id=ref_name,
444 _query=dict(at=ref_name))
445 _query=dict(at=ref_name))
445
446
446 data.append({
447 data.append({
447 "name": _render('name', ref_name, files_url, closed),
448 "name": _render('name', ref_name, files_url, closed),
448 "name_raw": ref_name,
449 "name_raw": ref_name,
449 "date": _render('date', commit.date),
450 "date": _render('date', commit.date),
450 "date_raw": datetime_to_time(commit.date),
451 "date_raw": datetime_to_time(commit.date),
451 "author": _render('author', commit.author),
452 "author": _render('author', commit.author),
452 "commit": _render(
453 "commit": _render(
453 'commit', commit.message, commit.raw_id, commit.idx),
454 'commit', commit.message, commit.raw_id, commit.idx),
454 "commit_raw": commit.idx,
455 "commit_raw": commit.idx,
455 "compare": _render(
456 "compare": _render(
456 'compare', format_ref_id(ref_name, commit.raw_id)),
457 'compare', format_ref_id(ref_name, commit.raw_id)),
457 })
458 })
458
459
459 return data
460 return data
460
461
461
462
462 class RepoRoutePredicate(object):
463 class RepoRoutePredicate(object):
463 def __init__(self, val, config):
464 def __init__(self, val, config):
464 self.val = val
465 self.val = val
465
466
466 def text(self):
467 def text(self):
467 return 'repo_route = %s' % self.val
468 return 'repo_route = %s' % self.val
468
469
469 phash = text
470 phash = text
470
471
471 def __call__(self, info, request):
472 def __call__(self, info, request):
472 if hasattr(request, 'vcs_call'):
473 if hasattr(request, 'vcs_call'):
473 # skip vcs calls
474 # skip vcs calls
474 return
475 return
475
476
476 repo_name = info['match']['repo_name']
477 repo_name = info['match']['repo_name']
477 repo_model = repo.RepoModel()
478 repo_model = repo.RepoModel()
478
479
479 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
480 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
480
481
481 def redirect_if_creating(route_info, db_repo):
482 def redirect_if_creating(route_info, db_repo):
482 skip_views = ['edit_repo_advanced_delete']
483 skip_views = ['edit_repo_advanced_delete']
483 route = route_info['route']
484 route = route_info['route']
484 # we should skip delete view so we can actually "remove" repositories
485 # we should skip delete view so we can actually "remove" repositories
485 # if they get stuck in creating state.
486 # if they get stuck in creating state.
486 if route.name in skip_views:
487 if route.name in skip_views:
487 return
488 return
488
489
489 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
490 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
490 repo_creating_url = request.route_path(
491 repo_creating_url = request.route_path(
491 'repo_creating', repo_name=db_repo.repo_name)
492 'repo_creating', repo_name=db_repo.repo_name)
492 raise HTTPFound(repo_creating_url)
493 raise HTTPFound(repo_creating_url)
493
494
494 if by_name_match:
495 if by_name_match:
495 # register this as request object we can re-use later
496 # register this as request object we can re-use later
496 request.db_repo = by_name_match
497 request.db_repo = by_name_match
497 redirect_if_creating(info, by_name_match)
498 redirect_if_creating(info, by_name_match)
498 return True
499 return True
499
500
500 by_id_match = repo_model.get_repo_by_id(repo_name)
501 by_id_match = repo_model.get_repo_by_id(repo_name)
501 if by_id_match:
502 if by_id_match:
502 request.db_repo = by_id_match
503 request.db_repo = by_id_match
503 redirect_if_creating(info, by_id_match)
504 redirect_if_creating(info, by_id_match)
504 return True
505 return True
505
506
506 return False
507 return False
507
508
508
509
509 class RepoForbidArchivedRoutePredicate(object):
510 class RepoForbidArchivedRoutePredicate(object):
510 def __init__(self, val, config):
511 def __init__(self, val, config):
511 self.val = val
512 self.val = val
512
513
513 def text(self):
514 def text(self):
514 return 'repo_forbid_archived = %s' % self.val
515 return 'repo_forbid_archived = %s' % self.val
515
516
516 phash = text
517 phash = text
517
518
518 def __call__(self, info, request):
519 def __call__(self, info, request):
519 _ = request.translate
520 _ = request.translate
520 rhodecode_db_repo = request.db_repo
521 rhodecode_db_repo = request.db_repo
521
522
522 log.debug(
523 log.debug(
523 '%s checking if archived flag for repo for %s',
524 '%s checking if archived flag for repo for %s',
524 self.__class__.__name__, rhodecode_db_repo.repo_name)
525 self.__class__.__name__, rhodecode_db_repo.repo_name)
525
526
526 if rhodecode_db_repo.archived:
527 if rhodecode_db_repo.archived:
527 log.warning('Current view is not supported for archived repo:%s',
528 log.warning('Current view is not supported for archived repo:%s',
528 rhodecode_db_repo.repo_name)
529 rhodecode_db_repo.repo_name)
529
530
530 h.flash(
531 h.flash(
531 h.literal(_('Action not supported for archived repository.')),
532 h.literal(_('Action not supported for archived repository.')),
532 category='warning')
533 category='warning')
533 summary_url = request.route_path(
534 summary_url = request.route_path(
534 'repo_summary', repo_name=rhodecode_db_repo.repo_name)
535 'repo_summary', repo_name=rhodecode_db_repo.repo_name)
535 raise HTTPFound(summary_url)
536 raise HTTPFound(summary_url)
536 return True
537 return True
537
538
538
539
539 class RepoTypeRoutePredicate(object):
540 class RepoTypeRoutePredicate(object):
540 def __init__(self, val, config):
541 def __init__(self, val, config):
541 self.val = val or ['hg', 'git', 'svn']
542 self.val = val or ['hg', 'git', 'svn']
542
543
543 def text(self):
544 def text(self):
544 return 'repo_accepted_type = %s' % self.val
545 return 'repo_accepted_type = %s' % self.val
545
546
546 phash = text
547 phash = text
547
548
548 def __call__(self, info, request):
549 def __call__(self, info, request):
549 if hasattr(request, 'vcs_call'):
550 if hasattr(request, 'vcs_call'):
550 # skip vcs calls
551 # skip vcs calls
551 return
552 return
552
553
553 rhodecode_db_repo = request.db_repo
554 rhodecode_db_repo = request.db_repo
554
555
555 log.debug(
556 log.debug(
556 '%s checking repo type for %s in %s',
557 '%s checking repo type for %s in %s',
557 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
558 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
558
559
559 if rhodecode_db_repo.repo_type in self.val:
560 if rhodecode_db_repo.repo_type in self.val:
560 return True
561 return True
561 else:
562 else:
562 log.warning('Current view is not supported for repo type:%s',
563 log.warning('Current view is not supported for repo type:%s',
563 rhodecode_db_repo.repo_type)
564 rhodecode_db_repo.repo_type)
564 return False
565 return False
565
566
566
567
567 class RepoGroupRoutePredicate(object):
568 class RepoGroupRoutePredicate(object):
568 def __init__(self, val, config):
569 def __init__(self, val, config):
569 self.val = val
570 self.val = val
570
571
571 def text(self):
572 def text(self):
572 return 'repo_group_route = %s' % self.val
573 return 'repo_group_route = %s' % self.val
573
574
574 phash = text
575 phash = text
575
576
576 def __call__(self, info, request):
577 def __call__(self, info, request):
577 if hasattr(request, 'vcs_call'):
578 if hasattr(request, 'vcs_call'):
578 # skip vcs calls
579 # skip vcs calls
579 return
580 return
580
581
581 repo_group_name = info['match']['repo_group_name']
582 repo_group_name = info['match']['repo_group_name']
582 repo_group_model = repo_group.RepoGroupModel()
583 repo_group_model = repo_group.RepoGroupModel()
583 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
584 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
584
585
585 if by_name_match:
586 if by_name_match:
586 # register this as request object we can re-use later
587 # register this as request object we can re-use later
587 request.db_repo_group = by_name_match
588 request.db_repo_group = by_name_match
588 return True
589 return True
589
590
590 return False
591 return False
591
592
592
593
593 class UserGroupRoutePredicate(object):
594 class UserGroupRoutePredicate(object):
594 def __init__(self, val, config):
595 def __init__(self, val, config):
595 self.val = val
596 self.val = val
596
597
597 def text(self):
598 def text(self):
598 return 'user_group_route = %s' % self.val
599 return 'user_group_route = %s' % self.val
599
600
600 phash = text
601 phash = text
601
602
602 def __call__(self, info, request):
603 def __call__(self, info, request):
603 if hasattr(request, 'vcs_call'):
604 if hasattr(request, 'vcs_call'):
604 # skip vcs calls
605 # skip vcs calls
605 return
606 return
606
607
607 user_group_id = info['match']['user_group_id']
608 user_group_id = info['match']['user_group_id']
608 user_group_model = user_group.UserGroup()
609 user_group_model = user_group.UserGroup()
609 by_id_match = user_group_model.get(user_group_id, cache=False)
610 by_id_match = user_group_model.get(user_group_id, cache=False)
610
611
611 if by_id_match:
612 if by_id_match:
612 # register this as request object we can re-use later
613 # register this as request object we can re-use later
613 request.db_user_group = by_id_match
614 request.db_user_group = by_id_match
614 return True
615 return True
615
616
616 return False
617 return False
617
618
618
619
619 class UserRoutePredicateBase(object):
620 class UserRoutePredicateBase(object):
620 supports_default = None
621 supports_default = None
621
622
622 def __init__(self, val, config):
623 def __init__(self, val, config):
623 self.val = val
624 self.val = val
624
625
625 def text(self):
626 def text(self):
626 raise NotImplementedError()
627 raise NotImplementedError()
627
628
628 def __call__(self, info, request):
629 def __call__(self, info, request):
629 if hasattr(request, 'vcs_call'):
630 if hasattr(request, 'vcs_call'):
630 # skip vcs calls
631 # skip vcs calls
631 return
632 return
632
633
633 user_id = info['match']['user_id']
634 user_id = info['match']['user_id']
634 user_model = user.User()
635 user_model = user.User()
635 by_id_match = user_model.get(user_id, cache=False)
636 by_id_match = user_model.get(user_id, cache=False)
636
637
637 if by_id_match:
638 if by_id_match:
638 # register this as request object we can re-use later
639 # register this as request object we can re-use later
639 request.db_user = by_id_match
640 request.db_user = by_id_match
640 request.db_user_supports_default = self.supports_default
641 request.db_user_supports_default = self.supports_default
641 return True
642 return True
642
643
643 return False
644 return False
644
645
645
646
646 class UserRoutePredicate(UserRoutePredicateBase):
647 class UserRoutePredicate(UserRoutePredicateBase):
647 supports_default = False
648 supports_default = False
648
649
649 def text(self):
650 def text(self):
650 return 'user_route = %s' % self.val
651 return 'user_route = %s' % self.val
651
652
652 phash = text
653 phash = text
653
654
654
655
655 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
656 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
656 supports_default = True
657 supports_default = True
657
658
658 def text(self):
659 def text(self):
659 return 'user_with_default_route = %s' % self.val
660 return 'user_with_default_route = %s' % self.val
660
661
661 phash = text
662 phash = text
662
663
663
664
664 def includeme(config):
665 def includeme(config):
665 config.add_route_predicate(
666 config.add_route_predicate(
666 'repo_route', RepoRoutePredicate)
667 'repo_route', RepoRoutePredicate)
667 config.add_route_predicate(
668 config.add_route_predicate(
668 'repo_accepted_types', RepoTypeRoutePredicate)
669 'repo_accepted_types', RepoTypeRoutePredicate)
669 config.add_route_predicate(
670 config.add_route_predicate(
670 'repo_forbid_when_archived', RepoForbidArchivedRoutePredicate)
671 'repo_forbid_when_archived', RepoForbidArchivedRoutePredicate)
671 config.add_route_predicate(
672 config.add_route_predicate(
672 'repo_group_route', RepoGroupRoutePredicate)
673 'repo_group_route', RepoGroupRoutePredicate)
673 config.add_route_predicate(
674 config.add_route_predicate(
674 'user_group_route', UserGroupRoutePredicate)
675 'user_group_route', UserGroupRoutePredicate)
675 config.add_route_predicate(
676 config.add_route_predicate(
676 'user_route_with_default', UserRouteWithDefaultPredicate)
677 'user_route_with_default', UserRouteWithDefaultPredicate)
677 config.add_route_predicate(
678 config.add_route_predicate(
678 'user_route', UserRoutePredicate)
679 'user_route', UserRoutePredicate)
@@ -1,54 +1,54 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2018 RhodeCode GmbH
3 # Copyright (C) 2016-2018 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 pytest
21 import pytest
22
22
23
23
24 class TestSSHWrapper(object):
24 class TestSSHWrapper(object):
25
25
26 def test_serve_raises_an_exception_when_vcs_is_not_recognized(self, ssh_wrapper):
26 def test_serve_raises_an_exception_when_vcs_is_not_recognized(self, ssh_wrapper):
27 with pytest.raises(Exception) as exc_info:
27 with pytest.raises(Exception) as exc_info:
28 ssh_wrapper.serve(
28 ssh_wrapper.serve(
29 vcs='microsoft-tfs', repo='test-repo', mode=None, user='test',
29 vcs='microsoft-tfs', repo='test-repo', mode=None, user='test',
30 permissions={}, branch_permissions={})
30 permissions={}, branch_permissions={})
31 assert exc_info.value.message == 'Unrecognised VCS: microsoft-tfs'
31 assert str(exc_info.value) == 'Unrecognised VCS: microsoft-tfs'
32
32
33 def test_parse_config(self, ssh_wrapper):
33 def test_parse_config(self, ssh_wrapper):
34 config = ssh_wrapper.parse_config(ssh_wrapper.ini_path)
34 config = ssh_wrapper.parse_config(ssh_wrapper.ini_path)
35 assert config
35 assert config
36
36
37 def test_get_connection_info(self, ssh_wrapper):
37 def test_get_connection_info(self, ssh_wrapper):
38 conn_info = ssh_wrapper.get_connection_info()
38 conn_info = ssh_wrapper.get_connection_info()
39 assert {'client_ip': '127.0.0.1',
39 assert {'client_ip': '127.0.0.1',
40 'client_port': '22',
40 'client_port': '22',
41 'server_ip': '10.0.0.1',
41 'server_ip': '10.0.0.1',
42 'server_port': '443'} == conn_info
42 'server_port': '443'} == conn_info
43
43
44 @pytest.mark.parametrize('command, vcs', [
44 @pytest.mark.parametrize('command, vcs', [
45 ('xxx', None),
45 ('xxx', None),
46 ('svnserve -t', 'svn'),
46 ('svnserve -t', 'svn'),
47 ('hg -R repo serve --stdio', 'hg'),
47 ('hg -R repo serve --stdio', 'hg'),
48 ('git-receive-pack \'repo.git\'', 'git'),
48 ('git-receive-pack \'repo.git\'', 'git'),
49
49
50 ])
50 ])
51 def test_get_repo_details(self, ssh_wrapper, command, vcs):
51 def test_get_repo_details(self, ssh_wrapper, command, vcs):
52 ssh_wrapper.command = command
52 ssh_wrapper.command = command
53 vcs_type, repo_name, mode = ssh_wrapper.get_repo_details(mode='auto')
53 vcs_type, repo_name, mode = ssh_wrapper.get_repo_details(mode='auto')
54 assert vcs_type == vcs
54 assert vcs_type == vcs
General Comments 0
You need to be logged in to leave comments. Login now