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