##// END OF EJS Templates
repo-api: allow repo admins to get/set settings. Previously it was only super-admins that could do that, and it's wrong.
marcink -
r4474:673400c0 default
parent child Browse files
Show More
@@ -1,453 +1,452 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2014-2020 RhodeCode GmbH
3 # Copyright (C) 2014-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 JSON RPC utils
22 JSON RPC utils
23 """
23 """
24
24
25 import collections
25 import collections
26 import logging
26 import logging
27
27
28 from rhodecode.api.exc import JSONRPCError
28 from rhodecode.api.exc import JSONRPCError
29 from rhodecode.lib.auth import (
29 from rhodecode.lib.auth import (
30 HasPermissionAnyApi, HasRepoPermissionAnyApi, HasRepoGroupPermissionAnyApi)
30 HasPermissionAnyApi, HasRepoPermissionAnyApi, HasRepoGroupPermissionAnyApi)
31 from rhodecode.lib.utils import safe_unicode
31 from rhodecode.lib.utils import safe_unicode
32 from rhodecode.lib.vcs.exceptions import RepositoryError
32 from rhodecode.lib.vcs.exceptions import RepositoryError
33 from rhodecode.lib.view_utils import get_commit_from_ref_name
33 from rhodecode.lib.view_utils import get_commit_from_ref_name
34 from rhodecode.lib.utils2 import str2bool
34 from rhodecode.lib.utils2 import str2bool
35
35
36 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
37
37
38
38
39 class OAttr(object):
39 class OAttr(object):
40 """
40 """
41 Special Option that defines other attribute, and can default to them
41 Special Option that defines other attribute, and can default to them
42
42
43 Example::
43 Example::
44
44
45 def test(apiuser, userid=Optional(OAttr('apiuser')):
45 def test(apiuser, userid=Optional(OAttr('apiuser')):
46 user = Optional.extract(userid, evaluate_locals=local())
46 user = Optional.extract(userid, evaluate_locals=local())
47 #if we pass in userid, we get it, else it will default to apiuser
47 #if we pass in userid, we get it, else it will default to apiuser
48 #attribute
48 #attribute
49 """
49 """
50
50
51 def __init__(self, attr_name):
51 def __init__(self, attr_name):
52 self.attr_name = attr_name
52 self.attr_name = attr_name
53
53
54 def __repr__(self):
54 def __repr__(self):
55 return '<OptionalAttr:%s>' % self.attr_name
55 return '<OptionalAttr:%s>' % self.attr_name
56
56
57 def __call__(self):
57 def __call__(self):
58 return self
58 return self
59
59
60
60
61 class Optional(object):
61 class Optional(object):
62 """
62 """
63 Defines an optional parameter::
63 Defines an optional parameter::
64
64
65 param = param.getval() if isinstance(param, Optional) else param
65 param = param.getval() if isinstance(param, Optional) else param
66 param = param() if isinstance(param, Optional) else param
66 param = param() if isinstance(param, Optional) else param
67
67
68 is equivalent of::
68 is equivalent of::
69
69
70 param = Optional.extract(param)
70 param = Optional.extract(param)
71
71
72 """
72 """
73
73
74 def __init__(self, type_):
74 def __init__(self, type_):
75 self.type_ = type_
75 self.type_ = type_
76
76
77 def __repr__(self):
77 def __repr__(self):
78 return '<Optional:%s>' % self.type_.__repr__()
78 return '<Optional:%s>' % self.type_.__repr__()
79
79
80 def __call__(self):
80 def __call__(self):
81 return self.getval()
81 return self.getval()
82
82
83 def getval(self, evaluate_locals=None):
83 def getval(self, evaluate_locals=None):
84 """
84 """
85 returns value from this Optional instance
85 returns value from this Optional instance
86 """
86 """
87 if isinstance(self.type_, OAttr):
87 if isinstance(self.type_, OAttr):
88 param_name = self.type_.attr_name
88 param_name = self.type_.attr_name
89 if evaluate_locals:
89 if evaluate_locals:
90 return evaluate_locals[param_name]
90 return evaluate_locals[param_name]
91 # use params name
91 # use params name
92 return param_name
92 return param_name
93 return self.type_
93 return self.type_
94
94
95 @classmethod
95 @classmethod
96 def extract(cls, val, evaluate_locals=None, binary=None):
96 def extract(cls, val, evaluate_locals=None, binary=None):
97 """
97 """
98 Extracts value from Optional() instance
98 Extracts value from Optional() instance
99
99
100 :param val:
100 :param val:
101 :return: original value if it's not Optional instance else
101 :return: original value if it's not Optional instance else
102 value of instance
102 value of instance
103 """
103 """
104 if isinstance(val, cls):
104 if isinstance(val, cls):
105 val = val.getval(evaluate_locals)
105 val = val.getval(evaluate_locals)
106
106
107 if binary:
107 if binary:
108 val = str2bool(val)
108 val = str2bool(val)
109
109
110 return val
110 return val
111
111
112
112
113 def parse_args(cli_args, key_prefix=''):
113 def parse_args(cli_args, key_prefix=''):
114 from rhodecode.lib.utils2 import (escape_split)
114 from rhodecode.lib.utils2 import (escape_split)
115 kwargs = collections.defaultdict(dict)
115 kwargs = collections.defaultdict(dict)
116 for el in escape_split(cli_args, ','):
116 for el in escape_split(cli_args, ','):
117 kv = escape_split(el, '=', 1)
117 kv = escape_split(el, '=', 1)
118 if len(kv) == 2:
118 if len(kv) == 2:
119 k, v = kv
119 k, v = kv
120 kwargs[key_prefix + k] = v
120 kwargs[key_prefix + k] = v
121 return kwargs
121 return kwargs
122
122
123
123
124 def get_origin(obj):
124 def get_origin(obj):
125 """
125 """
126 Get origin of permission from object.
126 Get origin of permission from object.
127
127
128 :param obj:
128 :param obj:
129 """
129 """
130 origin = 'permission'
130 origin = 'permission'
131
131
132 if getattr(obj, 'owner_row', '') and getattr(obj, 'admin_row', ''):
132 if getattr(obj, 'owner_row', '') and getattr(obj, 'admin_row', ''):
133 # admin and owner case, maybe we should use dual string ?
133 # admin and owner case, maybe we should use dual string ?
134 origin = 'owner'
134 origin = 'owner'
135 elif getattr(obj, 'owner_row', ''):
135 elif getattr(obj, 'owner_row', ''):
136 origin = 'owner'
136 origin = 'owner'
137 elif getattr(obj, 'admin_row', ''):
137 elif getattr(obj, 'admin_row', ''):
138 origin = 'super-admin'
138 origin = 'super-admin'
139 return origin
139 return origin
140
140
141
141
142 def store_update(updates, attr, name):
142 def store_update(updates, attr, name):
143 """
143 """
144 Stores param in updates dict if it's not instance of Optional
144 Stores param in updates dict if it's not instance of Optional
145 allows easy updates of passed in params
145 allows easy updates of passed in params
146 """
146 """
147 if not isinstance(attr, Optional):
147 if not isinstance(attr, Optional):
148 updates[name] = attr
148 updates[name] = attr
149
149
150
150
151 def has_superadmin_permission(apiuser):
151 def has_superadmin_permission(apiuser):
152 """
152 """
153 Return True if apiuser is admin or return False
153 Return True if apiuser is admin or return False
154
154
155 :param apiuser:
155 :param apiuser:
156 """
156 """
157 if HasPermissionAnyApi('hg.admin')(user=apiuser):
157 if HasPermissionAnyApi('hg.admin')(user=apiuser):
158 return True
158 return True
159 return False
159 return False
160
160
161
161
162 def validate_repo_permissions(apiuser, repoid, repo, perms):
162 def validate_repo_permissions(apiuser, repoid, repo, perms):
163 """
163 """
164 Raise JsonRPCError if apiuser is not authorized or return True
164 Raise JsonRPCError if apiuser is not authorized or return True
165
165
166 :param apiuser:
166 :param apiuser:
167 :param repoid:
167 :param repoid:
168 :param repo:
168 :param repo:
169 :param perms:
169 :param perms:
170 """
170 """
171 if not HasRepoPermissionAnyApi(*perms)(
171 if not HasRepoPermissionAnyApi(*perms)(
172 user=apiuser, repo_name=repo.repo_name):
172 user=apiuser, repo_name=repo.repo_name):
173 raise JSONRPCError(
173 raise JSONRPCError('repository `%s` does not exist' % repoid)
174 'repository `%s` does not exist' % repoid)
175
174
176 return True
175 return True
177
176
178
177
179 def validate_repo_group_permissions(apiuser, repogroupid, repo_group, perms):
178 def validate_repo_group_permissions(apiuser, repogroupid, repo_group, perms):
180 """
179 """
181 Raise JsonRPCError if apiuser is not authorized or return True
180 Raise JsonRPCError if apiuser is not authorized or return True
182
181
183 :param apiuser:
182 :param apiuser:
184 :param repogroupid: just the id of repository group
183 :param repogroupid: just the id of repository group
185 :param repo_group: instance of repo_group
184 :param repo_group: instance of repo_group
186 :param perms:
185 :param perms:
187 """
186 """
188 if not HasRepoGroupPermissionAnyApi(*perms)(
187 if not HasRepoGroupPermissionAnyApi(*perms)(
189 user=apiuser, group_name=repo_group.group_name):
188 user=apiuser, group_name=repo_group.group_name):
190 raise JSONRPCError(
189 raise JSONRPCError(
191 'repository group `%s` does not exist' % repogroupid)
190 'repository group `%s` does not exist' % repogroupid)
192
191
193 return True
192 return True
194
193
195
194
196 def validate_set_owner_permissions(apiuser, owner):
195 def validate_set_owner_permissions(apiuser, owner):
197 if isinstance(owner, Optional):
196 if isinstance(owner, Optional):
198 owner = get_user_or_error(apiuser.user_id)
197 owner = get_user_or_error(apiuser.user_id)
199 else:
198 else:
200 if has_superadmin_permission(apiuser):
199 if has_superadmin_permission(apiuser):
201 owner = get_user_or_error(owner)
200 owner = get_user_or_error(owner)
202 else:
201 else:
203 # forbid setting owner for non-admins
202 # forbid setting owner for non-admins
204 raise JSONRPCError(
203 raise JSONRPCError(
205 'Only RhodeCode super-admin can specify `owner` param')
204 'Only RhodeCode super-admin can specify `owner` param')
206 return owner
205 return owner
207
206
208
207
209 def get_user_or_error(userid):
208 def get_user_or_error(userid):
210 """
209 """
211 Get user by id or name or return JsonRPCError if not found
210 Get user by id or name or return JsonRPCError if not found
212
211
213 :param userid:
212 :param userid:
214 """
213 """
215 from rhodecode.model.user import UserModel
214 from rhodecode.model.user import UserModel
216 user_model = UserModel()
215 user_model = UserModel()
217
216
218 if isinstance(userid, (int, long)):
217 if isinstance(userid, (int, long)):
219 try:
218 try:
220 user = user_model.get_user(userid)
219 user = user_model.get_user(userid)
221 except ValueError:
220 except ValueError:
222 user = None
221 user = None
223 else:
222 else:
224 user = user_model.get_by_username(userid)
223 user = user_model.get_by_username(userid)
225
224
226 if user is None:
225 if user is None:
227 raise JSONRPCError(
226 raise JSONRPCError(
228 'user `%s` does not exist' % (userid,))
227 'user `%s` does not exist' % (userid,))
229 return user
228 return user
230
229
231
230
232 def get_repo_or_error(repoid):
231 def get_repo_or_error(repoid):
233 """
232 """
234 Get repo by id or name or return JsonRPCError if not found
233 Get repo by id or name or return JsonRPCError if not found
235
234
236 :param repoid:
235 :param repoid:
237 """
236 """
238 from rhodecode.model.repo import RepoModel
237 from rhodecode.model.repo import RepoModel
239 repo_model = RepoModel()
238 repo_model = RepoModel()
240
239
241 if isinstance(repoid, (int, long)):
240 if isinstance(repoid, (int, long)):
242 try:
241 try:
243 repo = repo_model.get_repo(repoid)
242 repo = repo_model.get_repo(repoid)
244 except ValueError:
243 except ValueError:
245 repo = None
244 repo = None
246 else:
245 else:
247 repo = repo_model.get_by_repo_name(repoid)
246 repo = repo_model.get_by_repo_name(repoid)
248
247
249 if repo is None:
248 if repo is None:
250 raise JSONRPCError(
249 raise JSONRPCError(
251 'repository `%s` does not exist' % (repoid,))
250 'repository `%s` does not exist' % (repoid,))
252 return repo
251 return repo
253
252
254
253
255 def get_repo_group_or_error(repogroupid):
254 def get_repo_group_or_error(repogroupid):
256 """
255 """
257 Get repo group by id or name or return JsonRPCError if not found
256 Get repo group by id or name or return JsonRPCError if not found
258
257
259 :param repogroupid:
258 :param repogroupid:
260 """
259 """
261 from rhodecode.model.repo_group import RepoGroupModel
260 from rhodecode.model.repo_group import RepoGroupModel
262 repo_group_model = RepoGroupModel()
261 repo_group_model = RepoGroupModel()
263
262
264 if isinstance(repogroupid, (int, long)):
263 if isinstance(repogroupid, (int, long)):
265 try:
264 try:
266 repo_group = repo_group_model._get_repo_group(repogroupid)
265 repo_group = repo_group_model._get_repo_group(repogroupid)
267 except ValueError:
266 except ValueError:
268 repo_group = None
267 repo_group = None
269 else:
268 else:
270 repo_group = repo_group_model.get_by_group_name(repogroupid)
269 repo_group = repo_group_model.get_by_group_name(repogroupid)
271
270
272 if repo_group is None:
271 if repo_group is None:
273 raise JSONRPCError(
272 raise JSONRPCError(
274 'repository group `%s` does not exist' % (repogroupid,))
273 'repository group `%s` does not exist' % (repogroupid,))
275 return repo_group
274 return repo_group
276
275
277
276
278 def get_user_group_or_error(usergroupid):
277 def get_user_group_or_error(usergroupid):
279 """
278 """
280 Get user group by id or name or return JsonRPCError if not found
279 Get user group by id or name or return JsonRPCError if not found
281
280
282 :param usergroupid:
281 :param usergroupid:
283 """
282 """
284 from rhodecode.model.user_group import UserGroupModel
283 from rhodecode.model.user_group import UserGroupModel
285 user_group_model = UserGroupModel()
284 user_group_model = UserGroupModel()
286
285
287 if isinstance(usergroupid, (int, long)):
286 if isinstance(usergroupid, (int, long)):
288 try:
287 try:
289 user_group = user_group_model.get_group(usergroupid)
288 user_group = user_group_model.get_group(usergroupid)
290 except ValueError:
289 except ValueError:
291 user_group = None
290 user_group = None
292 else:
291 else:
293 user_group = user_group_model.get_by_name(usergroupid)
292 user_group = user_group_model.get_by_name(usergroupid)
294
293
295 if user_group is None:
294 if user_group is None:
296 raise JSONRPCError(
295 raise JSONRPCError(
297 'user group `%s` does not exist' % (usergroupid,))
296 'user group `%s` does not exist' % (usergroupid,))
298 return user_group
297 return user_group
299
298
300
299
301 def get_perm_or_error(permid, prefix=None):
300 def get_perm_or_error(permid, prefix=None):
302 """
301 """
303 Get permission by id or name or return JsonRPCError if not found
302 Get permission by id or name or return JsonRPCError if not found
304
303
305 :param permid:
304 :param permid:
306 """
305 """
307 from rhodecode.model.permission import PermissionModel
306 from rhodecode.model.permission import PermissionModel
308
307
309 perm = PermissionModel.cls.get_by_key(permid)
308 perm = PermissionModel.cls.get_by_key(permid)
310 if perm is None:
309 if perm is None:
311 msg = 'permission `{}` does not exist.'.format(permid)
310 msg = 'permission `{}` does not exist.'.format(permid)
312 if prefix:
311 if prefix:
313 msg += ' Permission should start with prefix: `{}`'.format(prefix)
312 msg += ' Permission should start with prefix: `{}`'.format(prefix)
314 raise JSONRPCError(msg)
313 raise JSONRPCError(msg)
315
314
316 if prefix:
315 if prefix:
317 if not perm.permission_name.startswith(prefix):
316 if not perm.permission_name.startswith(prefix):
318 raise JSONRPCError('permission `%s` is invalid, '
317 raise JSONRPCError('permission `%s` is invalid, '
319 'should start with %s' % (permid, prefix))
318 'should start with %s' % (permid, prefix))
320 return perm
319 return perm
321
320
322
321
323 def get_gist_or_error(gistid):
322 def get_gist_or_error(gistid):
324 """
323 """
325 Get gist by id or gist_access_id or return JsonRPCError if not found
324 Get gist by id or gist_access_id or return JsonRPCError if not found
326
325
327 :param gistid:
326 :param gistid:
328 """
327 """
329 from rhodecode.model.gist import GistModel
328 from rhodecode.model.gist import GistModel
330
329
331 gist = GistModel.cls.get_by_access_id(gistid)
330 gist = GistModel.cls.get_by_access_id(gistid)
332 if gist is None:
331 if gist is None:
333 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
332 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
334 return gist
333 return gist
335
334
336
335
337 def get_pull_request_or_error(pullrequestid):
336 def get_pull_request_or_error(pullrequestid):
338 """
337 """
339 Get pull request by id or return JsonRPCError if not found
338 Get pull request by id or return JsonRPCError if not found
340
339
341 :param pullrequestid:
340 :param pullrequestid:
342 """
341 """
343 from rhodecode.model.pull_request import PullRequestModel
342 from rhodecode.model.pull_request import PullRequestModel
344
343
345 try:
344 try:
346 pull_request = PullRequestModel().get(int(pullrequestid))
345 pull_request = PullRequestModel().get(int(pullrequestid))
347 except ValueError:
346 except ValueError:
348 raise JSONRPCError('pullrequestid must be an integer')
347 raise JSONRPCError('pullrequestid must be an integer')
349 if not pull_request:
348 if not pull_request:
350 raise JSONRPCError('pull request `%s` does not exist' % (
349 raise JSONRPCError('pull request `%s` does not exist' % (
351 pullrequestid,))
350 pullrequestid,))
352 return pull_request
351 return pull_request
353
352
354
353
355 def build_commit_data(commit, detail_level):
354 def build_commit_data(commit, detail_level):
356 parsed_diff = []
355 parsed_diff = []
357 if detail_level == 'extended':
356 if detail_level == 'extended':
358 for f_path in commit.added_paths:
357 for f_path in commit.added_paths:
359 parsed_diff.append(_get_commit_dict(filename=f_path, op='A'))
358 parsed_diff.append(_get_commit_dict(filename=f_path, op='A'))
360 for f_path in commit.changed_paths:
359 for f_path in commit.changed_paths:
361 parsed_diff.append(_get_commit_dict(filename=f_path, op='M'))
360 parsed_diff.append(_get_commit_dict(filename=f_path, op='M'))
362 for f_path in commit.removed_paths:
361 for f_path in commit.removed_paths:
363 parsed_diff.append(_get_commit_dict(filename=f_path, op='D'))
362 parsed_diff.append(_get_commit_dict(filename=f_path, op='D'))
364
363
365 elif detail_level == 'full':
364 elif detail_level == 'full':
366 from rhodecode.lib.diffs import DiffProcessor
365 from rhodecode.lib.diffs import DiffProcessor
367 diff_processor = DiffProcessor(commit.diff())
366 diff_processor = DiffProcessor(commit.diff())
368 for dp in diff_processor.prepare():
367 for dp in diff_processor.prepare():
369 del dp['stats']['ops']
368 del dp['stats']['ops']
370 _stats = dp['stats']
369 _stats = dp['stats']
371 parsed_diff.append(_get_commit_dict(
370 parsed_diff.append(_get_commit_dict(
372 filename=dp['filename'], op=dp['operation'],
371 filename=dp['filename'], op=dp['operation'],
373 new_revision=dp['new_revision'],
372 new_revision=dp['new_revision'],
374 old_revision=dp['old_revision'],
373 old_revision=dp['old_revision'],
375 raw_diff=dp['raw_diff'], stats=_stats))
374 raw_diff=dp['raw_diff'], stats=_stats))
376
375
377 return parsed_diff
376 return parsed_diff
378
377
379
378
380 def get_commit_or_error(ref, repo):
379 def get_commit_or_error(ref, repo):
381 try:
380 try:
382 ref_type, _, ref_hash = ref.split(':')
381 ref_type, _, ref_hash = ref.split(':')
383 except ValueError:
382 except ValueError:
384 raise JSONRPCError(
383 raise JSONRPCError(
385 'Ref `{ref}` given in a wrong format. Please check the API'
384 'Ref `{ref}` given in a wrong format. Please check the API'
386 ' documentation for more details'.format(ref=ref))
385 ' documentation for more details'.format(ref=ref))
387 try:
386 try:
388 # TODO: dan: refactor this to use repo.scm_instance().get_commit()
387 # TODO: dan: refactor this to use repo.scm_instance().get_commit()
389 # once get_commit supports ref_types
388 # once get_commit supports ref_types
390 return get_commit_from_ref_name(repo, ref_hash)
389 return get_commit_from_ref_name(repo, ref_hash)
391 except RepositoryError:
390 except RepositoryError:
392 raise JSONRPCError('Ref `{ref}` does not exist'.format(ref=ref))
391 raise JSONRPCError('Ref `{ref}` does not exist'.format(ref=ref))
393
392
394
393
395 def _get_ref_hash(repo, type_, name):
394 def _get_ref_hash(repo, type_, name):
396 vcs_repo = repo.scm_instance()
395 vcs_repo = repo.scm_instance()
397 if type_ in ['branch'] and vcs_repo.alias in ('hg', 'git'):
396 if type_ in ['branch'] and vcs_repo.alias in ('hg', 'git'):
398 return vcs_repo.branches[name]
397 return vcs_repo.branches[name]
399 elif type_ in ['bookmark', 'book'] and vcs_repo.alias == 'hg':
398 elif type_ in ['bookmark', 'book'] and vcs_repo.alias == 'hg':
400 return vcs_repo.bookmarks[name]
399 return vcs_repo.bookmarks[name]
401 else:
400 else:
402 raise ValueError()
401 raise ValueError()
403
402
404
403
405 def resolve_ref_or_error(ref, repo, allowed_ref_types=None):
404 def resolve_ref_or_error(ref, repo, allowed_ref_types=None):
406 allowed_ref_types = allowed_ref_types or ['bookmark', 'book', 'tag', 'branch']
405 allowed_ref_types = allowed_ref_types or ['bookmark', 'book', 'tag', 'branch']
407
406
408 def _parse_ref(type_, name, hash_=None):
407 def _parse_ref(type_, name, hash_=None):
409 return type_, name, hash_
408 return type_, name, hash_
410
409
411 try:
410 try:
412 ref_type, ref_name, ref_hash = _parse_ref(*ref.split(':'))
411 ref_type, ref_name, ref_hash = _parse_ref(*ref.split(':'))
413 except TypeError:
412 except TypeError:
414 raise JSONRPCError(
413 raise JSONRPCError(
415 'Ref `{ref}` given in a wrong format. Please check the API'
414 'Ref `{ref}` given in a wrong format. Please check the API'
416 ' documentation for more details'.format(ref=ref))
415 ' documentation for more details'.format(ref=ref))
417
416
418 if ref_type not in allowed_ref_types:
417 if ref_type not in allowed_ref_types:
419 raise JSONRPCError(
418 raise JSONRPCError(
420 'Ref `{ref}` type is not allowed. '
419 'Ref `{ref}` type is not allowed. '
421 'Only:{allowed_refs} are possible.'.format(
420 'Only:{allowed_refs} are possible.'.format(
422 ref=ref, allowed_refs=allowed_ref_types))
421 ref=ref, allowed_refs=allowed_ref_types))
423
422
424 try:
423 try:
425 ref_hash = ref_hash or _get_ref_hash(repo, ref_type, ref_name)
424 ref_hash = ref_hash or _get_ref_hash(repo, ref_type, ref_name)
426 except (KeyError, ValueError):
425 except (KeyError, ValueError):
427 raise JSONRPCError(
426 raise JSONRPCError(
428 'The specified value:{type}:`{name}` does not exist, or is not allowed.'.format(
427 'The specified value:{type}:`{name}` does not exist, or is not allowed.'.format(
429 type=ref_type, name=ref_name))
428 type=ref_type, name=ref_name))
430
429
431 return ':'.join([ref_type, ref_name, ref_hash])
430 return ':'.join([ref_type, ref_name, ref_hash])
432
431
433
432
434 def _get_commit_dict(
433 def _get_commit_dict(
435 filename, op, new_revision=None, old_revision=None,
434 filename, op, new_revision=None, old_revision=None,
436 raw_diff=None, stats=None):
435 raw_diff=None, stats=None):
437 if stats is None:
436 if stats is None:
438 stats = {
437 stats = {
439 "added": None,
438 "added": None,
440 "binary": None,
439 "binary": None,
441 "deleted": None
440 "deleted": None
442 }
441 }
443 return {
442 return {
444 "filename": safe_unicode(filename),
443 "filename": safe_unicode(filename),
445 "op": op,
444 "op": op,
446
445
447 # extra details
446 # extra details
448 "new_revision": new_revision,
447 "new_revision": new_revision,
449 "old_revision": old_revision,
448 "old_revision": old_revision,
450
449
451 "raw_diff": raw_diff,
450 "raw_diff": raw_diff,
452 "stats": stats
451 "stats": stats
453 }
452 }
@@ -1,2506 +1,2507 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2020 RhodeCode GmbH
3 # Copyright (C) 2011-2020 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, rc_cache
32 from rhodecode.lib import audit_logger, rc_cache
33 from rhodecode.lib import repo_maintenance
33 from rhodecode.lib import repo_maintenance
34 from rhodecode.lib.auth import (
34 from rhodecode.lib.auth import (
35 HasPermissionAnyApi, HasUserGroupPermissionAnyApi,
35 HasPermissionAnyApi, HasUserGroupPermissionAnyApi,
36 HasRepoPermissionAnyApi)
36 HasRepoPermissionAnyApi)
37 from rhodecode.lib.celerylib.utils import get_task_id
37 from rhodecode.lib.celerylib.utils import get_task_id
38 from rhodecode.lib.utils2 import (
38 from rhodecode.lib.utils2 import (
39 str2bool, time_to_datetime, safe_str, safe_int, safe_unicode)
39 str2bool, time_to_datetime, safe_str, safe_int, safe_unicode)
40 from rhodecode.lib.ext_json import json
40 from rhodecode.lib.ext_json import json
41 from rhodecode.lib.exceptions import (
41 from rhodecode.lib.exceptions import (
42 StatusChangeOnClosedPullRequestError, CommentVersionMismatch)
42 StatusChangeOnClosedPullRequestError, CommentVersionMismatch)
43 from rhodecode.lib.vcs import RepositoryError
43 from rhodecode.lib.vcs import RepositoryError
44 from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError
44 from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError
45 from rhodecode.model.changeset_status import ChangesetStatusModel
45 from rhodecode.model.changeset_status import ChangesetStatusModel
46 from rhodecode.model.comment import CommentsModel
46 from rhodecode.model.comment import CommentsModel
47 from rhodecode.model.db import (
47 from rhodecode.model.db import (
48 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup,
48 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup,
49 ChangesetComment)
49 ChangesetComment)
50 from rhodecode.model.permission import PermissionModel
50 from rhodecode.model.permission import PermissionModel
51 from rhodecode.model.pull_request import PullRequestModel
51 from rhodecode.model.pull_request import PullRequestModel
52 from rhodecode.model.repo import RepoModel
52 from rhodecode.model.repo import RepoModel
53 from rhodecode.model.scm import ScmModel, RepoList
53 from rhodecode.model.scm import ScmModel, RepoList
54 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
54 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
55 from rhodecode.model import validation_schema
55 from rhodecode.model import validation_schema
56 from rhodecode.model.validation_schema.schemas import repo_schema
56 from rhodecode.model.validation_schema.schemas import repo_schema
57
57
58 log = logging.getLogger(__name__)
58 log = logging.getLogger(__name__)
59
59
60
60
61 @jsonrpc_method()
61 @jsonrpc_method()
62 def get_repo(request, apiuser, repoid, cache=Optional(True)):
62 def get_repo(request, apiuser, repoid, cache=Optional(True)):
63 """
63 """
64 Gets an existing repository by its name or repository_id.
64 Gets an existing repository by its name or repository_id.
65
65
66 The members section so the output returns users groups or users
66 The members section so the output returns users groups or users
67 associated with that repository.
67 associated with that repository.
68
68
69 This command can only be run using an |authtoken| with admin rights,
69 This command can only be run using an |authtoken| with admin rights,
70 or users with at least read rights to the |repo|.
70 or users with at least read rights to the |repo|.
71
71
72 :param apiuser: This is filled automatically from the |authtoken|.
72 :param apiuser: This is filled automatically from the |authtoken|.
73 :type apiuser: AuthUser
73 :type apiuser: AuthUser
74 :param repoid: The repository name or repository id.
74 :param repoid: The repository name or repository id.
75 :type repoid: str or int
75 :type repoid: str or int
76 :param cache: use the cached value for last changeset
76 :param cache: use the cached value for last changeset
77 :type: cache: Optional(bool)
77 :type: cache: Optional(bool)
78
78
79 Example output:
79 Example output:
80
80
81 .. code-block:: bash
81 .. code-block:: bash
82
82
83 {
83 {
84 "error": null,
84 "error": null,
85 "id": <repo_id>,
85 "id": <repo_id>,
86 "result": {
86 "result": {
87 "clone_uri": null,
87 "clone_uri": null,
88 "created_on": "timestamp",
88 "created_on": "timestamp",
89 "description": "repo description",
89 "description": "repo description",
90 "enable_downloads": false,
90 "enable_downloads": false,
91 "enable_locking": false,
91 "enable_locking": false,
92 "enable_statistics": false,
92 "enable_statistics": false,
93 "followers": [
93 "followers": [
94 {
94 {
95 "active": true,
95 "active": true,
96 "admin": false,
96 "admin": false,
97 "api_key": "****************************************",
97 "api_key": "****************************************",
98 "api_keys": [
98 "api_keys": [
99 "****************************************"
99 "****************************************"
100 ],
100 ],
101 "email": "user@example.com",
101 "email": "user@example.com",
102 "emails": [
102 "emails": [
103 "user@example.com"
103 "user@example.com"
104 ],
104 ],
105 "extern_name": "rhodecode",
105 "extern_name": "rhodecode",
106 "extern_type": "rhodecode",
106 "extern_type": "rhodecode",
107 "firstname": "username",
107 "firstname": "username",
108 "ip_addresses": [],
108 "ip_addresses": [],
109 "language": null,
109 "language": null,
110 "last_login": "2015-09-16T17:16:35.854",
110 "last_login": "2015-09-16T17:16:35.854",
111 "lastname": "surname",
111 "lastname": "surname",
112 "user_id": <user_id>,
112 "user_id": <user_id>,
113 "username": "name"
113 "username": "name"
114 }
114 }
115 ],
115 ],
116 "fork_of": "parent-repo",
116 "fork_of": "parent-repo",
117 "landing_rev": [
117 "landing_rev": [
118 "rev",
118 "rev",
119 "tip"
119 "tip"
120 ],
120 ],
121 "last_changeset": {
121 "last_changeset": {
122 "author": "User <user@example.com>",
122 "author": "User <user@example.com>",
123 "branch": "default",
123 "branch": "default",
124 "date": "timestamp",
124 "date": "timestamp",
125 "message": "last commit message",
125 "message": "last commit message",
126 "parents": [
126 "parents": [
127 {
127 {
128 "raw_id": "commit-id"
128 "raw_id": "commit-id"
129 }
129 }
130 ],
130 ],
131 "raw_id": "commit-id",
131 "raw_id": "commit-id",
132 "revision": <revision number>,
132 "revision": <revision number>,
133 "short_id": "short id"
133 "short_id": "short id"
134 },
134 },
135 "lock_reason": null,
135 "lock_reason": null,
136 "locked_by": null,
136 "locked_by": null,
137 "locked_date": null,
137 "locked_date": null,
138 "owner": "owner-name",
138 "owner": "owner-name",
139 "permissions": [
139 "permissions": [
140 {
140 {
141 "name": "super-admin-name",
141 "name": "super-admin-name",
142 "origin": "super-admin",
142 "origin": "super-admin",
143 "permission": "repository.admin",
143 "permission": "repository.admin",
144 "type": "user"
144 "type": "user"
145 },
145 },
146 {
146 {
147 "name": "owner-name",
147 "name": "owner-name",
148 "origin": "owner",
148 "origin": "owner",
149 "permission": "repository.admin",
149 "permission": "repository.admin",
150 "type": "user"
150 "type": "user"
151 },
151 },
152 {
152 {
153 "name": "user-group-name",
153 "name": "user-group-name",
154 "origin": "permission",
154 "origin": "permission",
155 "permission": "repository.write",
155 "permission": "repository.write",
156 "type": "user_group"
156 "type": "user_group"
157 }
157 }
158 ],
158 ],
159 "private": true,
159 "private": true,
160 "repo_id": 676,
160 "repo_id": 676,
161 "repo_name": "user-group/repo-name",
161 "repo_name": "user-group/repo-name",
162 "repo_type": "hg"
162 "repo_type": "hg"
163 }
163 }
164 }
164 }
165 """
165 """
166
166
167 repo = get_repo_or_error(repoid)
167 repo = get_repo_or_error(repoid)
168 cache = Optional.extract(cache)
168 cache = Optional.extract(cache)
169
169
170 include_secrets = False
170 include_secrets = False
171 if has_superadmin_permission(apiuser):
171 if has_superadmin_permission(apiuser):
172 include_secrets = True
172 include_secrets = True
173 else:
173 else:
174 # check if we have at least read permission for this repo !
174 # check if we have at least read permission for this repo !
175 _perms = (
175 _perms = (
176 'repository.admin', 'repository.write', 'repository.read',)
176 'repository.admin', 'repository.write', 'repository.read',)
177 validate_repo_permissions(apiuser, repoid, repo, _perms)
177 validate_repo_permissions(apiuser, repoid, repo, _perms)
178
178
179 permissions = []
179 permissions = []
180 for _user in repo.permissions():
180 for _user in repo.permissions():
181 user_data = {
181 user_data = {
182 'name': _user.username,
182 'name': _user.username,
183 'permission': _user.permission,
183 'permission': _user.permission,
184 'origin': get_origin(_user),
184 'origin': get_origin(_user),
185 'type': "user",
185 'type': "user",
186 }
186 }
187 permissions.append(user_data)
187 permissions.append(user_data)
188
188
189 for _user_group in repo.permission_user_groups():
189 for _user_group in repo.permission_user_groups():
190 user_group_data = {
190 user_group_data = {
191 'name': _user_group.users_group_name,
191 'name': _user_group.users_group_name,
192 'permission': _user_group.permission,
192 'permission': _user_group.permission,
193 'origin': get_origin(_user_group),
193 'origin': get_origin(_user_group),
194 'type': "user_group",
194 'type': "user_group",
195 }
195 }
196 permissions.append(user_group_data)
196 permissions.append(user_group_data)
197
197
198 following_users = [
198 following_users = [
199 user.user.get_api_data(include_secrets=include_secrets)
199 user.user.get_api_data(include_secrets=include_secrets)
200 for user in repo.followers]
200 for user in repo.followers]
201
201
202 if not cache:
202 if not cache:
203 repo.update_commit_cache()
203 repo.update_commit_cache()
204 data = repo.get_api_data(include_secrets=include_secrets)
204 data = repo.get_api_data(include_secrets=include_secrets)
205 data['permissions'] = permissions
205 data['permissions'] = permissions
206 data['followers'] = following_users
206 data['followers'] = following_users
207 return data
207 return data
208
208
209
209
210 @jsonrpc_method()
210 @jsonrpc_method()
211 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
211 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
212 """
212 """
213 Lists all existing repositories.
213 Lists all existing repositories.
214
214
215 This command can only be run using an |authtoken| with admin rights,
215 This command can only be run using an |authtoken| with admin rights,
216 or users with at least read rights to |repos|.
216 or users with at least read rights to |repos|.
217
217
218 :param apiuser: This is filled automatically from the |authtoken|.
218 :param apiuser: This is filled automatically from the |authtoken|.
219 :type apiuser: AuthUser
219 :type apiuser: AuthUser
220 :param root: specify root repository group to fetch repositories.
220 :param root: specify root repository group to fetch repositories.
221 filters the returned repositories to be members of given root group.
221 filters the returned repositories to be members of given root group.
222 :type root: Optional(None)
222 :type root: Optional(None)
223 :param traverse: traverse given root into subrepositories. With this flag
223 :param traverse: traverse given root into subrepositories. With this flag
224 set to False, it will only return top-level repositories from `root`.
224 set to False, it will only return top-level repositories from `root`.
225 if root is empty it will return just top-level repositories.
225 if root is empty it will return just top-level repositories.
226 :type traverse: Optional(True)
226 :type traverse: Optional(True)
227
227
228
228
229 Example output:
229 Example output:
230
230
231 .. code-block:: bash
231 .. code-block:: bash
232
232
233 id : <id_given_in_input>
233 id : <id_given_in_input>
234 result: [
234 result: [
235 {
235 {
236 "repo_id" : "<repo_id>",
236 "repo_id" : "<repo_id>",
237 "repo_name" : "<reponame>"
237 "repo_name" : "<reponame>"
238 "repo_type" : "<repo_type>",
238 "repo_type" : "<repo_type>",
239 "clone_uri" : "<clone_uri>",
239 "clone_uri" : "<clone_uri>",
240 "private": : "<bool>",
240 "private": : "<bool>",
241 "created_on" : "<datetimecreated>",
241 "created_on" : "<datetimecreated>",
242 "description" : "<description>",
242 "description" : "<description>",
243 "landing_rev": "<landing_rev>",
243 "landing_rev": "<landing_rev>",
244 "owner": "<repo_owner>",
244 "owner": "<repo_owner>",
245 "fork_of": "<name_of_fork_parent>",
245 "fork_of": "<name_of_fork_parent>",
246 "enable_downloads": "<bool>",
246 "enable_downloads": "<bool>",
247 "enable_locking": "<bool>",
247 "enable_locking": "<bool>",
248 "enable_statistics": "<bool>",
248 "enable_statistics": "<bool>",
249 },
249 },
250 ...
250 ...
251 ]
251 ]
252 error: null
252 error: null
253 """
253 """
254
254
255 include_secrets = has_superadmin_permission(apiuser)
255 include_secrets = has_superadmin_permission(apiuser)
256 _perms = ('repository.read', 'repository.write', 'repository.admin',)
256 _perms = ('repository.read', 'repository.write', 'repository.admin',)
257 extras = {'user': apiuser}
257 extras = {'user': apiuser}
258
258
259 root = Optional.extract(root)
259 root = Optional.extract(root)
260 traverse = Optional.extract(traverse, binary=True)
260 traverse = Optional.extract(traverse, binary=True)
261
261
262 if root:
262 if root:
263 # verify parent existance, if it's empty return an error
263 # verify parent existance, if it's empty return an error
264 parent = RepoGroup.get_by_group_name(root)
264 parent = RepoGroup.get_by_group_name(root)
265 if not parent:
265 if not parent:
266 raise JSONRPCError(
266 raise JSONRPCError(
267 'Root repository group `{}` does not exist'.format(root))
267 'Root repository group `{}` does not exist'.format(root))
268
268
269 if traverse:
269 if traverse:
270 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
270 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
271 else:
271 else:
272 repos = RepoModel().get_repos_for_root(root=parent)
272 repos = RepoModel().get_repos_for_root(root=parent)
273 else:
273 else:
274 if traverse:
274 if traverse:
275 repos = RepoModel().get_all()
275 repos = RepoModel().get_all()
276 else:
276 else:
277 # return just top-level
277 # return just top-level
278 repos = RepoModel().get_repos_for_root(root=None)
278 repos = RepoModel().get_repos_for_root(root=None)
279
279
280 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
280 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
281 return [repo.get_api_data(include_secrets=include_secrets)
281 return [repo.get_api_data(include_secrets=include_secrets)
282 for repo in repo_list]
282 for repo in repo_list]
283
283
284
284
285 @jsonrpc_method()
285 @jsonrpc_method()
286 def get_repo_changeset(request, apiuser, repoid, revision,
286 def get_repo_changeset(request, apiuser, repoid, revision,
287 details=Optional('basic')):
287 details=Optional('basic')):
288 """
288 """
289 Returns information about a changeset.
289 Returns information about a changeset.
290
290
291 Additionally parameters define the amount of details returned by
291 Additionally parameters define the amount of details returned by
292 this function.
292 this function.
293
293
294 This command can only be run using an |authtoken| with admin rights,
294 This command can only be run using an |authtoken| with admin rights,
295 or users with at least read rights to the |repo|.
295 or users with at least read rights to the |repo|.
296
296
297 :param apiuser: This is filled automatically from the |authtoken|.
297 :param apiuser: This is filled automatically from the |authtoken|.
298 :type apiuser: AuthUser
298 :type apiuser: AuthUser
299 :param repoid: The repository name or repository id
299 :param repoid: The repository name or repository id
300 :type repoid: str or int
300 :type repoid: str or int
301 :param revision: revision for which listing should be done
301 :param revision: revision for which listing should be done
302 :type revision: str
302 :type revision: str
303 :param details: details can be 'basic|extended|full' full gives diff
303 :param details: details can be 'basic|extended|full' full gives diff
304 info details like the diff itself, and number of changed files etc.
304 info details like the diff itself, and number of changed files etc.
305 :type details: Optional(str)
305 :type details: Optional(str)
306
306
307 """
307 """
308 repo = get_repo_or_error(repoid)
308 repo = get_repo_or_error(repoid)
309 if not has_superadmin_permission(apiuser):
309 if not has_superadmin_permission(apiuser):
310 _perms = (
310 _perms = ('repository.admin', 'repository.write', 'repository.read',)
311 'repository.admin', 'repository.write', 'repository.read',)
312 validate_repo_permissions(apiuser, repoid, repo, _perms)
311 validate_repo_permissions(apiuser, repoid, repo, _perms)
313
312
314 changes_details = Optional.extract(details)
313 changes_details = Optional.extract(details)
315 _changes_details_types = ['basic', 'extended', 'full']
314 _changes_details_types = ['basic', 'extended', 'full']
316 if changes_details not in _changes_details_types:
315 if changes_details not in _changes_details_types:
317 raise JSONRPCError(
316 raise JSONRPCError(
318 'ret_type must be one of %s' % (
317 'ret_type must be one of %s' % (
319 ','.join(_changes_details_types)))
318 ','.join(_changes_details_types)))
320
319
321 pre_load = ['author', 'branch', 'date', 'message', 'parents',
320 pre_load = ['author', 'branch', 'date', 'message', 'parents',
322 'status', '_commit', '_file_paths']
321 'status', '_commit', '_file_paths']
323
322
324 try:
323 try:
325 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
324 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
326 except TypeError as e:
325 except TypeError as e:
327 raise JSONRPCError(safe_str(e))
326 raise JSONRPCError(safe_str(e))
328 _cs_json = cs.__json__()
327 _cs_json = cs.__json__()
329 _cs_json['diff'] = build_commit_data(cs, changes_details)
328 _cs_json['diff'] = build_commit_data(cs, changes_details)
330 if changes_details == 'full':
329 if changes_details == 'full':
331 _cs_json['refs'] = cs._get_refs()
330 _cs_json['refs'] = cs._get_refs()
332 return _cs_json
331 return _cs_json
333
332
334
333
335 @jsonrpc_method()
334 @jsonrpc_method()
336 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
335 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
337 details=Optional('basic')):
336 details=Optional('basic')):
338 """
337 """
339 Returns a set of commits limited by the number starting
338 Returns a set of commits limited by the number starting
340 from the `start_rev` option.
339 from the `start_rev` option.
341
340
342 Additional parameters define the amount of details returned by this
341 Additional parameters define the amount of details returned by this
343 function.
342 function.
344
343
345 This command can only be run using an |authtoken| with admin rights,
344 This command can only be run using an |authtoken| with admin rights,
346 or users with at least read rights to |repos|.
345 or users with at least read rights to |repos|.
347
346
348 :param apiuser: This is filled automatically from the |authtoken|.
347 :param apiuser: This is filled automatically from the |authtoken|.
349 :type apiuser: AuthUser
348 :type apiuser: AuthUser
350 :param repoid: The repository name or repository ID.
349 :param repoid: The repository name or repository ID.
351 :type repoid: str or int
350 :type repoid: str or int
352 :param start_rev: The starting revision from where to get changesets.
351 :param start_rev: The starting revision from where to get changesets.
353 :type start_rev: str
352 :type start_rev: str
354 :param limit: Limit the number of commits to this amount
353 :param limit: Limit the number of commits to this amount
355 :type limit: str or int
354 :type limit: str or int
356 :param details: Set the level of detail returned. Valid option are:
355 :param details: Set the level of detail returned. Valid option are:
357 ``basic``, ``extended`` and ``full``.
356 ``basic``, ``extended`` and ``full``.
358 :type details: Optional(str)
357 :type details: Optional(str)
359
358
360 .. note::
359 .. note::
361
360
362 Setting the parameter `details` to the value ``full`` is extensive
361 Setting the parameter `details` to the value ``full`` is extensive
363 and returns details like the diff itself, and the number
362 and returns details like the diff itself, and the number
364 of changed files.
363 of changed files.
365
364
366 """
365 """
367 repo = get_repo_or_error(repoid)
366 repo = get_repo_or_error(repoid)
368 if not has_superadmin_permission(apiuser):
367 if not has_superadmin_permission(apiuser):
369 _perms = (
368 _perms = ('repository.admin', 'repository.write', 'repository.read',)
370 'repository.admin', 'repository.write', 'repository.read',)
371 validate_repo_permissions(apiuser, repoid, repo, _perms)
369 validate_repo_permissions(apiuser, repoid, repo, _perms)
372
370
373 changes_details = Optional.extract(details)
371 changes_details = Optional.extract(details)
374 _changes_details_types = ['basic', 'extended', 'full']
372 _changes_details_types = ['basic', 'extended', 'full']
375 if changes_details not in _changes_details_types:
373 if changes_details not in _changes_details_types:
376 raise JSONRPCError(
374 raise JSONRPCError(
377 'ret_type must be one of %s' % (
375 'ret_type must be one of %s' % (
378 ','.join(_changes_details_types)))
376 ','.join(_changes_details_types)))
379
377
380 limit = int(limit)
378 limit = int(limit)
381 pre_load = ['author', 'branch', 'date', 'message', 'parents',
379 pre_load = ['author', 'branch', 'date', 'message', 'parents',
382 'status', '_commit', '_file_paths']
380 'status', '_commit', '_file_paths']
383
381
384 vcs_repo = repo.scm_instance()
382 vcs_repo = repo.scm_instance()
385 # SVN needs a special case to distinguish its index and commit id
383 # SVN needs a special case to distinguish its index and commit id
386 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
384 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
387 start_rev = vcs_repo.commit_ids[0]
385 start_rev = vcs_repo.commit_ids[0]
388
386
389 try:
387 try:
390 commits = vcs_repo.get_commits(
388 commits = vcs_repo.get_commits(
391 start_id=start_rev, pre_load=pre_load, translate_tags=False)
389 start_id=start_rev, pre_load=pre_load, translate_tags=False)
392 except TypeError as e:
390 except TypeError as e:
393 raise JSONRPCError(safe_str(e))
391 raise JSONRPCError(safe_str(e))
394 except Exception:
392 except Exception:
395 log.exception('Fetching of commits failed')
393 log.exception('Fetching of commits failed')
396 raise JSONRPCError('Error occurred during commit fetching')
394 raise JSONRPCError('Error occurred during commit fetching')
397
395
398 ret = []
396 ret = []
399 for cnt, commit in enumerate(commits):
397 for cnt, commit in enumerate(commits):
400 if cnt >= limit != -1:
398 if cnt >= limit != -1:
401 break
399 break
402 _cs_json = commit.__json__()
400 _cs_json = commit.__json__()
403 _cs_json['diff'] = build_commit_data(commit, changes_details)
401 _cs_json['diff'] = build_commit_data(commit, changes_details)
404 if changes_details == 'full':
402 if changes_details == 'full':
405 _cs_json['refs'] = {
403 _cs_json['refs'] = {
406 'branches': [commit.branch],
404 'branches': [commit.branch],
407 'bookmarks': getattr(commit, 'bookmarks', []),
405 'bookmarks': getattr(commit, 'bookmarks', []),
408 'tags': commit.tags
406 'tags': commit.tags
409 }
407 }
410 ret.append(_cs_json)
408 ret.append(_cs_json)
411 return ret
409 return ret
412
410
413
411
414 @jsonrpc_method()
412 @jsonrpc_method()
415 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
413 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
416 ret_type=Optional('all'), details=Optional('basic'),
414 ret_type=Optional('all'), details=Optional('basic'),
417 max_file_bytes=Optional(None)):
415 max_file_bytes=Optional(None)):
418 """
416 """
419 Returns a list of nodes and children in a flat list for a given
417 Returns a list of nodes and children in a flat list for a given
420 path at given revision.
418 path at given revision.
421
419
422 It's possible to specify ret_type to show only `files` or `dirs`.
420 It's possible to specify ret_type to show only `files` or `dirs`.
423
421
424 This command can only be run using an |authtoken| with admin rights,
422 This command can only be run using an |authtoken| with admin rights,
425 or users with at least read rights to |repos|.
423 or users with at least read rights to |repos|.
426
424
427 :param apiuser: This is filled automatically from the |authtoken|.
425 :param apiuser: This is filled automatically from the |authtoken|.
428 :type apiuser: AuthUser
426 :type apiuser: AuthUser
429 :param repoid: The repository name or repository ID.
427 :param repoid: The repository name or repository ID.
430 :type repoid: str or int
428 :type repoid: str or int
431 :param revision: The revision for which listing should be done.
429 :param revision: The revision for which listing should be done.
432 :type revision: str
430 :type revision: str
433 :param root_path: The path from which to start displaying.
431 :param root_path: The path from which to start displaying.
434 :type root_path: str
432 :type root_path: str
435 :param ret_type: Set the return type. Valid options are
433 :param ret_type: Set the return type. Valid options are
436 ``all`` (default), ``files`` and ``dirs``.
434 ``all`` (default), ``files`` and ``dirs``.
437 :type ret_type: Optional(str)
435 :type ret_type: Optional(str)
438 :param details: Returns extended information about nodes, such as
436 :param details: Returns extended information about nodes, such as
439 md5, binary, and or content.
437 md5, binary, and or content.
440 The valid options are ``basic`` and ``full``.
438 The valid options are ``basic`` and ``full``.
441 :type details: Optional(str)
439 :type details: Optional(str)
442 :param max_file_bytes: Only return file content under this file size bytes
440 :param max_file_bytes: Only return file content under this file size bytes
443 :type details: Optional(int)
441 :type details: Optional(int)
444
442
445 Example output:
443 Example output:
446
444
447 .. code-block:: bash
445 .. code-block:: bash
448
446
449 id : <id_given_in_input>
447 id : <id_given_in_input>
450 result: [
448 result: [
451 {
449 {
452 "binary": false,
450 "binary": false,
453 "content": "File line",
451 "content": "File line",
454 "extension": "md",
452 "extension": "md",
455 "lines": 2,
453 "lines": 2,
456 "md5": "059fa5d29b19c0657e384749480f6422",
454 "md5": "059fa5d29b19c0657e384749480f6422",
457 "mimetype": "text/x-minidsrc",
455 "mimetype": "text/x-minidsrc",
458 "name": "file.md",
456 "name": "file.md",
459 "size": 580,
457 "size": 580,
460 "type": "file"
458 "type": "file"
461 },
459 },
462 ...
460 ...
463 ]
461 ]
464 error: null
462 error: null
465 """
463 """
466
464
467 repo = get_repo_or_error(repoid)
465 repo = get_repo_or_error(repoid)
468 if not has_superadmin_permission(apiuser):
466 if not has_superadmin_permission(apiuser):
469 _perms = ('repository.admin', 'repository.write', 'repository.read',)
467 _perms = ('repository.admin', 'repository.write', 'repository.read',)
470 validate_repo_permissions(apiuser, repoid, repo, _perms)
468 validate_repo_permissions(apiuser, repoid, repo, _perms)
471
469
472 ret_type = Optional.extract(ret_type)
470 ret_type = Optional.extract(ret_type)
473 details = Optional.extract(details)
471 details = Optional.extract(details)
474 _extended_types = ['basic', 'full']
472 _extended_types = ['basic', 'full']
475 if details not in _extended_types:
473 if details not in _extended_types:
476 raise JSONRPCError('ret_type must be one of %s' % (','.join(_extended_types)))
474 raise JSONRPCError('ret_type must be one of %s' % (','.join(_extended_types)))
477 extended_info = False
475 extended_info = False
478 content = False
476 content = False
479 if details == 'basic':
477 if details == 'basic':
480 extended_info = True
478 extended_info = True
481
479
482 if details == 'full':
480 if details == 'full':
483 extended_info = content = True
481 extended_info = content = True
484
482
485 _map = {}
483 _map = {}
486 try:
484 try:
487 # check if repo is not empty by any chance, skip quicker if it is.
485 # check if repo is not empty by any chance, skip quicker if it is.
488 _scm = repo.scm_instance()
486 _scm = repo.scm_instance()
489 if _scm.is_empty():
487 if _scm.is_empty():
490 return []
488 return []
491
489
492 _d, _f = ScmModel().get_nodes(
490 _d, _f = ScmModel().get_nodes(
493 repo, revision, root_path, flat=False,
491 repo, revision, root_path, flat=False,
494 extended_info=extended_info, content=content,
492 extended_info=extended_info, content=content,
495 max_file_bytes=max_file_bytes)
493 max_file_bytes=max_file_bytes)
496 _map = {
494 _map = {
497 'all': _d + _f,
495 'all': _d + _f,
498 'files': _f,
496 'files': _f,
499 'dirs': _d,
497 'dirs': _d,
500 }
498 }
501 return _map[ret_type]
499 return _map[ret_type]
502 except KeyError:
500 except KeyError:
503 raise JSONRPCError(
501 raise JSONRPCError(
504 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
502 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
505 except Exception:
503 except Exception:
506 log.exception("Exception occurred while trying to get repo nodes")
504 log.exception("Exception occurred while trying to get repo nodes")
507 raise JSONRPCError(
505 raise JSONRPCError(
508 'failed to get repo: `%s` nodes' % repo.repo_name
506 'failed to get repo: `%s` nodes' % repo.repo_name
509 )
507 )
510
508
511
509
512 @jsonrpc_method()
510 @jsonrpc_method()
513 def get_repo_file(request, apiuser, repoid, commit_id, file_path,
511 def get_repo_file(request, apiuser, repoid, commit_id, file_path,
514 max_file_bytes=Optional(None), details=Optional('basic'),
512 max_file_bytes=Optional(None), details=Optional('basic'),
515 cache=Optional(True)):
513 cache=Optional(True)):
516 """
514 """
517 Returns a single file from repository at given revision.
515 Returns a single file from repository at given revision.
518
516
519 This command can only be run using an |authtoken| with admin rights,
517 This command can only be run using an |authtoken| with admin rights,
520 or users with at least read rights to |repos|.
518 or users with at least read rights to |repos|.
521
519
522 :param apiuser: This is filled automatically from the |authtoken|.
520 :param apiuser: This is filled automatically from the |authtoken|.
523 :type apiuser: AuthUser
521 :type apiuser: AuthUser
524 :param repoid: The repository name or repository ID.
522 :param repoid: The repository name or repository ID.
525 :type repoid: str or int
523 :type repoid: str or int
526 :param commit_id: The revision for which listing should be done.
524 :param commit_id: The revision for which listing should be done.
527 :type commit_id: str
525 :type commit_id: str
528 :param file_path: The path from which to start displaying.
526 :param file_path: The path from which to start displaying.
529 :type file_path: str
527 :type file_path: str
530 :param details: Returns different set of information about nodes.
528 :param details: Returns different set of information about nodes.
531 The valid options are ``minimal`` ``basic`` and ``full``.
529 The valid options are ``minimal`` ``basic`` and ``full``.
532 :type details: Optional(str)
530 :type details: Optional(str)
533 :param max_file_bytes: Only return file content under this file size bytes
531 :param max_file_bytes: Only return file content under this file size bytes
534 :type max_file_bytes: Optional(int)
532 :type max_file_bytes: Optional(int)
535 :param cache: Use internal caches for fetching files. If disabled fetching
533 :param cache: Use internal caches for fetching files. If disabled fetching
536 files is slower but more memory efficient
534 files is slower but more memory efficient
537 :type cache: Optional(bool)
535 :type cache: Optional(bool)
538
536
539 Example output:
537 Example output:
540
538
541 .. code-block:: bash
539 .. code-block:: bash
542
540
543 id : <id_given_in_input>
541 id : <id_given_in_input>
544 result: {
542 result: {
545 "binary": false,
543 "binary": false,
546 "extension": "py",
544 "extension": "py",
547 "lines": 35,
545 "lines": 35,
548 "content": "....",
546 "content": "....",
549 "md5": "76318336366b0f17ee249e11b0c99c41",
547 "md5": "76318336366b0f17ee249e11b0c99c41",
550 "mimetype": "text/x-python",
548 "mimetype": "text/x-python",
551 "name": "python.py",
549 "name": "python.py",
552 "size": 817,
550 "size": 817,
553 "type": "file",
551 "type": "file",
554 }
552 }
555 error: null
553 error: null
556 """
554 """
557
555
558 repo = get_repo_or_error(repoid)
556 repo = get_repo_or_error(repoid)
559 if not has_superadmin_permission(apiuser):
557 if not has_superadmin_permission(apiuser):
560 _perms = ('repository.admin', 'repository.write', 'repository.read',)
558 _perms = ('repository.admin', 'repository.write', 'repository.read',)
561 validate_repo_permissions(apiuser, repoid, repo, _perms)
559 validate_repo_permissions(apiuser, repoid, repo, _perms)
562
560
563 cache = Optional.extract(cache, binary=True)
561 cache = Optional.extract(cache, binary=True)
564 details = Optional.extract(details)
562 details = Optional.extract(details)
565 _extended_types = ['minimal', 'minimal+search', 'basic', 'full']
563 _extended_types = ['minimal', 'minimal+search', 'basic', 'full']
566 if details not in _extended_types:
564 if details not in _extended_types:
567 raise JSONRPCError(
565 raise JSONRPCError(
568 'ret_type must be one of %s, got %s' % (','.join(_extended_types)), details)
566 'ret_type must be one of %s, got %s' % (','.join(_extended_types)), details)
569 extended_info = False
567 extended_info = False
570 content = False
568 content = False
571
569
572 if details == 'minimal':
570 if details == 'minimal':
573 extended_info = False
571 extended_info = False
574
572
575 elif details == 'basic':
573 elif details == 'basic':
576 extended_info = True
574 extended_info = True
577
575
578 elif details == 'full':
576 elif details == 'full':
579 extended_info = content = True
577 extended_info = content = True
580
578
581 file_path = safe_unicode(file_path)
579 file_path = safe_unicode(file_path)
582 try:
580 try:
583 # check if repo is not empty by any chance, skip quicker if it is.
581 # check if repo is not empty by any chance, skip quicker if it is.
584 _scm = repo.scm_instance()
582 _scm = repo.scm_instance()
585 if _scm.is_empty():
583 if _scm.is_empty():
586 return None
584 return None
587
585
588 node = ScmModel().get_node(
586 node = ScmModel().get_node(
589 repo, commit_id, file_path, extended_info=extended_info,
587 repo, commit_id, file_path, extended_info=extended_info,
590 content=content, max_file_bytes=max_file_bytes, cache=cache)
588 content=content, max_file_bytes=max_file_bytes, cache=cache)
591 except NodeDoesNotExistError:
589 except NodeDoesNotExistError:
592 raise JSONRPCError(u'There is no file in repo: `{}` at path `{}` for commit: `{}`'.format(
590 raise JSONRPCError(u'There is no file in repo: `{}` at path `{}` for commit: `{}`'.format(
593 repo.repo_name, file_path, commit_id))
591 repo.repo_name, file_path, commit_id))
594 except Exception:
592 except Exception:
595 log.exception(u"Exception occurred while trying to get repo %s file",
593 log.exception(u"Exception occurred while trying to get repo %s file",
596 repo.repo_name)
594 repo.repo_name)
597 raise JSONRPCError(u'failed to get repo: `{}` file at path {}'.format(
595 raise JSONRPCError(u'failed to get repo: `{}` file at path {}'.format(
598 repo.repo_name, file_path))
596 repo.repo_name, file_path))
599
597
600 return node
598 return node
601
599
602
600
603 @jsonrpc_method()
601 @jsonrpc_method()
604 def get_repo_fts_tree(request, apiuser, repoid, commit_id, root_path):
602 def get_repo_fts_tree(request, apiuser, repoid, commit_id, root_path):
605 """
603 """
606 Returns a list of tree nodes for path at given revision. This api is built
604 Returns a list of tree nodes for path at given revision. This api is built
607 strictly for usage in full text search building, and shouldn't be consumed
605 strictly for usage in full text search building, and shouldn't be consumed
608
606
609 This command can only be run using an |authtoken| with admin rights,
607 This command can only be run using an |authtoken| with admin rights,
610 or users with at least read rights to |repos|.
608 or users with at least read rights to |repos|.
611
609
612 """
610 """
613
611
614 repo = get_repo_or_error(repoid)
612 repo = get_repo_or_error(repoid)
615 if not has_superadmin_permission(apiuser):
613 if not has_superadmin_permission(apiuser):
616 _perms = ('repository.admin', 'repository.write', 'repository.read',)
614 _perms = ('repository.admin', 'repository.write', 'repository.read',)
617 validate_repo_permissions(apiuser, repoid, repo, _perms)
615 validate_repo_permissions(apiuser, repoid, repo, _perms)
618
616
619 repo_id = repo.repo_id
617 repo_id = repo.repo_id
620 cache_seconds = safe_int(rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
618 cache_seconds = safe_int(rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
621 cache_on = cache_seconds > 0
619 cache_on = cache_seconds > 0
622
620
623 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
621 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
624 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
622 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
625
623
626 def compute_fts_tree(cache_ver, repo_id, commit_id, root_path):
624 def compute_fts_tree(cache_ver, repo_id, commit_id, root_path):
627 return ScmModel().get_fts_data(repo_id, commit_id, root_path)
625 return ScmModel().get_fts_data(repo_id, commit_id, root_path)
628
626
629 try:
627 try:
630 # check if repo is not empty by any chance, skip quicker if it is.
628 # check if repo is not empty by any chance, skip quicker if it is.
631 _scm = repo.scm_instance()
629 _scm = repo.scm_instance()
632 if _scm.is_empty():
630 if _scm.is_empty():
633 return []
631 return []
634 except RepositoryError:
632 except RepositoryError:
635 log.exception("Exception occurred while trying to get repo nodes")
633 log.exception("Exception occurred while trying to get repo nodes")
636 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
634 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
637
635
638 try:
636 try:
639 # we need to resolve commit_id to a FULL sha for cache to work correctly.
637 # we need to resolve commit_id to a FULL sha for cache to work correctly.
640 # sending 'master' is a pointer that needs to be translated to current commit.
638 # sending 'master' is a pointer that needs to be translated to current commit.
641 commit_id = _scm.get_commit(commit_id=commit_id).raw_id
639 commit_id = _scm.get_commit(commit_id=commit_id).raw_id
642 log.debug(
640 log.debug(
643 'Computing FTS REPO TREE for repo_id %s commit_id `%s` '
641 'Computing FTS REPO TREE for repo_id %s commit_id `%s` '
644 'with caching: %s[TTL: %ss]' % (
642 'with caching: %s[TTL: %ss]' % (
645 repo_id, commit_id, cache_on, cache_seconds or 0))
643 repo_id, commit_id, cache_on, cache_seconds or 0))
646
644
647 tree_files = compute_fts_tree(rc_cache.FILE_TREE_CACHE_VER, repo_id, commit_id, root_path)
645 tree_files = compute_fts_tree(rc_cache.FILE_TREE_CACHE_VER, repo_id, commit_id, root_path)
648 return tree_files
646 return tree_files
649
647
650 except Exception:
648 except Exception:
651 log.exception("Exception occurred while trying to get repo nodes")
649 log.exception("Exception occurred while trying to get repo nodes")
652 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
650 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
653
651
654
652
655 @jsonrpc_method()
653 @jsonrpc_method()
656 def get_repo_refs(request, apiuser, repoid):
654 def get_repo_refs(request, apiuser, repoid):
657 """
655 """
658 Returns a dictionary of current references. It returns
656 Returns a dictionary of current references. It returns
659 bookmarks, branches, closed_branches, and tags for given repository
657 bookmarks, branches, closed_branches, and tags for given repository
660
658
661 It's possible to specify ret_type to show only `files` or `dirs`.
659 It's possible to specify ret_type to show only `files` or `dirs`.
662
660
663 This command can only be run using an |authtoken| with admin rights,
661 This command can only be run using an |authtoken| with admin rights,
664 or users with at least read rights to |repos|.
662 or users with at least read rights to |repos|.
665
663
666 :param apiuser: This is filled automatically from the |authtoken|.
664 :param apiuser: This is filled automatically from the |authtoken|.
667 :type apiuser: AuthUser
665 :type apiuser: AuthUser
668 :param repoid: The repository name or repository ID.
666 :param repoid: The repository name or repository ID.
669 :type repoid: str or int
667 :type repoid: str or int
670
668
671 Example output:
669 Example output:
672
670
673 .. code-block:: bash
671 .. code-block:: bash
674
672
675 id : <id_given_in_input>
673 id : <id_given_in_input>
676 "result": {
674 "result": {
677 "bookmarks": {
675 "bookmarks": {
678 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
676 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
679 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
677 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
680 },
678 },
681 "branches": {
679 "branches": {
682 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
680 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
683 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
681 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
684 },
682 },
685 "branches_closed": {},
683 "branches_closed": {},
686 "tags": {
684 "tags": {
687 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
685 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
688 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
686 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
689 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
687 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
690 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
688 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
691 }
689 }
692 }
690 }
693 error: null
691 error: null
694 """
692 """
695
693
696 repo = get_repo_or_error(repoid)
694 repo = get_repo_or_error(repoid)
697 if not has_superadmin_permission(apiuser):
695 if not has_superadmin_permission(apiuser):
698 _perms = ('repository.admin', 'repository.write', 'repository.read',)
696 _perms = ('repository.admin', 'repository.write', 'repository.read',)
699 validate_repo_permissions(apiuser, repoid, repo, _perms)
697 validate_repo_permissions(apiuser, repoid, repo, _perms)
700
698
701 try:
699 try:
702 # check if repo is not empty by any chance, skip quicker if it is.
700 # check if repo is not empty by any chance, skip quicker if it is.
703 vcs_instance = repo.scm_instance()
701 vcs_instance = repo.scm_instance()
704 refs = vcs_instance.refs()
702 refs = vcs_instance.refs()
705 return refs
703 return refs
706 except Exception:
704 except Exception:
707 log.exception("Exception occurred while trying to get repo refs")
705 log.exception("Exception occurred while trying to get repo refs")
708 raise JSONRPCError(
706 raise JSONRPCError(
709 'failed to get repo: `%s` references' % repo.repo_name
707 'failed to get repo: `%s` references' % repo.repo_name
710 )
708 )
711
709
712
710
713 @jsonrpc_method()
711 @jsonrpc_method()
714 def create_repo(
712 def create_repo(
715 request, apiuser, repo_name, repo_type,
713 request, apiuser, repo_name, repo_type,
716 owner=Optional(OAttr('apiuser')),
714 owner=Optional(OAttr('apiuser')),
717 description=Optional(''),
715 description=Optional(''),
718 private=Optional(False),
716 private=Optional(False),
719 clone_uri=Optional(None),
717 clone_uri=Optional(None),
720 push_uri=Optional(None),
718 push_uri=Optional(None),
721 landing_rev=Optional(None),
719 landing_rev=Optional(None),
722 enable_statistics=Optional(False),
720 enable_statistics=Optional(False),
723 enable_locking=Optional(False),
721 enable_locking=Optional(False),
724 enable_downloads=Optional(False),
722 enable_downloads=Optional(False),
725 copy_permissions=Optional(False)):
723 copy_permissions=Optional(False)):
726 """
724 """
727 Creates a repository.
725 Creates a repository.
728
726
729 * If the repository name contains "/", repository will be created inside
727 * If the repository name contains "/", repository will be created inside
730 a repository group or nested repository groups
728 a repository group or nested repository groups
731
729
732 For example "foo/bar/repo1" will create |repo| called "repo1" inside
730 For example "foo/bar/repo1" will create |repo| called "repo1" inside
733 group "foo/bar". You have to have permissions to access and write to
731 group "foo/bar". You have to have permissions to access and write to
734 the last repository group ("bar" in this example)
732 the last repository group ("bar" in this example)
735
733
736 This command can only be run using an |authtoken| with at least
734 This command can only be run using an |authtoken| with at least
737 permissions to create repositories, or write permissions to
735 permissions to create repositories, or write permissions to
738 parent repository groups.
736 parent repository groups.
739
737
740 :param apiuser: This is filled automatically from the |authtoken|.
738 :param apiuser: This is filled automatically from the |authtoken|.
741 :type apiuser: AuthUser
739 :type apiuser: AuthUser
742 :param repo_name: Set the repository name.
740 :param repo_name: Set the repository name.
743 :type repo_name: str
741 :type repo_name: str
744 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
742 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
745 :type repo_type: str
743 :type repo_type: str
746 :param owner: user_id or username
744 :param owner: user_id or username
747 :type owner: Optional(str)
745 :type owner: Optional(str)
748 :param description: Set the repository description.
746 :param description: Set the repository description.
749 :type description: Optional(str)
747 :type description: Optional(str)
750 :param private: set repository as private
748 :param private: set repository as private
751 :type private: bool
749 :type private: bool
752 :param clone_uri: set clone_uri
750 :param clone_uri: set clone_uri
753 :type clone_uri: str
751 :type clone_uri: str
754 :param push_uri: set push_uri
752 :param push_uri: set push_uri
755 :type push_uri: str
753 :type push_uri: str
756 :param landing_rev: <rev_type>:<rev>, e.g branch:default, book:dev, rev:abcd
754 :param landing_rev: <rev_type>:<rev>, e.g branch:default, book:dev, rev:abcd
757 :type landing_rev: str
755 :type landing_rev: str
758 :param enable_locking:
756 :param enable_locking:
759 :type enable_locking: bool
757 :type enable_locking: bool
760 :param enable_downloads:
758 :param enable_downloads:
761 :type enable_downloads: bool
759 :type enable_downloads: bool
762 :param enable_statistics:
760 :param enable_statistics:
763 :type enable_statistics: bool
761 :type enable_statistics: bool
764 :param copy_permissions: Copy permission from group in which the
762 :param copy_permissions: Copy permission from group in which the
765 repository is being created.
763 repository is being created.
766 :type copy_permissions: bool
764 :type copy_permissions: bool
767
765
768
766
769 Example output:
767 Example output:
770
768
771 .. code-block:: bash
769 .. code-block:: bash
772
770
773 id : <id_given_in_input>
771 id : <id_given_in_input>
774 result: {
772 result: {
775 "msg": "Created new repository `<reponame>`",
773 "msg": "Created new repository `<reponame>`",
776 "success": true,
774 "success": true,
777 "task": "<celery task id or None if done sync>"
775 "task": "<celery task id or None if done sync>"
778 }
776 }
779 error: null
777 error: null
780
778
781
779
782 Example error output:
780 Example error output:
783
781
784 .. code-block:: bash
782 .. code-block:: bash
785
783
786 id : <id_given_in_input>
784 id : <id_given_in_input>
787 result : null
785 result : null
788 error : {
786 error : {
789 'failed to create repository `<repo_name>`'
787 'failed to create repository `<repo_name>`'
790 }
788 }
791
789
792 """
790 """
793
791
794 owner = validate_set_owner_permissions(apiuser, owner)
792 owner = validate_set_owner_permissions(apiuser, owner)
795
793
796 description = Optional.extract(description)
794 description = Optional.extract(description)
797 copy_permissions = Optional.extract(copy_permissions)
795 copy_permissions = Optional.extract(copy_permissions)
798 clone_uri = Optional.extract(clone_uri)
796 clone_uri = Optional.extract(clone_uri)
799 push_uri = Optional.extract(push_uri)
797 push_uri = Optional.extract(push_uri)
800
798
801 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
799 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
802 if isinstance(private, Optional):
800 if isinstance(private, Optional):
803 private = defs.get('repo_private') or Optional.extract(private)
801 private = defs.get('repo_private') or Optional.extract(private)
804 if isinstance(repo_type, Optional):
802 if isinstance(repo_type, Optional):
805 repo_type = defs.get('repo_type')
803 repo_type = defs.get('repo_type')
806 if isinstance(enable_statistics, Optional):
804 if isinstance(enable_statistics, Optional):
807 enable_statistics = defs.get('repo_enable_statistics')
805 enable_statistics = defs.get('repo_enable_statistics')
808 if isinstance(enable_locking, Optional):
806 if isinstance(enable_locking, Optional):
809 enable_locking = defs.get('repo_enable_locking')
807 enable_locking = defs.get('repo_enable_locking')
810 if isinstance(enable_downloads, Optional):
808 if isinstance(enable_downloads, Optional):
811 enable_downloads = defs.get('repo_enable_downloads')
809 enable_downloads = defs.get('repo_enable_downloads')
812
810
813 landing_ref, _label = ScmModel.backend_landing_ref(repo_type)
811 landing_ref, _label = ScmModel.backend_landing_ref(repo_type)
814 ref_choices, _labels = ScmModel().get_repo_landing_revs(request.translate)
812 ref_choices, _labels = ScmModel().get_repo_landing_revs(request.translate)
815 ref_choices = list(set(ref_choices + [landing_ref]))
813 ref_choices = list(set(ref_choices + [landing_ref]))
816
814
817 landing_commit_ref = Optional.extract(landing_rev) or landing_ref
815 landing_commit_ref = Optional.extract(landing_rev) or landing_ref
818
816
819 schema = repo_schema.RepoSchema().bind(
817 schema = repo_schema.RepoSchema().bind(
820 repo_type_options=rhodecode.BACKENDS.keys(),
818 repo_type_options=rhodecode.BACKENDS.keys(),
821 repo_ref_options=ref_choices,
819 repo_ref_options=ref_choices,
822 repo_type=repo_type,
820 repo_type=repo_type,
823 # user caller
821 # user caller
824 user=apiuser)
822 user=apiuser)
825
823
826 try:
824 try:
827 schema_data = schema.deserialize(dict(
825 schema_data = schema.deserialize(dict(
828 repo_name=repo_name,
826 repo_name=repo_name,
829 repo_type=repo_type,
827 repo_type=repo_type,
830 repo_owner=owner.username,
828 repo_owner=owner.username,
831 repo_description=description,
829 repo_description=description,
832 repo_landing_commit_ref=landing_commit_ref,
830 repo_landing_commit_ref=landing_commit_ref,
833 repo_clone_uri=clone_uri,
831 repo_clone_uri=clone_uri,
834 repo_push_uri=push_uri,
832 repo_push_uri=push_uri,
835 repo_private=private,
833 repo_private=private,
836 repo_copy_permissions=copy_permissions,
834 repo_copy_permissions=copy_permissions,
837 repo_enable_statistics=enable_statistics,
835 repo_enable_statistics=enable_statistics,
838 repo_enable_downloads=enable_downloads,
836 repo_enable_downloads=enable_downloads,
839 repo_enable_locking=enable_locking))
837 repo_enable_locking=enable_locking))
840 except validation_schema.Invalid as err:
838 except validation_schema.Invalid as err:
841 raise JSONRPCValidationError(colander_exc=err)
839 raise JSONRPCValidationError(colander_exc=err)
842
840
843 try:
841 try:
844 data = {
842 data = {
845 'owner': owner,
843 'owner': owner,
846 'repo_name': schema_data['repo_group']['repo_name_without_group'],
844 'repo_name': schema_data['repo_group']['repo_name_without_group'],
847 'repo_name_full': schema_data['repo_name'],
845 'repo_name_full': schema_data['repo_name'],
848 'repo_group': schema_data['repo_group']['repo_group_id'],
846 'repo_group': schema_data['repo_group']['repo_group_id'],
849 'repo_type': schema_data['repo_type'],
847 'repo_type': schema_data['repo_type'],
850 'repo_description': schema_data['repo_description'],
848 'repo_description': schema_data['repo_description'],
851 'repo_private': schema_data['repo_private'],
849 'repo_private': schema_data['repo_private'],
852 'clone_uri': schema_data['repo_clone_uri'],
850 'clone_uri': schema_data['repo_clone_uri'],
853 'push_uri': schema_data['repo_push_uri'],
851 'push_uri': schema_data['repo_push_uri'],
854 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
852 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
855 'enable_statistics': schema_data['repo_enable_statistics'],
853 'enable_statistics': schema_data['repo_enable_statistics'],
856 'enable_locking': schema_data['repo_enable_locking'],
854 'enable_locking': schema_data['repo_enable_locking'],
857 'enable_downloads': schema_data['repo_enable_downloads'],
855 'enable_downloads': schema_data['repo_enable_downloads'],
858 'repo_copy_permissions': schema_data['repo_copy_permissions'],
856 'repo_copy_permissions': schema_data['repo_copy_permissions'],
859 }
857 }
860
858
861 task = RepoModel().create(form_data=data, cur_user=owner.user_id)
859 task = RepoModel().create(form_data=data, cur_user=owner.user_id)
862 task_id = get_task_id(task)
860 task_id = get_task_id(task)
863 # no commit, it's done in RepoModel, or async via celery
861 # no commit, it's done in RepoModel, or async via celery
864 return {
862 return {
865 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
863 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
866 'success': True, # cannot return the repo data here since fork
864 'success': True, # cannot return the repo data here since fork
867 # can be done async
865 # can be done async
868 'task': task_id
866 'task': task_id
869 }
867 }
870 except Exception:
868 except Exception:
871 log.exception(
869 log.exception(
872 u"Exception while trying to create the repository %s",
870 u"Exception while trying to create the repository %s",
873 schema_data['repo_name'])
871 schema_data['repo_name'])
874 raise JSONRPCError(
872 raise JSONRPCError(
875 'failed to create repository `%s`' % (schema_data['repo_name'],))
873 'failed to create repository `%s`' % (schema_data['repo_name'],))
876
874
877
875
878 @jsonrpc_method()
876 @jsonrpc_method()
879 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
877 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
880 description=Optional('')):
878 description=Optional('')):
881 """
879 """
882 Adds an extra field to a repository.
880 Adds an extra field to a repository.
883
881
884 This command can only be run using an |authtoken| with at least
882 This command can only be run using an |authtoken| with at least
885 write permissions to the |repo|.
883 write permissions to the |repo|.
886
884
887 :param apiuser: This is filled automatically from the |authtoken|.
885 :param apiuser: This is filled automatically from the |authtoken|.
888 :type apiuser: AuthUser
886 :type apiuser: AuthUser
889 :param repoid: Set the repository name or repository id.
887 :param repoid: Set the repository name or repository id.
890 :type repoid: str or int
888 :type repoid: str or int
891 :param key: Create a unique field key for this repository.
889 :param key: Create a unique field key for this repository.
892 :type key: str
890 :type key: str
893 :param label:
891 :param label:
894 :type label: Optional(str)
892 :type label: Optional(str)
895 :param description:
893 :param description:
896 :type description: Optional(str)
894 :type description: Optional(str)
897 """
895 """
898 repo = get_repo_or_error(repoid)
896 repo = get_repo_or_error(repoid)
899 if not has_superadmin_permission(apiuser):
897 if not has_superadmin_permission(apiuser):
900 _perms = ('repository.admin',)
898 _perms = ('repository.admin',)
901 validate_repo_permissions(apiuser, repoid, repo, _perms)
899 validate_repo_permissions(apiuser, repoid, repo, _perms)
902
900
903 label = Optional.extract(label) or key
901 label = Optional.extract(label) or key
904 description = Optional.extract(description)
902 description = Optional.extract(description)
905
903
906 field = RepositoryField.get_by_key_name(key, repo)
904 field = RepositoryField.get_by_key_name(key, repo)
907 if field:
905 if field:
908 raise JSONRPCError('Field with key '
906 raise JSONRPCError('Field with key '
909 '`%s` exists for repo `%s`' % (key, repoid))
907 '`%s` exists for repo `%s`' % (key, repoid))
910
908
911 try:
909 try:
912 RepoModel().add_repo_field(repo, key, field_label=label,
910 RepoModel().add_repo_field(repo, key, field_label=label,
913 field_desc=description)
911 field_desc=description)
914 Session().commit()
912 Session().commit()
915 return {
913 return {
916 'msg': "Added new repository field `%s`" % (key,),
914 'msg': "Added new repository field `%s`" % (key,),
917 'success': True,
915 'success': True,
918 }
916 }
919 except Exception:
917 except Exception:
920 log.exception("Exception occurred while trying to add field to repo")
918 log.exception("Exception occurred while trying to add field to repo")
921 raise JSONRPCError(
919 raise JSONRPCError(
922 'failed to create new field for repository `%s`' % (repoid,))
920 'failed to create new field for repository `%s`' % (repoid,))
923
921
924
922
925 @jsonrpc_method()
923 @jsonrpc_method()
926 def remove_field_from_repo(request, apiuser, repoid, key):
924 def remove_field_from_repo(request, apiuser, repoid, key):
927 """
925 """
928 Removes an extra field from a repository.
926 Removes an extra field from a repository.
929
927
930 This command can only be run using an |authtoken| with at least
928 This command can only be run using an |authtoken| with at least
931 write permissions to the |repo|.
929 write permissions to the |repo|.
932
930
933 :param apiuser: This is filled automatically from the |authtoken|.
931 :param apiuser: This is filled automatically from the |authtoken|.
934 :type apiuser: AuthUser
932 :type apiuser: AuthUser
935 :param repoid: Set the repository name or repository ID.
933 :param repoid: Set the repository name or repository ID.
936 :type repoid: str or int
934 :type repoid: str or int
937 :param key: Set the unique field key for this repository.
935 :param key: Set the unique field key for this repository.
938 :type key: str
936 :type key: str
939 """
937 """
940
938
941 repo = get_repo_or_error(repoid)
939 repo = get_repo_or_error(repoid)
942 if not has_superadmin_permission(apiuser):
940 if not has_superadmin_permission(apiuser):
943 _perms = ('repository.admin',)
941 _perms = ('repository.admin',)
944 validate_repo_permissions(apiuser, repoid, repo, _perms)
942 validate_repo_permissions(apiuser, repoid, repo, _perms)
945
943
946 field = RepositoryField.get_by_key_name(key, repo)
944 field = RepositoryField.get_by_key_name(key, repo)
947 if not field:
945 if not field:
948 raise JSONRPCError('Field with key `%s` does not '
946 raise JSONRPCError('Field with key `%s` does not '
949 'exists for repo `%s`' % (key, repoid))
947 'exists for repo `%s`' % (key, repoid))
950
948
951 try:
949 try:
952 RepoModel().delete_repo_field(repo, field_key=key)
950 RepoModel().delete_repo_field(repo, field_key=key)
953 Session().commit()
951 Session().commit()
954 return {
952 return {
955 'msg': "Deleted repository field `%s`" % (key,),
953 'msg': "Deleted repository field `%s`" % (key,),
956 'success': True,
954 'success': True,
957 }
955 }
958 except Exception:
956 except Exception:
959 log.exception(
957 log.exception(
960 "Exception occurred while trying to delete field from repo")
958 "Exception occurred while trying to delete field from repo")
961 raise JSONRPCError(
959 raise JSONRPCError(
962 'failed to delete field for repository `%s`' % (repoid,))
960 'failed to delete field for repository `%s`' % (repoid,))
963
961
964
962
965 @jsonrpc_method()
963 @jsonrpc_method()
966 def update_repo(
964 def update_repo(
967 request, apiuser, repoid, repo_name=Optional(None),
965 request, apiuser, repoid, repo_name=Optional(None),
968 owner=Optional(OAttr('apiuser')), description=Optional(''),
966 owner=Optional(OAttr('apiuser')), description=Optional(''),
969 private=Optional(False),
967 private=Optional(False),
970 clone_uri=Optional(None), push_uri=Optional(None),
968 clone_uri=Optional(None), push_uri=Optional(None),
971 landing_rev=Optional(None), fork_of=Optional(None),
969 landing_rev=Optional(None), fork_of=Optional(None),
972 enable_statistics=Optional(False),
970 enable_statistics=Optional(False),
973 enable_locking=Optional(False),
971 enable_locking=Optional(False),
974 enable_downloads=Optional(False), fields=Optional('')):
972 enable_downloads=Optional(False), fields=Optional('')):
975 """
973 """
976 Updates a repository with the given information.
974 Updates a repository with the given information.
977
975
978 This command can only be run using an |authtoken| with at least
976 This command can only be run using an |authtoken| with at least
979 admin permissions to the |repo|.
977 admin permissions to the |repo|.
980
978
981 * If the repository name contains "/", repository will be updated
979 * If the repository name contains "/", repository will be updated
982 accordingly with a repository group or nested repository groups
980 accordingly with a repository group or nested repository groups
983
981
984 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
982 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
985 called "repo-test" and place it inside group "foo/bar".
983 called "repo-test" and place it inside group "foo/bar".
986 You have to have permissions to access and write to the last repository
984 You have to have permissions to access and write to the last repository
987 group ("bar" in this example)
985 group ("bar" in this example)
988
986
989 :param apiuser: This is filled automatically from the |authtoken|.
987 :param apiuser: This is filled automatically from the |authtoken|.
990 :type apiuser: AuthUser
988 :type apiuser: AuthUser
991 :param repoid: repository name or repository ID.
989 :param repoid: repository name or repository ID.
992 :type repoid: str or int
990 :type repoid: str or int
993 :param repo_name: Update the |repo| name, including the
991 :param repo_name: Update the |repo| name, including the
994 repository group it's in.
992 repository group it's in.
995 :type repo_name: str
993 :type repo_name: str
996 :param owner: Set the |repo| owner.
994 :param owner: Set the |repo| owner.
997 :type owner: str
995 :type owner: str
998 :param fork_of: Set the |repo| as fork of another |repo|.
996 :param fork_of: Set the |repo| as fork of another |repo|.
999 :type fork_of: str
997 :type fork_of: str
1000 :param description: Update the |repo| description.
998 :param description: Update the |repo| description.
1001 :type description: str
999 :type description: str
1002 :param private: Set the |repo| as private. (True | False)
1000 :param private: Set the |repo| as private. (True | False)
1003 :type private: bool
1001 :type private: bool
1004 :param clone_uri: Update the |repo| clone URI.
1002 :param clone_uri: Update the |repo| clone URI.
1005 :type clone_uri: str
1003 :type clone_uri: str
1006 :param landing_rev: Set the |repo| landing revision. e.g branch:default, book:dev, rev:abcd
1004 :param landing_rev: Set the |repo| landing revision. e.g branch:default, book:dev, rev:abcd
1007 :type landing_rev: str
1005 :type landing_rev: str
1008 :param enable_statistics: Enable statistics on the |repo|, (True | False).
1006 :param enable_statistics: Enable statistics on the |repo|, (True | False).
1009 :type enable_statistics: bool
1007 :type enable_statistics: bool
1010 :param enable_locking: Enable |repo| locking.
1008 :param enable_locking: Enable |repo| locking.
1011 :type enable_locking: bool
1009 :type enable_locking: bool
1012 :param enable_downloads: Enable downloads from the |repo|, (True | False).
1010 :param enable_downloads: Enable downloads from the |repo|, (True | False).
1013 :type enable_downloads: bool
1011 :type enable_downloads: bool
1014 :param fields: Add extra fields to the |repo|. Use the following
1012 :param fields: Add extra fields to the |repo|. Use the following
1015 example format: ``field_key=field_val,field_key2=fieldval2``.
1013 example format: ``field_key=field_val,field_key2=fieldval2``.
1016 Escape ', ' with \,
1014 Escape ', ' with \,
1017 :type fields: str
1015 :type fields: str
1018 """
1016 """
1019
1017
1020 repo = get_repo_or_error(repoid)
1018 repo = get_repo_or_error(repoid)
1021
1019
1022 include_secrets = False
1020 include_secrets = False
1023 if not has_superadmin_permission(apiuser):
1021 if not has_superadmin_permission(apiuser):
1024 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
1022 _perms = ('repository.admin',)
1023 validate_repo_permissions(apiuser, repoid, repo, _perms)
1025 else:
1024 else:
1026 include_secrets = True
1025 include_secrets = True
1027
1026
1028 updates = dict(
1027 updates = dict(
1029 repo_name=repo_name
1028 repo_name=repo_name
1030 if not isinstance(repo_name, Optional) else repo.repo_name,
1029 if not isinstance(repo_name, Optional) else repo.repo_name,
1031
1030
1032 fork_id=fork_of
1031 fork_id=fork_of
1033 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
1032 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
1034
1033
1035 user=owner
1034 user=owner
1036 if not isinstance(owner, Optional) else repo.user.username,
1035 if not isinstance(owner, Optional) else repo.user.username,
1037
1036
1038 repo_description=description
1037 repo_description=description
1039 if not isinstance(description, Optional) else repo.description,
1038 if not isinstance(description, Optional) else repo.description,
1040
1039
1041 repo_private=private
1040 repo_private=private
1042 if not isinstance(private, Optional) else repo.private,
1041 if not isinstance(private, Optional) else repo.private,
1043
1042
1044 clone_uri=clone_uri
1043 clone_uri=clone_uri
1045 if not isinstance(clone_uri, Optional) else repo.clone_uri,
1044 if not isinstance(clone_uri, Optional) else repo.clone_uri,
1046
1045
1047 push_uri=push_uri
1046 push_uri=push_uri
1048 if not isinstance(push_uri, Optional) else repo.push_uri,
1047 if not isinstance(push_uri, Optional) else repo.push_uri,
1049
1048
1050 repo_landing_rev=landing_rev
1049 repo_landing_rev=landing_rev
1051 if not isinstance(landing_rev, Optional) else repo._landing_revision,
1050 if not isinstance(landing_rev, Optional) else repo._landing_revision,
1052
1051
1053 repo_enable_statistics=enable_statistics
1052 repo_enable_statistics=enable_statistics
1054 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
1053 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
1055
1054
1056 repo_enable_locking=enable_locking
1055 repo_enable_locking=enable_locking
1057 if not isinstance(enable_locking, Optional) else repo.enable_locking,
1056 if not isinstance(enable_locking, Optional) else repo.enable_locking,
1058
1057
1059 repo_enable_downloads=enable_downloads
1058 repo_enable_downloads=enable_downloads
1060 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
1059 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
1061
1060
1062 landing_ref, _label = ScmModel.backend_landing_ref(repo.repo_type)
1061 landing_ref, _label = ScmModel.backend_landing_ref(repo.repo_type)
1063 ref_choices, _labels = ScmModel().get_repo_landing_revs(
1062 ref_choices, _labels = ScmModel().get_repo_landing_revs(
1064 request.translate, repo=repo)
1063 request.translate, repo=repo)
1065 ref_choices = list(set(ref_choices + [landing_ref]))
1064 ref_choices = list(set(ref_choices + [landing_ref]))
1066
1065
1067 old_values = repo.get_api_data()
1066 old_values = repo.get_api_data()
1068 repo_type = repo.repo_type
1067 repo_type = repo.repo_type
1069 schema = repo_schema.RepoSchema().bind(
1068 schema = repo_schema.RepoSchema().bind(
1070 repo_type_options=rhodecode.BACKENDS.keys(),
1069 repo_type_options=rhodecode.BACKENDS.keys(),
1071 repo_ref_options=ref_choices,
1070 repo_ref_options=ref_choices,
1072 repo_type=repo_type,
1071 repo_type=repo_type,
1073 # user caller
1072 # user caller
1074 user=apiuser,
1073 user=apiuser,
1075 old_values=old_values)
1074 old_values=old_values)
1076 try:
1075 try:
1077 schema_data = schema.deserialize(dict(
1076 schema_data = schema.deserialize(dict(
1078 # we save old value, users cannot change type
1077 # we save old value, users cannot change type
1079 repo_type=repo_type,
1078 repo_type=repo_type,
1080
1079
1081 repo_name=updates['repo_name'],
1080 repo_name=updates['repo_name'],
1082 repo_owner=updates['user'],
1081 repo_owner=updates['user'],
1083 repo_description=updates['repo_description'],
1082 repo_description=updates['repo_description'],
1084 repo_clone_uri=updates['clone_uri'],
1083 repo_clone_uri=updates['clone_uri'],
1085 repo_push_uri=updates['push_uri'],
1084 repo_push_uri=updates['push_uri'],
1086 repo_fork_of=updates['fork_id'],
1085 repo_fork_of=updates['fork_id'],
1087 repo_private=updates['repo_private'],
1086 repo_private=updates['repo_private'],
1088 repo_landing_commit_ref=updates['repo_landing_rev'],
1087 repo_landing_commit_ref=updates['repo_landing_rev'],
1089 repo_enable_statistics=updates['repo_enable_statistics'],
1088 repo_enable_statistics=updates['repo_enable_statistics'],
1090 repo_enable_downloads=updates['repo_enable_downloads'],
1089 repo_enable_downloads=updates['repo_enable_downloads'],
1091 repo_enable_locking=updates['repo_enable_locking']))
1090 repo_enable_locking=updates['repo_enable_locking']))
1092 except validation_schema.Invalid as err:
1091 except validation_schema.Invalid as err:
1093 raise JSONRPCValidationError(colander_exc=err)
1092 raise JSONRPCValidationError(colander_exc=err)
1094
1093
1095 # save validated data back into the updates dict
1094 # save validated data back into the updates dict
1096 validated_updates = dict(
1095 validated_updates = dict(
1097 repo_name=schema_data['repo_group']['repo_name_without_group'],
1096 repo_name=schema_data['repo_group']['repo_name_without_group'],
1098 repo_group=schema_data['repo_group']['repo_group_id'],
1097 repo_group=schema_data['repo_group']['repo_group_id'],
1099
1098
1100 user=schema_data['repo_owner'],
1099 user=schema_data['repo_owner'],
1101 repo_description=schema_data['repo_description'],
1100 repo_description=schema_data['repo_description'],
1102 repo_private=schema_data['repo_private'],
1101 repo_private=schema_data['repo_private'],
1103 clone_uri=schema_data['repo_clone_uri'],
1102 clone_uri=schema_data['repo_clone_uri'],
1104 push_uri=schema_data['repo_push_uri'],
1103 push_uri=schema_data['repo_push_uri'],
1105 repo_landing_rev=schema_data['repo_landing_commit_ref'],
1104 repo_landing_rev=schema_data['repo_landing_commit_ref'],
1106 repo_enable_statistics=schema_data['repo_enable_statistics'],
1105 repo_enable_statistics=schema_data['repo_enable_statistics'],
1107 repo_enable_locking=schema_data['repo_enable_locking'],
1106 repo_enable_locking=schema_data['repo_enable_locking'],
1108 repo_enable_downloads=schema_data['repo_enable_downloads'],
1107 repo_enable_downloads=schema_data['repo_enable_downloads'],
1109 )
1108 )
1110
1109
1111 if schema_data['repo_fork_of']:
1110 if schema_data['repo_fork_of']:
1112 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
1111 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
1113 validated_updates['fork_id'] = fork_repo.repo_id
1112 validated_updates['fork_id'] = fork_repo.repo_id
1114
1113
1115 # extra fields
1114 # extra fields
1116 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
1115 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
1117 if fields:
1116 if fields:
1118 validated_updates.update(fields)
1117 validated_updates.update(fields)
1119
1118
1120 try:
1119 try:
1121 RepoModel().update(repo, **validated_updates)
1120 RepoModel().update(repo, **validated_updates)
1122 audit_logger.store_api(
1121 audit_logger.store_api(
1123 'repo.edit', action_data={'old_data': old_values},
1122 'repo.edit', action_data={'old_data': old_values},
1124 user=apiuser, repo=repo)
1123 user=apiuser, repo=repo)
1125 Session().commit()
1124 Session().commit()
1126 return {
1125 return {
1127 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
1126 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
1128 'repository': repo.get_api_data(include_secrets=include_secrets)
1127 'repository': repo.get_api_data(include_secrets=include_secrets)
1129 }
1128 }
1130 except Exception:
1129 except Exception:
1131 log.exception(
1130 log.exception(
1132 u"Exception while trying to update the repository %s",
1131 u"Exception while trying to update the repository %s",
1133 repoid)
1132 repoid)
1134 raise JSONRPCError('failed to update repo `%s`' % repoid)
1133 raise JSONRPCError('failed to update repo `%s`' % repoid)
1135
1134
1136
1135
1137 @jsonrpc_method()
1136 @jsonrpc_method()
1138 def fork_repo(request, apiuser, repoid, fork_name,
1137 def fork_repo(request, apiuser, repoid, fork_name,
1139 owner=Optional(OAttr('apiuser')),
1138 owner=Optional(OAttr('apiuser')),
1140 description=Optional(''),
1139 description=Optional(''),
1141 private=Optional(False),
1140 private=Optional(False),
1142 clone_uri=Optional(None),
1141 clone_uri=Optional(None),
1143 landing_rev=Optional(None),
1142 landing_rev=Optional(None),
1144 copy_permissions=Optional(False)):
1143 copy_permissions=Optional(False)):
1145 """
1144 """
1146 Creates a fork of the specified |repo|.
1145 Creates a fork of the specified |repo|.
1147
1146
1148 * If the fork_name contains "/", fork will be created inside
1147 * If the fork_name contains "/", fork will be created inside
1149 a repository group or nested repository groups
1148 a repository group or nested repository groups
1150
1149
1151 For example "foo/bar/fork-repo" will create fork called "fork-repo"
1150 For example "foo/bar/fork-repo" will create fork called "fork-repo"
1152 inside group "foo/bar". You have to have permissions to access and
1151 inside group "foo/bar". You have to have permissions to access and
1153 write to the last repository group ("bar" in this example)
1152 write to the last repository group ("bar" in this example)
1154
1153
1155 This command can only be run using an |authtoken| with minimum
1154 This command can only be run using an |authtoken| with minimum
1156 read permissions of the forked repo, create fork permissions for an user.
1155 read permissions of the forked repo, create fork permissions for an user.
1157
1156
1158 :param apiuser: This is filled automatically from the |authtoken|.
1157 :param apiuser: This is filled automatically from the |authtoken|.
1159 :type apiuser: AuthUser
1158 :type apiuser: AuthUser
1160 :param repoid: Set repository name or repository ID.
1159 :param repoid: Set repository name or repository ID.
1161 :type repoid: str or int
1160 :type repoid: str or int
1162 :param fork_name: Set the fork name, including it's repository group membership.
1161 :param fork_name: Set the fork name, including it's repository group membership.
1163 :type fork_name: str
1162 :type fork_name: str
1164 :param owner: Set the fork owner.
1163 :param owner: Set the fork owner.
1165 :type owner: str
1164 :type owner: str
1166 :param description: Set the fork description.
1165 :param description: Set the fork description.
1167 :type description: str
1166 :type description: str
1168 :param copy_permissions: Copy permissions from parent |repo|. The
1167 :param copy_permissions: Copy permissions from parent |repo|. The
1169 default is False.
1168 default is False.
1170 :type copy_permissions: bool
1169 :type copy_permissions: bool
1171 :param private: Make the fork private. The default is False.
1170 :param private: Make the fork private. The default is False.
1172 :type private: bool
1171 :type private: bool
1173 :param landing_rev: Set the landing revision. E.g branch:default, book:dev, rev:abcd
1172 :param landing_rev: Set the landing revision. E.g branch:default, book:dev, rev:abcd
1174
1173
1175 Example output:
1174 Example output:
1176
1175
1177 .. code-block:: bash
1176 .. code-block:: bash
1178
1177
1179 id : <id_for_response>
1178 id : <id_for_response>
1180 api_key : "<api_key>"
1179 api_key : "<api_key>"
1181 args: {
1180 args: {
1182 "repoid" : "<reponame or repo_id>",
1181 "repoid" : "<reponame or repo_id>",
1183 "fork_name": "<forkname>",
1182 "fork_name": "<forkname>",
1184 "owner": "<username or user_id = Optional(=apiuser)>",
1183 "owner": "<username or user_id = Optional(=apiuser)>",
1185 "description": "<description>",
1184 "description": "<description>",
1186 "copy_permissions": "<bool>",
1185 "copy_permissions": "<bool>",
1187 "private": "<bool>",
1186 "private": "<bool>",
1188 "landing_rev": "<landing_rev>"
1187 "landing_rev": "<landing_rev>"
1189 }
1188 }
1190
1189
1191 Example error output:
1190 Example error output:
1192
1191
1193 .. code-block:: bash
1192 .. code-block:: bash
1194
1193
1195 id : <id_given_in_input>
1194 id : <id_given_in_input>
1196 result: {
1195 result: {
1197 "msg": "Created fork of `<reponame>` as `<forkname>`",
1196 "msg": "Created fork of `<reponame>` as `<forkname>`",
1198 "success": true,
1197 "success": true,
1199 "task": "<celery task id or None if done sync>"
1198 "task": "<celery task id or None if done sync>"
1200 }
1199 }
1201 error: null
1200 error: null
1202
1201
1203 """
1202 """
1204
1203
1205 repo = get_repo_or_error(repoid)
1204 repo = get_repo_or_error(repoid)
1206 repo_name = repo.repo_name
1205 repo_name = repo.repo_name
1207
1206
1208 if not has_superadmin_permission(apiuser):
1207 if not has_superadmin_permission(apiuser):
1209 # check if we have at least read permission for
1208 # check if we have at least read permission for
1210 # this repo that we fork !
1209 # this repo that we fork !
1211 _perms = (
1210 _perms = ('repository.admin', 'repository.write', 'repository.read')
1212 'repository.admin', 'repository.write', 'repository.read')
1213 validate_repo_permissions(apiuser, repoid, repo, _perms)
1211 validate_repo_permissions(apiuser, repoid, repo, _perms)
1214
1212
1215 # check if the regular user has at least fork permissions as well
1213 # check if the regular user has at least fork permissions as well
1216 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1214 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1217 raise JSONRPCForbidden()
1215 raise JSONRPCForbidden()
1218
1216
1219 # check if user can set owner parameter
1217 # check if user can set owner parameter
1220 owner = validate_set_owner_permissions(apiuser, owner)
1218 owner = validate_set_owner_permissions(apiuser, owner)
1221
1219
1222 description = Optional.extract(description)
1220 description = Optional.extract(description)
1223 copy_permissions = Optional.extract(copy_permissions)
1221 copy_permissions = Optional.extract(copy_permissions)
1224 clone_uri = Optional.extract(clone_uri)
1222 clone_uri = Optional.extract(clone_uri)
1225
1223
1226 landing_ref, _label = ScmModel.backend_landing_ref(repo.repo_type)
1224 landing_ref, _label = ScmModel.backend_landing_ref(repo.repo_type)
1227 ref_choices, _labels = ScmModel().get_repo_landing_revs(request.translate)
1225 ref_choices, _labels = ScmModel().get_repo_landing_revs(request.translate)
1228 ref_choices = list(set(ref_choices + [landing_ref]))
1226 ref_choices = list(set(ref_choices + [landing_ref]))
1229 landing_commit_ref = Optional.extract(landing_rev) or landing_ref
1227 landing_commit_ref = Optional.extract(landing_rev) or landing_ref
1230
1228
1231 private = Optional.extract(private)
1229 private = Optional.extract(private)
1232
1230
1233 schema = repo_schema.RepoSchema().bind(
1231 schema = repo_schema.RepoSchema().bind(
1234 repo_type_options=rhodecode.BACKENDS.keys(),
1232 repo_type_options=rhodecode.BACKENDS.keys(),
1235 repo_ref_options=ref_choices,
1233 repo_ref_options=ref_choices,
1236 repo_type=repo.repo_type,
1234 repo_type=repo.repo_type,
1237 # user caller
1235 # user caller
1238 user=apiuser)
1236 user=apiuser)
1239
1237
1240 try:
1238 try:
1241 schema_data = schema.deserialize(dict(
1239 schema_data = schema.deserialize(dict(
1242 repo_name=fork_name,
1240 repo_name=fork_name,
1243 repo_type=repo.repo_type,
1241 repo_type=repo.repo_type,
1244 repo_owner=owner.username,
1242 repo_owner=owner.username,
1245 repo_description=description,
1243 repo_description=description,
1246 repo_landing_commit_ref=landing_commit_ref,
1244 repo_landing_commit_ref=landing_commit_ref,
1247 repo_clone_uri=clone_uri,
1245 repo_clone_uri=clone_uri,
1248 repo_private=private,
1246 repo_private=private,
1249 repo_copy_permissions=copy_permissions))
1247 repo_copy_permissions=copy_permissions))
1250 except validation_schema.Invalid as err:
1248 except validation_schema.Invalid as err:
1251 raise JSONRPCValidationError(colander_exc=err)
1249 raise JSONRPCValidationError(colander_exc=err)
1252
1250
1253 try:
1251 try:
1254 data = {
1252 data = {
1255 'fork_parent_id': repo.repo_id,
1253 'fork_parent_id': repo.repo_id,
1256
1254
1257 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1255 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1258 'repo_name_full': schema_data['repo_name'],
1256 'repo_name_full': schema_data['repo_name'],
1259 'repo_group': schema_data['repo_group']['repo_group_id'],
1257 'repo_group': schema_data['repo_group']['repo_group_id'],
1260 'repo_type': schema_data['repo_type'],
1258 'repo_type': schema_data['repo_type'],
1261 'description': schema_data['repo_description'],
1259 'description': schema_data['repo_description'],
1262 'private': schema_data['repo_private'],
1260 'private': schema_data['repo_private'],
1263 'copy_permissions': schema_data['repo_copy_permissions'],
1261 'copy_permissions': schema_data['repo_copy_permissions'],
1264 'landing_rev': schema_data['repo_landing_commit_ref'],
1262 'landing_rev': schema_data['repo_landing_commit_ref'],
1265 }
1263 }
1266
1264
1267 task = RepoModel().create_fork(data, cur_user=owner.user_id)
1265 task = RepoModel().create_fork(data, cur_user=owner.user_id)
1268 # no commit, it's done in RepoModel, or async via celery
1266 # no commit, it's done in RepoModel, or async via celery
1269 task_id = get_task_id(task)
1267 task_id = get_task_id(task)
1270
1268
1271 return {
1269 return {
1272 'msg': 'Created fork of `%s` as `%s`' % (
1270 'msg': 'Created fork of `%s` as `%s`' % (
1273 repo.repo_name, schema_data['repo_name']),
1271 repo.repo_name, schema_data['repo_name']),
1274 'success': True, # cannot return the repo data here since fork
1272 'success': True, # cannot return the repo data here since fork
1275 # can be done async
1273 # can be done async
1276 'task': task_id
1274 'task': task_id
1277 }
1275 }
1278 except Exception:
1276 except Exception:
1279 log.exception(
1277 log.exception(
1280 u"Exception while trying to create fork %s",
1278 u"Exception while trying to create fork %s",
1281 schema_data['repo_name'])
1279 schema_data['repo_name'])
1282 raise JSONRPCError(
1280 raise JSONRPCError(
1283 'failed to fork repository `%s` as `%s`' % (
1281 'failed to fork repository `%s` as `%s`' % (
1284 repo_name, schema_data['repo_name']))
1282 repo_name, schema_data['repo_name']))
1285
1283
1286
1284
1287 @jsonrpc_method()
1285 @jsonrpc_method()
1288 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1286 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1289 """
1287 """
1290 Deletes a repository.
1288 Deletes a repository.
1291
1289
1292 * When the `forks` parameter is set it's possible to detach or delete
1290 * When the `forks` parameter is set it's possible to detach or delete
1293 forks of deleted repository.
1291 forks of deleted repository.
1294
1292
1295 This command can only be run using an |authtoken| with admin
1293 This command can only be run using an |authtoken| with admin
1296 permissions on the |repo|.
1294 permissions on the |repo|.
1297
1295
1298 :param apiuser: This is filled automatically from the |authtoken|.
1296 :param apiuser: This is filled automatically from the |authtoken|.
1299 :type apiuser: AuthUser
1297 :type apiuser: AuthUser
1300 :param repoid: Set the repository name or repository ID.
1298 :param repoid: Set the repository name or repository ID.
1301 :type repoid: str or int
1299 :type repoid: str or int
1302 :param forks: Set to `detach` or `delete` forks from the |repo|.
1300 :param forks: Set to `detach` or `delete` forks from the |repo|.
1303 :type forks: Optional(str)
1301 :type forks: Optional(str)
1304
1302
1305 Example error output:
1303 Example error output:
1306
1304
1307 .. code-block:: bash
1305 .. code-block:: bash
1308
1306
1309 id : <id_given_in_input>
1307 id : <id_given_in_input>
1310 result: {
1308 result: {
1311 "msg": "Deleted repository `<reponame>`",
1309 "msg": "Deleted repository `<reponame>`",
1312 "success": true
1310 "success": true
1313 }
1311 }
1314 error: null
1312 error: null
1315 """
1313 """
1316
1314
1317 repo = get_repo_or_error(repoid)
1315 repo = get_repo_or_error(repoid)
1318 repo_name = repo.repo_name
1316 repo_name = repo.repo_name
1319 if not has_superadmin_permission(apiuser):
1317 if not has_superadmin_permission(apiuser):
1320 _perms = ('repository.admin',)
1318 _perms = ('repository.admin',)
1321 validate_repo_permissions(apiuser, repoid, repo, _perms)
1319 validate_repo_permissions(apiuser, repoid, repo, _perms)
1322
1320
1323 try:
1321 try:
1324 handle_forks = Optional.extract(forks)
1322 handle_forks = Optional.extract(forks)
1325 _forks_msg = ''
1323 _forks_msg = ''
1326 _forks = [f for f in repo.forks]
1324 _forks = [f for f in repo.forks]
1327 if handle_forks == 'detach':
1325 if handle_forks == 'detach':
1328 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1326 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1329 elif handle_forks == 'delete':
1327 elif handle_forks == 'delete':
1330 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1328 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1331 elif _forks:
1329 elif _forks:
1332 raise JSONRPCError(
1330 raise JSONRPCError(
1333 'Cannot delete `%s` it still contains attached forks' %
1331 'Cannot delete `%s` it still contains attached forks' %
1334 (repo.repo_name,)
1332 (repo.repo_name,)
1335 )
1333 )
1336 old_data = repo.get_api_data()
1334 old_data = repo.get_api_data()
1337 RepoModel().delete(repo, forks=forks)
1335 RepoModel().delete(repo, forks=forks)
1338
1336
1339 repo = audit_logger.RepoWrap(repo_id=None,
1337 repo = audit_logger.RepoWrap(repo_id=None,
1340 repo_name=repo.repo_name)
1338 repo_name=repo.repo_name)
1341
1339
1342 audit_logger.store_api(
1340 audit_logger.store_api(
1343 'repo.delete', action_data={'old_data': old_data},
1341 'repo.delete', action_data={'old_data': old_data},
1344 user=apiuser, repo=repo)
1342 user=apiuser, repo=repo)
1345
1343
1346 ScmModel().mark_for_invalidation(repo_name, delete=True)
1344 ScmModel().mark_for_invalidation(repo_name, delete=True)
1347 Session().commit()
1345 Session().commit()
1348 return {
1346 return {
1349 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg),
1347 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg),
1350 'success': True
1348 'success': True
1351 }
1349 }
1352 except Exception:
1350 except Exception:
1353 log.exception("Exception occurred while trying to delete repo")
1351 log.exception("Exception occurred while trying to delete repo")
1354 raise JSONRPCError(
1352 raise JSONRPCError(
1355 'failed to delete repository `%s`' % (repo_name,)
1353 'failed to delete repository `%s`' % (repo_name,)
1356 )
1354 )
1357
1355
1358
1356
1359 #TODO: marcink, change name ?
1357 #TODO: marcink, change name ?
1360 @jsonrpc_method()
1358 @jsonrpc_method()
1361 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1359 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1362 """
1360 """
1363 Invalidates the cache for the specified repository.
1361 Invalidates the cache for the specified repository.
1364
1362
1365 This command can only be run using an |authtoken| with admin rights to
1363 This command can only be run using an |authtoken| with admin rights to
1366 the specified repository.
1364 the specified repository.
1367
1365
1368 This command takes the following options:
1366 This command takes the following options:
1369
1367
1370 :param apiuser: This is filled automatically from |authtoken|.
1368 :param apiuser: This is filled automatically from |authtoken|.
1371 :type apiuser: AuthUser
1369 :type apiuser: AuthUser
1372 :param repoid: Sets the repository name or repository ID.
1370 :param repoid: Sets the repository name or repository ID.
1373 :type repoid: str or int
1371 :type repoid: str or int
1374 :param delete_keys: This deletes the invalidated keys instead of
1372 :param delete_keys: This deletes the invalidated keys instead of
1375 just flagging them.
1373 just flagging them.
1376 :type delete_keys: Optional(``True`` | ``False``)
1374 :type delete_keys: Optional(``True`` | ``False``)
1377
1375
1378 Example output:
1376 Example output:
1379
1377
1380 .. code-block:: bash
1378 .. code-block:: bash
1381
1379
1382 id : <id_given_in_input>
1380 id : <id_given_in_input>
1383 result : {
1381 result : {
1384 'msg': Cache for repository `<repository name>` was invalidated,
1382 'msg': Cache for repository `<repository name>` was invalidated,
1385 'repository': <repository name>
1383 'repository': <repository name>
1386 }
1384 }
1387 error : null
1385 error : null
1388
1386
1389 Example error output:
1387 Example error output:
1390
1388
1391 .. code-block:: bash
1389 .. code-block:: bash
1392
1390
1393 id : <id_given_in_input>
1391 id : <id_given_in_input>
1394 result : null
1392 result : null
1395 error : {
1393 error : {
1396 'Error occurred during cache invalidation action'
1394 'Error occurred during cache invalidation action'
1397 }
1395 }
1398
1396
1399 """
1397 """
1400
1398
1401 repo = get_repo_or_error(repoid)
1399 repo = get_repo_or_error(repoid)
1402 if not has_superadmin_permission(apiuser):
1400 if not has_superadmin_permission(apiuser):
1403 _perms = ('repository.admin', 'repository.write',)
1401 _perms = ('repository.admin', 'repository.write',)
1404 validate_repo_permissions(apiuser, repoid, repo, _perms)
1402 validate_repo_permissions(apiuser, repoid, repo, _perms)
1405
1403
1406 delete = Optional.extract(delete_keys)
1404 delete = Optional.extract(delete_keys)
1407 try:
1405 try:
1408 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1406 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1409 return {
1407 return {
1410 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1408 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1411 'repository': repo.repo_name
1409 'repository': repo.repo_name
1412 }
1410 }
1413 except Exception:
1411 except Exception:
1414 log.exception(
1412 log.exception(
1415 "Exception occurred while trying to invalidate repo cache")
1413 "Exception occurred while trying to invalidate repo cache")
1416 raise JSONRPCError(
1414 raise JSONRPCError(
1417 'Error occurred during cache invalidation action'
1415 'Error occurred during cache invalidation action'
1418 )
1416 )
1419
1417
1420
1418
1421 #TODO: marcink, change name ?
1419 #TODO: marcink, change name ?
1422 @jsonrpc_method()
1420 @jsonrpc_method()
1423 def lock(request, apiuser, repoid, locked=Optional(None),
1421 def lock(request, apiuser, repoid, locked=Optional(None),
1424 userid=Optional(OAttr('apiuser'))):
1422 userid=Optional(OAttr('apiuser'))):
1425 """
1423 """
1426 Sets the lock state of the specified |repo| by the given user.
1424 Sets the lock state of the specified |repo| by the given user.
1427 From more information, see :ref:`repo-locking`.
1425 From more information, see :ref:`repo-locking`.
1428
1426
1429 * If the ``userid`` option is not set, the repository is locked to the
1427 * If the ``userid`` option is not set, the repository is locked to the
1430 user who called the method.
1428 user who called the method.
1431 * If the ``locked`` parameter is not set, the current lock state of the
1429 * If the ``locked`` parameter is not set, the current lock state of the
1432 repository is displayed.
1430 repository is displayed.
1433
1431
1434 This command can only be run using an |authtoken| with admin rights to
1432 This command can only be run using an |authtoken| with admin rights to
1435 the specified repository.
1433 the specified repository.
1436
1434
1437 This command takes the following options:
1435 This command takes the following options:
1438
1436
1439 :param apiuser: This is filled automatically from the |authtoken|.
1437 :param apiuser: This is filled automatically from the |authtoken|.
1440 :type apiuser: AuthUser
1438 :type apiuser: AuthUser
1441 :param repoid: Sets the repository name or repository ID.
1439 :param repoid: Sets the repository name or repository ID.
1442 :type repoid: str or int
1440 :type repoid: str or int
1443 :param locked: Sets the lock state.
1441 :param locked: Sets the lock state.
1444 :type locked: Optional(``True`` | ``False``)
1442 :type locked: Optional(``True`` | ``False``)
1445 :param userid: Set the repository lock to this user.
1443 :param userid: Set the repository lock to this user.
1446 :type userid: Optional(str or int)
1444 :type userid: Optional(str or int)
1447
1445
1448 Example error output:
1446 Example error output:
1449
1447
1450 .. code-block:: bash
1448 .. code-block:: bash
1451
1449
1452 id : <id_given_in_input>
1450 id : <id_given_in_input>
1453 result : {
1451 result : {
1454 'repo': '<reponame>',
1452 'repo': '<reponame>',
1455 'locked': <bool: lock state>,
1453 'locked': <bool: lock state>,
1456 'locked_since': <int: lock timestamp>,
1454 'locked_since': <int: lock timestamp>,
1457 'locked_by': <username of person who made the lock>,
1455 'locked_by': <username of person who made the lock>,
1458 'lock_reason': <str: reason for locking>,
1456 'lock_reason': <str: reason for locking>,
1459 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1457 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1460 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1458 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1461 or
1459 or
1462 'msg': 'Repo `<repository name>` not locked.'
1460 'msg': 'Repo `<repository name>` not locked.'
1463 or
1461 or
1464 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1462 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1465 }
1463 }
1466 error : null
1464 error : null
1467
1465
1468 Example error output:
1466 Example error output:
1469
1467
1470 .. code-block:: bash
1468 .. code-block:: bash
1471
1469
1472 id : <id_given_in_input>
1470 id : <id_given_in_input>
1473 result : null
1471 result : null
1474 error : {
1472 error : {
1475 'Error occurred locking repository `<reponame>`'
1473 'Error occurred locking repository `<reponame>`'
1476 }
1474 }
1477 """
1475 """
1478
1476
1479 repo = get_repo_or_error(repoid)
1477 repo = get_repo_or_error(repoid)
1480 if not has_superadmin_permission(apiuser):
1478 if not has_superadmin_permission(apiuser):
1481 # check if we have at least write permission for this repo !
1479 # check if we have at least write permission for this repo !
1482 _perms = ('repository.admin', 'repository.write',)
1480 _perms = ('repository.admin', 'repository.write',)
1483 validate_repo_permissions(apiuser, repoid, repo, _perms)
1481 validate_repo_permissions(apiuser, repoid, repo, _perms)
1484
1482
1485 # make sure normal user does not pass someone else userid,
1483 # make sure normal user does not pass someone else userid,
1486 # he is not allowed to do that
1484 # he is not allowed to do that
1487 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1485 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1488 raise JSONRPCError('userid is not the same as your user')
1486 raise JSONRPCError('userid is not the same as your user')
1489
1487
1490 if isinstance(userid, Optional):
1488 if isinstance(userid, Optional):
1491 userid = apiuser.user_id
1489 userid = apiuser.user_id
1492
1490
1493 user = get_user_or_error(userid)
1491 user = get_user_or_error(userid)
1494
1492
1495 if isinstance(locked, Optional):
1493 if isinstance(locked, Optional):
1496 lockobj = repo.locked
1494 lockobj = repo.locked
1497
1495
1498 if lockobj[0] is None:
1496 if lockobj[0] is None:
1499 _d = {
1497 _d = {
1500 'repo': repo.repo_name,
1498 'repo': repo.repo_name,
1501 'locked': False,
1499 'locked': False,
1502 'locked_since': None,
1500 'locked_since': None,
1503 'locked_by': None,
1501 'locked_by': None,
1504 'lock_reason': None,
1502 'lock_reason': None,
1505 'lock_state_changed': False,
1503 'lock_state_changed': False,
1506 'msg': 'Repo `%s` not locked.' % repo.repo_name
1504 'msg': 'Repo `%s` not locked.' % repo.repo_name
1507 }
1505 }
1508 return _d
1506 return _d
1509 else:
1507 else:
1510 _user_id, _time, _reason = lockobj
1508 _user_id, _time, _reason = lockobj
1511 lock_user = get_user_or_error(userid)
1509 lock_user = get_user_or_error(userid)
1512 _d = {
1510 _d = {
1513 'repo': repo.repo_name,
1511 'repo': repo.repo_name,
1514 'locked': True,
1512 'locked': True,
1515 'locked_since': _time,
1513 'locked_since': _time,
1516 'locked_by': lock_user.username,
1514 'locked_by': lock_user.username,
1517 'lock_reason': _reason,
1515 'lock_reason': _reason,
1518 'lock_state_changed': False,
1516 'lock_state_changed': False,
1519 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1517 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1520 % (repo.repo_name, lock_user.username,
1518 % (repo.repo_name, lock_user.username,
1521 json.dumps(time_to_datetime(_time))))
1519 json.dumps(time_to_datetime(_time))))
1522 }
1520 }
1523 return _d
1521 return _d
1524
1522
1525 # force locked state through a flag
1523 # force locked state through a flag
1526 else:
1524 else:
1527 locked = str2bool(locked)
1525 locked = str2bool(locked)
1528 lock_reason = Repository.LOCK_API
1526 lock_reason = Repository.LOCK_API
1529 try:
1527 try:
1530 if locked:
1528 if locked:
1531 lock_time = time.time()
1529 lock_time = time.time()
1532 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1530 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1533 else:
1531 else:
1534 lock_time = None
1532 lock_time = None
1535 Repository.unlock(repo)
1533 Repository.unlock(repo)
1536 _d = {
1534 _d = {
1537 'repo': repo.repo_name,
1535 'repo': repo.repo_name,
1538 'locked': locked,
1536 'locked': locked,
1539 'locked_since': lock_time,
1537 'locked_since': lock_time,
1540 'locked_by': user.username,
1538 'locked_by': user.username,
1541 'lock_reason': lock_reason,
1539 'lock_reason': lock_reason,
1542 'lock_state_changed': True,
1540 'lock_state_changed': True,
1543 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1541 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1544 % (user.username, repo.repo_name, locked))
1542 % (user.username, repo.repo_name, locked))
1545 }
1543 }
1546 return _d
1544 return _d
1547 except Exception:
1545 except Exception:
1548 log.exception(
1546 log.exception(
1549 "Exception occurred while trying to lock repository")
1547 "Exception occurred while trying to lock repository")
1550 raise JSONRPCError(
1548 raise JSONRPCError(
1551 'Error occurred locking repository `%s`' % repo.repo_name
1549 'Error occurred locking repository `%s`' % repo.repo_name
1552 )
1550 )
1553
1551
1554
1552
1555 @jsonrpc_method()
1553 @jsonrpc_method()
1556 def comment_commit(
1554 def comment_commit(
1557 request, apiuser, repoid, commit_id, message, status=Optional(None),
1555 request, apiuser, repoid, commit_id, message, status=Optional(None),
1558 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1556 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1559 resolves_comment_id=Optional(None), extra_recipients=Optional([]),
1557 resolves_comment_id=Optional(None), extra_recipients=Optional([]),
1560 userid=Optional(OAttr('apiuser')), send_email=Optional(True)):
1558 userid=Optional(OAttr('apiuser')), send_email=Optional(True)):
1561 """
1559 """
1562 Set a commit comment, and optionally change the status of the commit.
1560 Set a commit comment, and optionally change the status of the commit.
1563
1561
1564 :param apiuser: This is filled automatically from the |authtoken|.
1562 :param apiuser: This is filled automatically from the |authtoken|.
1565 :type apiuser: AuthUser
1563 :type apiuser: AuthUser
1566 :param repoid: Set the repository name or repository ID.
1564 :param repoid: Set the repository name or repository ID.
1567 :type repoid: str or int
1565 :type repoid: str or int
1568 :param commit_id: Specify the commit_id for which to set a comment.
1566 :param commit_id: Specify the commit_id for which to set a comment.
1569 :type commit_id: str
1567 :type commit_id: str
1570 :param message: The comment text.
1568 :param message: The comment text.
1571 :type message: str
1569 :type message: str
1572 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1570 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1573 'approved', 'rejected', 'under_review'
1571 'approved', 'rejected', 'under_review'
1574 :type status: str
1572 :type status: str
1575 :param comment_type: Comment type, one of: 'note', 'todo'
1573 :param comment_type: Comment type, one of: 'note', 'todo'
1576 :type comment_type: Optional(str), default: 'note'
1574 :type comment_type: Optional(str), default: 'note'
1577 :param resolves_comment_id: id of comment which this one will resolve
1575 :param resolves_comment_id: id of comment which this one will resolve
1578 :type resolves_comment_id: Optional(int)
1576 :type resolves_comment_id: Optional(int)
1579 :param extra_recipients: list of user ids or usernames to add
1577 :param extra_recipients: list of user ids or usernames to add
1580 notifications for this comment. Acts like a CC for notification
1578 notifications for this comment. Acts like a CC for notification
1581 :type extra_recipients: Optional(list)
1579 :type extra_recipients: Optional(list)
1582 :param userid: Set the user name of the comment creator.
1580 :param userid: Set the user name of the comment creator.
1583 :type userid: Optional(str or int)
1581 :type userid: Optional(str or int)
1584 :param send_email: Define if this comment should also send email notification
1582 :param send_email: Define if this comment should also send email notification
1585 :type send_email: Optional(bool)
1583 :type send_email: Optional(bool)
1586
1584
1587 Example error output:
1585 Example error output:
1588
1586
1589 .. code-block:: bash
1587 .. code-block:: bash
1590
1588
1591 {
1589 {
1592 "id" : <id_given_in_input>,
1590 "id" : <id_given_in_input>,
1593 "result" : {
1591 "result" : {
1594 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1592 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1595 "status_change": null or <status>,
1593 "status_change": null or <status>,
1596 "success": true
1594 "success": true
1597 },
1595 },
1598 "error" : null
1596 "error" : null
1599 }
1597 }
1600
1598
1601 """
1599 """
1602 repo = get_repo_or_error(repoid)
1600 repo = get_repo_or_error(repoid)
1603 if not has_superadmin_permission(apiuser):
1601 if not has_superadmin_permission(apiuser):
1604 _perms = ('repository.read', 'repository.write', 'repository.admin')
1602 _perms = ('repository.read', 'repository.write', 'repository.admin')
1605 validate_repo_permissions(apiuser, repoid, repo, _perms)
1603 validate_repo_permissions(apiuser, repoid, repo, _perms)
1606
1604
1607 try:
1605 try:
1608 commit = repo.scm_instance().get_commit(commit_id=commit_id)
1606 commit = repo.scm_instance().get_commit(commit_id=commit_id)
1609 commit_id = commit.raw_id
1607 commit_id = commit.raw_id
1610 except Exception as e:
1608 except Exception as e:
1611 log.exception('Failed to fetch commit')
1609 log.exception('Failed to fetch commit')
1612 raise JSONRPCError(safe_str(e))
1610 raise JSONRPCError(safe_str(e))
1613
1611
1614 if isinstance(userid, Optional):
1612 if isinstance(userid, Optional):
1615 userid = apiuser.user_id
1613 userid = apiuser.user_id
1616
1614
1617 user = get_user_or_error(userid)
1615 user = get_user_or_error(userid)
1618 status = Optional.extract(status)
1616 status = Optional.extract(status)
1619 comment_type = Optional.extract(comment_type)
1617 comment_type = Optional.extract(comment_type)
1620 resolves_comment_id = Optional.extract(resolves_comment_id)
1618 resolves_comment_id = Optional.extract(resolves_comment_id)
1621 extra_recipients = Optional.extract(extra_recipients)
1619 extra_recipients = Optional.extract(extra_recipients)
1622 send_email = Optional.extract(send_email, binary=True)
1620 send_email = Optional.extract(send_email, binary=True)
1623
1621
1624 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1622 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1625 if status and status not in allowed_statuses:
1623 if status and status not in allowed_statuses:
1626 raise JSONRPCError('Bad status, must be on '
1624 raise JSONRPCError('Bad status, must be on '
1627 'of %s got %s' % (allowed_statuses, status,))
1625 'of %s got %s' % (allowed_statuses, status,))
1628
1626
1629 if resolves_comment_id:
1627 if resolves_comment_id:
1630 comment = ChangesetComment.get(resolves_comment_id)
1628 comment = ChangesetComment.get(resolves_comment_id)
1631 if not comment:
1629 if not comment:
1632 raise JSONRPCError(
1630 raise JSONRPCError(
1633 'Invalid resolves_comment_id `%s` for this commit.'
1631 'Invalid resolves_comment_id `%s` for this commit.'
1634 % resolves_comment_id)
1632 % resolves_comment_id)
1635 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1633 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1636 raise JSONRPCError(
1634 raise JSONRPCError(
1637 'Comment `%s` is wrong type for setting status to resolved.'
1635 'Comment `%s` is wrong type for setting status to resolved.'
1638 % resolves_comment_id)
1636 % resolves_comment_id)
1639
1637
1640 try:
1638 try:
1641 rc_config = SettingsModel().get_all_settings()
1639 rc_config = SettingsModel().get_all_settings()
1642 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1640 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1643 status_change_label = ChangesetStatus.get_status_lbl(status)
1641 status_change_label = ChangesetStatus.get_status_lbl(status)
1644 comment = CommentsModel().create(
1642 comment = CommentsModel().create(
1645 message, repo, user, commit_id=commit_id,
1643 message, repo, user, commit_id=commit_id,
1646 status_change=status_change_label,
1644 status_change=status_change_label,
1647 status_change_type=status,
1645 status_change_type=status,
1648 renderer=renderer,
1646 renderer=renderer,
1649 comment_type=comment_type,
1647 comment_type=comment_type,
1650 resolves_comment_id=resolves_comment_id,
1648 resolves_comment_id=resolves_comment_id,
1651 auth_user=apiuser,
1649 auth_user=apiuser,
1652 extra_recipients=extra_recipients,
1650 extra_recipients=extra_recipients,
1653 send_email=send_email
1651 send_email=send_email
1654 )
1652 )
1655 if status:
1653 if status:
1656 # also do a status change
1654 # also do a status change
1657 try:
1655 try:
1658 ChangesetStatusModel().set_status(
1656 ChangesetStatusModel().set_status(
1659 repo, status, user, comment, revision=commit_id,
1657 repo, status, user, comment, revision=commit_id,
1660 dont_allow_on_closed_pull_request=True
1658 dont_allow_on_closed_pull_request=True
1661 )
1659 )
1662 except StatusChangeOnClosedPullRequestError:
1660 except StatusChangeOnClosedPullRequestError:
1663 log.exception(
1661 log.exception(
1664 "Exception occurred while trying to change repo commit status")
1662 "Exception occurred while trying to change repo commit status")
1665 msg = ('Changing status on a commit associated with '
1663 msg = ('Changing status on a commit associated with '
1666 'a closed pull request is not allowed')
1664 'a closed pull request is not allowed')
1667 raise JSONRPCError(msg)
1665 raise JSONRPCError(msg)
1668
1666
1669 CommentsModel().trigger_commit_comment_hook(
1667 CommentsModel().trigger_commit_comment_hook(
1670 repo, apiuser, 'create',
1668 repo, apiuser, 'create',
1671 data={'comment': comment, 'commit': commit})
1669 data={'comment': comment, 'commit': commit})
1672
1670
1673 Session().commit()
1671 Session().commit()
1674 return {
1672 return {
1675 'msg': (
1673 'msg': (
1676 'Commented on commit `%s` for repository `%s`' % (
1674 'Commented on commit `%s` for repository `%s`' % (
1677 comment.revision, repo.repo_name)),
1675 comment.revision, repo.repo_name)),
1678 'status_change': status,
1676 'status_change': status,
1679 'success': True,
1677 'success': True,
1680 }
1678 }
1681 except JSONRPCError:
1679 except JSONRPCError:
1682 # catch any inside errors, and re-raise them to prevent from
1680 # catch any inside errors, and re-raise them to prevent from
1683 # below global catch to silence them
1681 # below global catch to silence them
1684 raise
1682 raise
1685 except Exception:
1683 except Exception:
1686 log.exception("Exception occurred while trying to comment on commit")
1684 log.exception("Exception occurred while trying to comment on commit")
1687 raise JSONRPCError(
1685 raise JSONRPCError(
1688 'failed to set comment on repository `%s`' % (repo.repo_name,)
1686 'failed to set comment on repository `%s`' % (repo.repo_name,)
1689 )
1687 )
1690
1688
1691
1689
1692 @jsonrpc_method()
1690 @jsonrpc_method()
1693 def get_repo_comments(request, apiuser, repoid,
1691 def get_repo_comments(request, apiuser, repoid,
1694 commit_id=Optional(None), comment_type=Optional(None),
1692 commit_id=Optional(None), comment_type=Optional(None),
1695 userid=Optional(None)):
1693 userid=Optional(None)):
1696 """
1694 """
1697 Get all comments for a repository
1695 Get all comments for a repository
1698
1696
1699 :param apiuser: This is filled automatically from the |authtoken|.
1697 :param apiuser: This is filled automatically from the |authtoken|.
1700 :type apiuser: AuthUser
1698 :type apiuser: AuthUser
1701 :param repoid: Set the repository name or repository ID.
1699 :param repoid: Set the repository name or repository ID.
1702 :type repoid: str or int
1700 :type repoid: str or int
1703 :param commit_id: Optionally filter the comments by the commit_id
1701 :param commit_id: Optionally filter the comments by the commit_id
1704 :type commit_id: Optional(str), default: None
1702 :type commit_id: Optional(str), default: None
1705 :param comment_type: Optionally filter the comments by the comment_type
1703 :param comment_type: Optionally filter the comments by the comment_type
1706 one of: 'note', 'todo'
1704 one of: 'note', 'todo'
1707 :type comment_type: Optional(str), default: None
1705 :type comment_type: Optional(str), default: None
1708 :param userid: Optionally filter the comments by the author of comment
1706 :param userid: Optionally filter the comments by the author of comment
1709 :type userid: Optional(str or int), Default: None
1707 :type userid: Optional(str or int), Default: None
1710
1708
1711 Example error output:
1709 Example error output:
1712
1710
1713 .. code-block:: bash
1711 .. code-block:: bash
1714
1712
1715 {
1713 {
1716 "id" : <id_given_in_input>,
1714 "id" : <id_given_in_input>,
1717 "result" : [
1715 "result" : [
1718 {
1716 {
1719 "comment_author": <USER_DETAILS>,
1717 "comment_author": <USER_DETAILS>,
1720 "comment_created_on": "2017-02-01T14:38:16.309",
1718 "comment_created_on": "2017-02-01T14:38:16.309",
1721 "comment_f_path": "file.txt",
1719 "comment_f_path": "file.txt",
1722 "comment_id": 282,
1720 "comment_id": 282,
1723 "comment_lineno": "n1",
1721 "comment_lineno": "n1",
1724 "comment_resolved_by": null,
1722 "comment_resolved_by": null,
1725 "comment_status": [],
1723 "comment_status": [],
1726 "comment_text": "This file needs a header",
1724 "comment_text": "This file needs a header",
1727 "comment_type": "todo",
1725 "comment_type": "todo",
1728 "comment_last_version: 0
1726 "comment_last_version: 0
1729 }
1727 }
1730 ],
1728 ],
1731 "error" : null
1729 "error" : null
1732 }
1730 }
1733
1731
1734 """
1732 """
1735 repo = get_repo_or_error(repoid)
1733 repo = get_repo_or_error(repoid)
1736 if not has_superadmin_permission(apiuser):
1734 if not has_superadmin_permission(apiuser):
1737 _perms = ('repository.read', 'repository.write', 'repository.admin')
1735 _perms = ('repository.read', 'repository.write', 'repository.admin')
1738 validate_repo_permissions(apiuser, repoid, repo, _perms)
1736 validate_repo_permissions(apiuser, repoid, repo, _perms)
1739
1737
1740 commit_id = Optional.extract(commit_id)
1738 commit_id = Optional.extract(commit_id)
1741
1739
1742 userid = Optional.extract(userid)
1740 userid = Optional.extract(userid)
1743 if userid:
1741 if userid:
1744 user = get_user_or_error(userid)
1742 user = get_user_or_error(userid)
1745 else:
1743 else:
1746 user = None
1744 user = None
1747
1745
1748 comment_type = Optional.extract(comment_type)
1746 comment_type = Optional.extract(comment_type)
1749 if comment_type and comment_type not in ChangesetComment.COMMENT_TYPES:
1747 if comment_type and comment_type not in ChangesetComment.COMMENT_TYPES:
1750 raise JSONRPCError(
1748 raise JSONRPCError(
1751 'comment_type must be one of `{}` got {}'.format(
1749 'comment_type must be one of `{}` got {}'.format(
1752 ChangesetComment.COMMENT_TYPES, comment_type)
1750 ChangesetComment.COMMENT_TYPES, comment_type)
1753 )
1751 )
1754
1752
1755 comments = CommentsModel().get_repository_comments(
1753 comments = CommentsModel().get_repository_comments(
1756 repo=repo, comment_type=comment_type, user=user, commit_id=commit_id)
1754 repo=repo, comment_type=comment_type, user=user, commit_id=commit_id)
1757 return comments
1755 return comments
1758
1756
1759
1757
1760 @jsonrpc_method()
1758 @jsonrpc_method()
1761 def get_comment(request, apiuser, comment_id):
1759 def get_comment(request, apiuser, comment_id):
1762 """
1760 """
1763 Get single comment from repository or pull_request
1761 Get single comment from repository or pull_request
1764
1762
1765 :param apiuser: This is filled automatically from the |authtoken|.
1763 :param apiuser: This is filled automatically from the |authtoken|.
1766 :type apiuser: AuthUser
1764 :type apiuser: AuthUser
1767 :param comment_id: comment id found in the URL of comment
1765 :param comment_id: comment id found in the URL of comment
1768 :type comment_id: str or int
1766 :type comment_id: str or int
1769
1767
1770 Example error output:
1768 Example error output:
1771
1769
1772 .. code-block:: bash
1770 .. code-block:: bash
1773
1771
1774 {
1772 {
1775 "id" : <id_given_in_input>,
1773 "id" : <id_given_in_input>,
1776 "result" : {
1774 "result" : {
1777 "comment_author": <USER_DETAILS>,
1775 "comment_author": <USER_DETAILS>,
1778 "comment_created_on": "2017-02-01T14:38:16.309",
1776 "comment_created_on": "2017-02-01T14:38:16.309",
1779 "comment_f_path": "file.txt",
1777 "comment_f_path": "file.txt",
1780 "comment_id": 282,
1778 "comment_id": 282,
1781 "comment_lineno": "n1",
1779 "comment_lineno": "n1",
1782 "comment_resolved_by": null,
1780 "comment_resolved_by": null,
1783 "comment_status": [],
1781 "comment_status": [],
1784 "comment_text": "This file needs a header",
1782 "comment_text": "This file needs a header",
1785 "comment_type": "todo",
1783 "comment_type": "todo",
1786 "comment_last_version: 0
1784 "comment_last_version: 0
1787 },
1785 },
1788 "error" : null
1786 "error" : null
1789 }
1787 }
1790
1788
1791 """
1789 """
1792
1790
1793 comment = ChangesetComment.get(comment_id)
1791 comment = ChangesetComment.get(comment_id)
1794 if not comment:
1792 if not comment:
1795 raise JSONRPCError('comment `%s` does not exist' % (comment_id,))
1793 raise JSONRPCError('comment `%s` does not exist' % (comment_id,))
1796
1794
1797 perms = ('repository.read', 'repository.write', 'repository.admin')
1795 perms = ('repository.read', 'repository.write', 'repository.admin')
1798 has_comment_perm = HasRepoPermissionAnyApi(*perms)\
1796 has_comment_perm = HasRepoPermissionAnyApi(*perms)\
1799 (user=apiuser, repo_name=comment.repo.repo_name)
1797 (user=apiuser, repo_name=comment.repo.repo_name)
1800
1798
1801 if not has_comment_perm:
1799 if not has_comment_perm:
1802 raise JSONRPCError('comment `%s` does not exist' % (comment_id,))
1800 raise JSONRPCError('comment `%s` does not exist' % (comment_id,))
1803
1801
1804 return comment
1802 return comment
1805
1803
1806
1804
1807 @jsonrpc_method()
1805 @jsonrpc_method()
1808 def edit_comment(request, apiuser, message, comment_id, version,
1806 def edit_comment(request, apiuser, message, comment_id, version,
1809 userid=Optional(OAttr('apiuser'))):
1807 userid=Optional(OAttr('apiuser'))):
1810 """
1808 """
1811 Edit comment on the pull request or commit,
1809 Edit comment on the pull request or commit,
1812 specified by the `comment_id` and version. Initially version should be 0
1810 specified by the `comment_id` and version. Initially version should be 0
1813
1811
1814 :param apiuser: This is filled automatically from the |authtoken|.
1812 :param apiuser: This is filled automatically from the |authtoken|.
1815 :type apiuser: AuthUser
1813 :type apiuser: AuthUser
1816 :param comment_id: Specify the comment_id for editing
1814 :param comment_id: Specify the comment_id for editing
1817 :type comment_id: int
1815 :type comment_id: int
1818 :param version: version of the comment that will be created, starts from 0
1816 :param version: version of the comment that will be created, starts from 0
1819 :type version: int
1817 :type version: int
1820 :param message: The text content of the comment.
1818 :param message: The text content of the comment.
1821 :type message: str
1819 :type message: str
1822 :param userid: Comment on the pull request as this user
1820 :param userid: Comment on the pull request as this user
1823 :type userid: Optional(str or int)
1821 :type userid: Optional(str or int)
1824
1822
1825 Example output:
1823 Example output:
1826
1824
1827 .. code-block:: bash
1825 .. code-block:: bash
1828
1826
1829 id : <id_given_in_input>
1827 id : <id_given_in_input>
1830 result : {
1828 result : {
1831 "comment": "<comment data>",
1829 "comment": "<comment data>",
1832 "version": "<Integer>",
1830 "version": "<Integer>",
1833 },
1831 },
1834 error : null
1832 error : null
1835 """
1833 """
1836
1834
1837 auth_user = apiuser
1835 auth_user = apiuser
1838 comment = ChangesetComment.get(comment_id)
1836 comment = ChangesetComment.get(comment_id)
1839 if not comment:
1837 if not comment:
1840 raise JSONRPCError('comment `%s` does not exist' % (comment_id,))
1838 raise JSONRPCError('comment `%s` does not exist' % (comment_id,))
1841
1839
1842 is_super_admin = has_superadmin_permission(apiuser)
1840 is_super_admin = has_superadmin_permission(apiuser)
1843 is_repo_admin = HasRepoPermissionAnyApi('repository.admin')\
1841 is_repo_admin = HasRepoPermissionAnyApi('repository.admin')\
1844 (user=apiuser, repo_name=comment.repo.repo_name)
1842 (user=apiuser, repo_name=comment.repo.repo_name)
1845
1843
1846 if not isinstance(userid, Optional):
1844 if not isinstance(userid, Optional):
1847 if is_super_admin or is_repo_admin:
1845 if is_super_admin or is_repo_admin:
1848 apiuser = get_user_or_error(userid)
1846 apiuser = get_user_or_error(userid)
1849 auth_user = apiuser.AuthUser()
1847 auth_user = apiuser.AuthUser()
1850 else:
1848 else:
1851 raise JSONRPCError('userid is not the same as your user')
1849 raise JSONRPCError('userid is not the same as your user')
1852
1850
1853 comment_author = comment.author.user_id == auth_user.user_id
1851 comment_author = comment.author.user_id == auth_user.user_id
1854 if not (comment.immutable is False and (is_super_admin or is_repo_admin) or comment_author):
1852 if not (comment.immutable is False and (is_super_admin or is_repo_admin) or comment_author):
1855 raise JSONRPCError("you don't have access to edit this comment")
1853 raise JSONRPCError("you don't have access to edit this comment")
1856
1854
1857 try:
1855 try:
1858 comment_history = CommentsModel().edit(
1856 comment_history = CommentsModel().edit(
1859 comment_id=comment_id,
1857 comment_id=comment_id,
1860 text=message,
1858 text=message,
1861 auth_user=auth_user,
1859 auth_user=auth_user,
1862 version=version,
1860 version=version,
1863 )
1861 )
1864 Session().commit()
1862 Session().commit()
1865 except CommentVersionMismatch:
1863 except CommentVersionMismatch:
1866 raise JSONRPCError(
1864 raise JSONRPCError(
1867 'comment ({}) version ({}) mismatch'.format(comment_id, version)
1865 'comment ({}) version ({}) mismatch'.format(comment_id, version)
1868 )
1866 )
1869 if not comment_history and not message:
1867 if not comment_history and not message:
1870 raise JSONRPCError(
1868 raise JSONRPCError(
1871 "comment ({}) can't be changed with empty string".format(comment_id)
1869 "comment ({}) can't be changed with empty string".format(comment_id)
1872 )
1870 )
1873
1871
1874 if comment.pull_request:
1872 if comment.pull_request:
1875 pull_request = comment.pull_request
1873 pull_request = comment.pull_request
1876 PullRequestModel().trigger_pull_request_hook(
1874 PullRequestModel().trigger_pull_request_hook(
1877 pull_request, apiuser, 'comment_edit',
1875 pull_request, apiuser, 'comment_edit',
1878 data={'comment': comment})
1876 data={'comment': comment})
1879 else:
1877 else:
1880 db_repo = comment.repo
1878 db_repo = comment.repo
1881 commit_id = comment.revision
1879 commit_id = comment.revision
1882 commit = db_repo.get_commit(commit_id)
1880 commit = db_repo.get_commit(commit_id)
1883 CommentsModel().trigger_commit_comment_hook(
1881 CommentsModel().trigger_commit_comment_hook(
1884 db_repo, apiuser, 'edit',
1882 db_repo, apiuser, 'edit',
1885 data={'comment': comment, 'commit': commit})
1883 data={'comment': comment, 'commit': commit})
1886
1884
1887 data = {
1885 data = {
1888 'comment': comment,
1886 'comment': comment,
1889 'version': comment_history.version if comment_history else None,
1887 'version': comment_history.version if comment_history else None,
1890 }
1888 }
1891 return data
1889 return data
1892
1890
1893
1891
1894 # TODO(marcink): write this with all required logic for deleting a comments in PR or commits
1892 # TODO(marcink): write this with all required logic for deleting a comments in PR or commits
1895 # @jsonrpc_method()
1893 # @jsonrpc_method()
1896 # def delete_comment(request, apiuser, comment_id):
1894 # def delete_comment(request, apiuser, comment_id):
1897 # auth_user = apiuser
1895 # auth_user = apiuser
1898 #
1896 #
1899 # comment = ChangesetComment.get(comment_id)
1897 # comment = ChangesetComment.get(comment_id)
1900 # if not comment:
1898 # if not comment:
1901 # raise JSONRPCError('comment `%s` does not exist' % (comment_id,))
1899 # raise JSONRPCError('comment `%s` does not exist' % (comment_id,))
1902 #
1900 #
1903 # is_super_admin = has_superadmin_permission(apiuser)
1901 # is_super_admin = has_superadmin_permission(apiuser)
1904 # is_repo_admin = HasRepoPermissionAnyApi('repository.admin')\
1902 # is_repo_admin = HasRepoPermissionAnyApi('repository.admin')\
1905 # (user=apiuser, repo_name=comment.repo.repo_name)
1903 # (user=apiuser, repo_name=comment.repo.repo_name)
1906 #
1904 #
1907 # comment_author = comment.author.user_id == auth_user.user_id
1905 # comment_author = comment.author.user_id == auth_user.user_id
1908 # if not (comment.immutable is False and (is_super_admin or is_repo_admin) or comment_author):
1906 # if not (comment.immutable is False and (is_super_admin or is_repo_admin) or comment_author):
1909 # raise JSONRPCError("you don't have access to edit this comment")
1907 # raise JSONRPCError("you don't have access to edit this comment")
1910
1908
1911 @jsonrpc_method()
1909 @jsonrpc_method()
1912 def grant_user_permission(request, apiuser, repoid, userid, perm):
1910 def grant_user_permission(request, apiuser, repoid, userid, perm):
1913 """
1911 """
1914 Grant permissions for the specified user on the given repository,
1912 Grant permissions for the specified user on the given repository,
1915 or update existing permissions if found.
1913 or update existing permissions if found.
1916
1914
1917 This command can only be run using an |authtoken| with admin
1915 This command can only be run using an |authtoken| with admin
1918 permissions on the |repo|.
1916 permissions on the |repo|.
1919
1917
1920 :param apiuser: This is filled automatically from the |authtoken|.
1918 :param apiuser: This is filled automatically from the |authtoken|.
1921 :type apiuser: AuthUser
1919 :type apiuser: AuthUser
1922 :param repoid: Set the repository name or repository ID.
1920 :param repoid: Set the repository name or repository ID.
1923 :type repoid: str or int
1921 :type repoid: str or int
1924 :param userid: Set the user name.
1922 :param userid: Set the user name.
1925 :type userid: str
1923 :type userid: str
1926 :param perm: Set the user permissions, using the following format
1924 :param perm: Set the user permissions, using the following format
1927 ``(repository.(none|read|write|admin))``
1925 ``(repository.(none|read|write|admin))``
1928 :type perm: str
1926 :type perm: str
1929
1927
1930 Example output:
1928 Example output:
1931
1929
1932 .. code-block:: bash
1930 .. code-block:: bash
1933
1931
1934 id : <id_given_in_input>
1932 id : <id_given_in_input>
1935 result: {
1933 result: {
1936 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1934 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1937 "success": true
1935 "success": true
1938 }
1936 }
1939 error: null
1937 error: null
1940 """
1938 """
1941
1939
1942 repo = get_repo_or_error(repoid)
1940 repo = get_repo_or_error(repoid)
1943 user = get_user_or_error(userid)
1941 user = get_user_or_error(userid)
1944 perm = get_perm_or_error(perm)
1942 perm = get_perm_or_error(perm)
1945 if not has_superadmin_permission(apiuser):
1943 if not has_superadmin_permission(apiuser):
1946 _perms = ('repository.admin',)
1944 _perms = ('repository.admin',)
1947 validate_repo_permissions(apiuser, repoid, repo, _perms)
1945 validate_repo_permissions(apiuser, repoid, repo, _perms)
1948
1946
1949 perm_additions = [[user.user_id, perm.permission_name, "user"]]
1947 perm_additions = [[user.user_id, perm.permission_name, "user"]]
1950 try:
1948 try:
1951 changes = RepoModel().update_permissions(
1949 changes = RepoModel().update_permissions(
1952 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1950 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1953
1951
1954 action_data = {
1952 action_data = {
1955 'added': changes['added'],
1953 'added': changes['added'],
1956 'updated': changes['updated'],
1954 'updated': changes['updated'],
1957 'deleted': changes['deleted'],
1955 'deleted': changes['deleted'],
1958 }
1956 }
1959 audit_logger.store_api(
1957 audit_logger.store_api(
1960 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1958 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1961 Session().commit()
1959 Session().commit()
1962 PermissionModel().flush_user_permission_caches(changes)
1960 PermissionModel().flush_user_permission_caches(changes)
1963
1961
1964 return {
1962 return {
1965 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1963 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1966 perm.permission_name, user.username, repo.repo_name
1964 perm.permission_name, user.username, repo.repo_name
1967 ),
1965 ),
1968 'success': True
1966 'success': True
1969 }
1967 }
1970 except Exception:
1968 except Exception:
1971 log.exception("Exception occurred while trying edit permissions for repo")
1969 log.exception("Exception occurred while trying edit permissions for repo")
1972 raise JSONRPCError(
1970 raise JSONRPCError(
1973 'failed to edit permission for user: `%s` in repo: `%s`' % (
1971 'failed to edit permission for user: `%s` in repo: `%s`' % (
1974 userid, repoid
1972 userid, repoid
1975 )
1973 )
1976 )
1974 )
1977
1975
1978
1976
1979 @jsonrpc_method()
1977 @jsonrpc_method()
1980 def revoke_user_permission(request, apiuser, repoid, userid):
1978 def revoke_user_permission(request, apiuser, repoid, userid):
1981 """
1979 """
1982 Revoke permission for a user on the specified repository.
1980 Revoke permission for a user on the specified repository.
1983
1981
1984 This command can only be run using an |authtoken| with admin
1982 This command can only be run using an |authtoken| with admin
1985 permissions on the |repo|.
1983 permissions on the |repo|.
1986
1984
1987 :param apiuser: This is filled automatically from the |authtoken|.
1985 :param apiuser: This is filled automatically from the |authtoken|.
1988 :type apiuser: AuthUser
1986 :type apiuser: AuthUser
1989 :param repoid: Set the repository name or repository ID.
1987 :param repoid: Set the repository name or repository ID.
1990 :type repoid: str or int
1988 :type repoid: str or int
1991 :param userid: Set the user name of revoked user.
1989 :param userid: Set the user name of revoked user.
1992 :type userid: str or int
1990 :type userid: str or int
1993
1991
1994 Example error output:
1992 Example error output:
1995
1993
1996 .. code-block:: bash
1994 .. code-block:: bash
1997
1995
1998 id : <id_given_in_input>
1996 id : <id_given_in_input>
1999 result: {
1997 result: {
2000 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1998 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
2001 "success": true
1999 "success": true
2002 }
2000 }
2003 error: null
2001 error: null
2004 """
2002 """
2005
2003
2006 repo = get_repo_or_error(repoid)
2004 repo = get_repo_or_error(repoid)
2007 user = get_user_or_error(userid)
2005 user = get_user_or_error(userid)
2008 if not has_superadmin_permission(apiuser):
2006 if not has_superadmin_permission(apiuser):
2009 _perms = ('repository.admin',)
2007 _perms = ('repository.admin',)
2010 validate_repo_permissions(apiuser, repoid, repo, _perms)
2008 validate_repo_permissions(apiuser, repoid, repo, _perms)
2011
2009
2012 perm_deletions = [[user.user_id, None, "user"]]
2010 perm_deletions = [[user.user_id, None, "user"]]
2013 try:
2011 try:
2014 changes = RepoModel().update_permissions(
2012 changes = RepoModel().update_permissions(
2015 repo=repo, perm_deletions=perm_deletions, cur_user=user)
2013 repo=repo, perm_deletions=perm_deletions, cur_user=user)
2016
2014
2017 action_data = {
2015 action_data = {
2018 'added': changes['added'],
2016 'added': changes['added'],
2019 'updated': changes['updated'],
2017 'updated': changes['updated'],
2020 'deleted': changes['deleted'],
2018 'deleted': changes['deleted'],
2021 }
2019 }
2022 audit_logger.store_api(
2020 audit_logger.store_api(
2023 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
2021 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
2024 Session().commit()
2022 Session().commit()
2025 PermissionModel().flush_user_permission_caches(changes)
2023 PermissionModel().flush_user_permission_caches(changes)
2026
2024
2027 return {
2025 return {
2028 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
2026 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
2029 user.username, repo.repo_name
2027 user.username, repo.repo_name
2030 ),
2028 ),
2031 'success': True
2029 'success': True
2032 }
2030 }
2033 except Exception:
2031 except Exception:
2034 log.exception("Exception occurred while trying revoke permissions to repo")
2032 log.exception("Exception occurred while trying revoke permissions to repo")
2035 raise JSONRPCError(
2033 raise JSONRPCError(
2036 'failed to edit permission for user: `%s` in repo: `%s`' % (
2034 'failed to edit permission for user: `%s` in repo: `%s`' % (
2037 userid, repoid
2035 userid, repoid
2038 )
2036 )
2039 )
2037 )
2040
2038
2041
2039
2042 @jsonrpc_method()
2040 @jsonrpc_method()
2043 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
2041 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
2044 """
2042 """
2045 Grant permission for a user group on the specified repository,
2043 Grant permission for a user group on the specified repository,
2046 or update existing permissions.
2044 or update existing permissions.
2047
2045
2048 This command can only be run using an |authtoken| with admin
2046 This command can only be run using an |authtoken| with admin
2049 permissions on the |repo|.
2047 permissions on the |repo|.
2050
2048
2051 :param apiuser: This is filled automatically from the |authtoken|.
2049 :param apiuser: This is filled automatically from the |authtoken|.
2052 :type apiuser: AuthUser
2050 :type apiuser: AuthUser
2053 :param repoid: Set the repository name or repository ID.
2051 :param repoid: Set the repository name or repository ID.
2054 :type repoid: str or int
2052 :type repoid: str or int
2055 :param usergroupid: Specify the ID of the user group.
2053 :param usergroupid: Specify the ID of the user group.
2056 :type usergroupid: str or int
2054 :type usergroupid: str or int
2057 :param perm: Set the user group permissions using the following
2055 :param perm: Set the user group permissions using the following
2058 format: (repository.(none|read|write|admin))
2056 format: (repository.(none|read|write|admin))
2059 :type perm: str
2057 :type perm: str
2060
2058
2061 Example output:
2059 Example output:
2062
2060
2063 .. code-block:: bash
2061 .. code-block:: bash
2064
2062
2065 id : <id_given_in_input>
2063 id : <id_given_in_input>
2066 result : {
2064 result : {
2067 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
2065 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
2068 "success": true
2066 "success": true
2069
2067
2070 }
2068 }
2071 error : null
2069 error : null
2072
2070
2073 Example error output:
2071 Example error output:
2074
2072
2075 .. code-block:: bash
2073 .. code-block:: bash
2076
2074
2077 id : <id_given_in_input>
2075 id : <id_given_in_input>
2078 result : null
2076 result : null
2079 error : {
2077 error : {
2080 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
2078 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
2081 }
2079 }
2082
2080
2083 """
2081 """
2084
2082
2085 repo = get_repo_or_error(repoid)
2083 repo = get_repo_or_error(repoid)
2086 perm = get_perm_or_error(perm)
2084 perm = get_perm_or_error(perm)
2087 if not has_superadmin_permission(apiuser):
2085 if not has_superadmin_permission(apiuser):
2088 _perms = ('repository.admin',)
2086 _perms = ('repository.admin',)
2089 validate_repo_permissions(apiuser, repoid, repo, _perms)
2087 validate_repo_permissions(apiuser, repoid, repo, _perms)
2090
2088
2091 user_group = get_user_group_or_error(usergroupid)
2089 user_group = get_user_group_or_error(usergroupid)
2092 if not has_superadmin_permission(apiuser):
2090 if not has_superadmin_permission(apiuser):
2093 # check if we have at least read permission for this user group !
2091 # check if we have at least read permission for this user group !
2094 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
2092 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
2095 if not HasUserGroupPermissionAnyApi(*_perms)(
2093 if not HasUserGroupPermissionAnyApi(*_perms)(
2096 user=apiuser, user_group_name=user_group.users_group_name):
2094 user=apiuser, user_group_name=user_group.users_group_name):
2097 raise JSONRPCError(
2095 raise JSONRPCError(
2098 'user group `%s` does not exist' % (usergroupid,))
2096 'user group `%s` does not exist' % (usergroupid,))
2099
2097
2100 perm_additions = [[user_group.users_group_id, perm.permission_name, "user_group"]]
2098 perm_additions = [[user_group.users_group_id, perm.permission_name, "user_group"]]
2101 try:
2099 try:
2102 changes = RepoModel().update_permissions(
2100 changes = RepoModel().update_permissions(
2103 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
2101 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
2104 action_data = {
2102 action_data = {
2105 'added': changes['added'],
2103 'added': changes['added'],
2106 'updated': changes['updated'],
2104 'updated': changes['updated'],
2107 'deleted': changes['deleted'],
2105 'deleted': changes['deleted'],
2108 }
2106 }
2109 audit_logger.store_api(
2107 audit_logger.store_api(
2110 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
2108 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
2111 Session().commit()
2109 Session().commit()
2112 PermissionModel().flush_user_permission_caches(changes)
2110 PermissionModel().flush_user_permission_caches(changes)
2113
2111
2114 return {
2112 return {
2115 'msg': 'Granted perm: `%s` for user group: `%s` in '
2113 'msg': 'Granted perm: `%s` for user group: `%s` in '
2116 'repo: `%s`' % (
2114 'repo: `%s`' % (
2117 perm.permission_name, user_group.users_group_name,
2115 perm.permission_name, user_group.users_group_name,
2118 repo.repo_name
2116 repo.repo_name
2119 ),
2117 ),
2120 'success': True
2118 'success': True
2121 }
2119 }
2122 except Exception:
2120 except Exception:
2123 log.exception(
2121 log.exception(
2124 "Exception occurred while trying change permission on repo")
2122 "Exception occurred while trying change permission on repo")
2125 raise JSONRPCError(
2123 raise JSONRPCError(
2126 'failed to edit permission for user group: `%s` in '
2124 'failed to edit permission for user group: `%s` in '
2127 'repo: `%s`' % (
2125 'repo: `%s`' % (
2128 usergroupid, repo.repo_name
2126 usergroupid, repo.repo_name
2129 )
2127 )
2130 )
2128 )
2131
2129
2132
2130
2133 @jsonrpc_method()
2131 @jsonrpc_method()
2134 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
2132 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
2135 """
2133 """
2136 Revoke the permissions of a user group on a given repository.
2134 Revoke the permissions of a user group on a given repository.
2137
2135
2138 This command can only be run using an |authtoken| with admin
2136 This command can only be run using an |authtoken| with admin
2139 permissions on the |repo|.
2137 permissions on the |repo|.
2140
2138
2141 :param apiuser: This is filled automatically from the |authtoken|.
2139 :param apiuser: This is filled automatically from the |authtoken|.
2142 :type apiuser: AuthUser
2140 :type apiuser: AuthUser
2143 :param repoid: Set the repository name or repository ID.
2141 :param repoid: Set the repository name or repository ID.
2144 :type repoid: str or int
2142 :type repoid: str or int
2145 :param usergroupid: Specify the user group ID.
2143 :param usergroupid: Specify the user group ID.
2146 :type usergroupid: str or int
2144 :type usergroupid: str or int
2147
2145
2148 Example output:
2146 Example output:
2149
2147
2150 .. code-block:: bash
2148 .. code-block:: bash
2151
2149
2152 id : <id_given_in_input>
2150 id : <id_given_in_input>
2153 result: {
2151 result: {
2154 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
2152 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
2155 "success": true
2153 "success": true
2156 }
2154 }
2157 error: null
2155 error: null
2158 """
2156 """
2159
2157
2160 repo = get_repo_or_error(repoid)
2158 repo = get_repo_or_error(repoid)
2161 if not has_superadmin_permission(apiuser):
2159 if not has_superadmin_permission(apiuser):
2162 _perms = ('repository.admin',)
2160 _perms = ('repository.admin',)
2163 validate_repo_permissions(apiuser, repoid, repo, _perms)
2161 validate_repo_permissions(apiuser, repoid, repo, _perms)
2164
2162
2165 user_group = get_user_group_or_error(usergroupid)
2163 user_group = get_user_group_or_error(usergroupid)
2166 if not has_superadmin_permission(apiuser):
2164 if not has_superadmin_permission(apiuser):
2167 # check if we have at least read permission for this user group !
2165 # check if we have at least read permission for this user group !
2168 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
2166 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
2169 if not HasUserGroupPermissionAnyApi(*_perms)(
2167 if not HasUserGroupPermissionAnyApi(*_perms)(
2170 user=apiuser, user_group_name=user_group.users_group_name):
2168 user=apiuser, user_group_name=user_group.users_group_name):
2171 raise JSONRPCError(
2169 raise JSONRPCError(
2172 'user group `%s` does not exist' % (usergroupid,))
2170 'user group `%s` does not exist' % (usergroupid,))
2173
2171
2174 perm_deletions = [[user_group.users_group_id, None, "user_group"]]
2172 perm_deletions = [[user_group.users_group_id, None, "user_group"]]
2175 try:
2173 try:
2176 changes = RepoModel().update_permissions(
2174 changes = RepoModel().update_permissions(
2177 repo=repo, perm_deletions=perm_deletions, cur_user=apiuser)
2175 repo=repo, perm_deletions=perm_deletions, cur_user=apiuser)
2178 action_data = {
2176 action_data = {
2179 'added': changes['added'],
2177 'added': changes['added'],
2180 'updated': changes['updated'],
2178 'updated': changes['updated'],
2181 'deleted': changes['deleted'],
2179 'deleted': changes['deleted'],
2182 }
2180 }
2183 audit_logger.store_api(
2181 audit_logger.store_api(
2184 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
2182 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
2185 Session().commit()
2183 Session().commit()
2186 PermissionModel().flush_user_permission_caches(changes)
2184 PermissionModel().flush_user_permission_caches(changes)
2187
2185
2188 return {
2186 return {
2189 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
2187 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
2190 user_group.users_group_name, repo.repo_name
2188 user_group.users_group_name, repo.repo_name
2191 ),
2189 ),
2192 'success': True
2190 'success': True
2193 }
2191 }
2194 except Exception:
2192 except Exception:
2195 log.exception("Exception occurred while trying revoke "
2193 log.exception("Exception occurred while trying revoke "
2196 "user group permission on repo")
2194 "user group permission on repo")
2197 raise JSONRPCError(
2195 raise JSONRPCError(
2198 'failed to edit permission for user group: `%s` in '
2196 'failed to edit permission for user group: `%s` in '
2199 'repo: `%s`' % (
2197 'repo: `%s`' % (
2200 user_group.users_group_name, repo.repo_name
2198 user_group.users_group_name, repo.repo_name
2201 )
2199 )
2202 )
2200 )
2203
2201
2204
2202
2205 @jsonrpc_method()
2203 @jsonrpc_method()
2206 def pull(request, apiuser, repoid, remote_uri=Optional(None)):
2204 def pull(request, apiuser, repoid, remote_uri=Optional(None)):
2207 """
2205 """
2208 Triggers a pull on the given repository from a remote location. You
2206 Triggers a pull on the given repository from a remote location. You
2209 can use this to keep remote repositories up-to-date.
2207 can use this to keep remote repositories up-to-date.
2210
2208
2211 This command can only be run using an |authtoken| with admin
2209 This command can only be run using an |authtoken| with admin
2212 rights to the specified repository. For more information,
2210 rights to the specified repository. For more information,
2213 see :ref:`config-token-ref`.
2211 see :ref:`config-token-ref`.
2214
2212
2215 This command takes the following options:
2213 This command takes the following options:
2216
2214
2217 :param apiuser: This is filled automatically from the |authtoken|.
2215 :param apiuser: This is filled automatically from the |authtoken|.
2218 :type apiuser: AuthUser
2216 :type apiuser: AuthUser
2219 :param repoid: The repository name or repository ID.
2217 :param repoid: The repository name or repository ID.
2220 :type repoid: str or int
2218 :type repoid: str or int
2221 :param remote_uri: Optional remote URI to pass in for pull
2219 :param remote_uri: Optional remote URI to pass in for pull
2222 :type remote_uri: str
2220 :type remote_uri: str
2223
2221
2224 Example output:
2222 Example output:
2225
2223
2226 .. code-block:: bash
2224 .. code-block:: bash
2227
2225
2228 id : <id_given_in_input>
2226 id : <id_given_in_input>
2229 result : {
2227 result : {
2230 "msg": "Pulled from url `<remote_url>` on repo `<repository name>`"
2228 "msg": "Pulled from url `<remote_url>` on repo `<repository name>`"
2231 "repository": "<repository name>"
2229 "repository": "<repository name>"
2232 }
2230 }
2233 error : null
2231 error : null
2234
2232
2235 Example error output:
2233 Example error output:
2236
2234
2237 .. code-block:: bash
2235 .. code-block:: bash
2238
2236
2239 id : <id_given_in_input>
2237 id : <id_given_in_input>
2240 result : null
2238 result : null
2241 error : {
2239 error : {
2242 "Unable to push changes from `<remote_url>`"
2240 "Unable to push changes from `<remote_url>`"
2243 }
2241 }
2244
2242
2245 """
2243 """
2246
2244
2247 repo = get_repo_or_error(repoid)
2245 repo = get_repo_or_error(repoid)
2248 remote_uri = Optional.extract(remote_uri)
2246 remote_uri = Optional.extract(remote_uri)
2249 remote_uri_display = remote_uri or repo.clone_uri_hidden
2247 remote_uri_display = remote_uri or repo.clone_uri_hidden
2250 if not has_superadmin_permission(apiuser):
2248 if not has_superadmin_permission(apiuser):
2251 _perms = ('repository.admin',)
2249 _perms = ('repository.admin',)
2252 validate_repo_permissions(apiuser, repoid, repo, _perms)
2250 validate_repo_permissions(apiuser, repoid, repo, _perms)
2253
2251
2254 try:
2252 try:
2255 ScmModel().pull_changes(
2253 ScmModel().pull_changes(
2256 repo.repo_name, apiuser.username, remote_uri=remote_uri)
2254 repo.repo_name, apiuser.username, remote_uri=remote_uri)
2257 return {
2255 return {
2258 'msg': 'Pulled from url `%s` on repo `%s`' % (
2256 'msg': 'Pulled from url `%s` on repo `%s`' % (
2259 remote_uri_display, repo.repo_name),
2257 remote_uri_display, repo.repo_name),
2260 'repository': repo.repo_name
2258 'repository': repo.repo_name
2261 }
2259 }
2262 except Exception:
2260 except Exception:
2263 log.exception("Exception occurred while trying to "
2261 log.exception("Exception occurred while trying to "
2264 "pull changes from remote location")
2262 "pull changes from remote location")
2265 raise JSONRPCError(
2263 raise JSONRPCError(
2266 'Unable to pull changes from `%s`' % remote_uri_display
2264 'Unable to pull changes from `%s`' % remote_uri_display
2267 )
2265 )
2268
2266
2269
2267
2270 @jsonrpc_method()
2268 @jsonrpc_method()
2271 def strip(request, apiuser, repoid, revision, branch):
2269 def strip(request, apiuser, repoid, revision, branch):
2272 """
2270 """
2273 Strips the given revision from the specified repository.
2271 Strips the given revision from the specified repository.
2274
2272
2275 * This will remove the revision and all of its decendants.
2273 * This will remove the revision and all of its decendants.
2276
2274
2277 This command can only be run using an |authtoken| with admin rights to
2275 This command can only be run using an |authtoken| with admin rights to
2278 the specified repository.
2276 the specified repository.
2279
2277
2280 This command takes the following options:
2278 This command takes the following options:
2281
2279
2282 :param apiuser: This is filled automatically from the |authtoken|.
2280 :param apiuser: This is filled automatically from the |authtoken|.
2283 :type apiuser: AuthUser
2281 :type apiuser: AuthUser
2284 :param repoid: The repository name or repository ID.
2282 :param repoid: The repository name or repository ID.
2285 :type repoid: str or int
2283 :type repoid: str or int
2286 :param revision: The revision you wish to strip.
2284 :param revision: The revision you wish to strip.
2287 :type revision: str
2285 :type revision: str
2288 :param branch: The branch from which to strip the revision.
2286 :param branch: The branch from which to strip the revision.
2289 :type branch: str
2287 :type branch: str
2290
2288
2291 Example output:
2289 Example output:
2292
2290
2293 .. code-block:: bash
2291 .. code-block:: bash
2294
2292
2295 id : <id_given_in_input>
2293 id : <id_given_in_input>
2296 result : {
2294 result : {
2297 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
2295 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
2298 "repository": "<repository name>"
2296 "repository": "<repository name>"
2299 }
2297 }
2300 error : null
2298 error : null
2301
2299
2302 Example error output:
2300 Example error output:
2303
2301
2304 .. code-block:: bash
2302 .. code-block:: bash
2305
2303
2306 id : <id_given_in_input>
2304 id : <id_given_in_input>
2307 result : null
2305 result : null
2308 error : {
2306 error : {
2309 "Unable to strip commit <commit_hash> from repo `<repository name>`"
2307 "Unable to strip commit <commit_hash> from repo `<repository name>`"
2310 }
2308 }
2311
2309
2312 """
2310 """
2313
2311
2314 repo = get_repo_or_error(repoid)
2312 repo = get_repo_or_error(repoid)
2315 if not has_superadmin_permission(apiuser):
2313 if not has_superadmin_permission(apiuser):
2316 _perms = ('repository.admin',)
2314 _perms = ('repository.admin',)
2317 validate_repo_permissions(apiuser, repoid, repo, _perms)
2315 validate_repo_permissions(apiuser, repoid, repo, _perms)
2318
2316
2319 try:
2317 try:
2320 ScmModel().strip(repo, revision, branch)
2318 ScmModel().strip(repo, revision, branch)
2321 audit_logger.store_api(
2319 audit_logger.store_api(
2322 'repo.commit.strip', action_data={'commit_id': revision},
2320 'repo.commit.strip', action_data={'commit_id': revision},
2323 repo=repo,
2321 repo=repo,
2324 user=apiuser, commit=True)
2322 user=apiuser, commit=True)
2325
2323
2326 return {
2324 return {
2327 'msg': 'Stripped commit %s from repo `%s`' % (
2325 'msg': 'Stripped commit %s from repo `%s`' % (
2328 revision, repo.repo_name),
2326 revision, repo.repo_name),
2329 'repository': repo.repo_name
2327 'repository': repo.repo_name
2330 }
2328 }
2331 except Exception:
2329 except Exception:
2332 log.exception("Exception while trying to strip")
2330 log.exception("Exception while trying to strip")
2333 raise JSONRPCError(
2331 raise JSONRPCError(
2334 'Unable to strip commit %s from repo `%s`' % (
2332 'Unable to strip commit %s from repo `%s`' % (
2335 revision, repo.repo_name)
2333 revision, repo.repo_name)
2336 )
2334 )
2337
2335
2338
2336
2339 @jsonrpc_method()
2337 @jsonrpc_method()
2340 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
2338 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
2341 """
2339 """
2342 Returns all settings for a repository. If key is given it only returns the
2340 Returns all settings for a repository. If key is given it only returns the
2343 setting identified by the key or null.
2341 setting identified by the key or null.
2344
2342
2345 :param apiuser: This is filled automatically from the |authtoken|.
2343 :param apiuser: This is filled automatically from the |authtoken|.
2346 :type apiuser: AuthUser
2344 :type apiuser: AuthUser
2347 :param repoid: The repository name or repository id.
2345 :param repoid: The repository name or repository id.
2348 :type repoid: str or int
2346 :type repoid: str or int
2349 :param key: Key of the setting to return.
2347 :param key: Key of the setting to return.
2350 :type: key: Optional(str)
2348 :type: key: Optional(str)
2351
2349
2352 Example output:
2350 Example output:
2353
2351
2354 .. code-block:: bash
2352 .. code-block:: bash
2355
2353
2356 {
2354 {
2357 "error": null,
2355 "error": null,
2358 "id": 237,
2356 "id": 237,
2359 "result": {
2357 "result": {
2360 "extensions_largefiles": true,
2358 "extensions_largefiles": true,
2361 "extensions_evolve": true,
2359 "extensions_evolve": true,
2362 "hooks_changegroup_push_logger": true,
2360 "hooks_changegroup_push_logger": true,
2363 "hooks_changegroup_repo_size": false,
2361 "hooks_changegroup_repo_size": false,
2364 "hooks_outgoing_pull_logger": true,
2362 "hooks_outgoing_pull_logger": true,
2365 "phases_publish": "True",
2363 "phases_publish": "True",
2366 "rhodecode_hg_use_rebase_for_merging": true,
2364 "rhodecode_hg_use_rebase_for_merging": true,
2367 "rhodecode_pr_merge_enabled": true,
2365 "rhodecode_pr_merge_enabled": true,
2368 "rhodecode_use_outdated_comments": true
2366 "rhodecode_use_outdated_comments": true
2369 }
2367 }
2370 }
2368 }
2371 """
2369 """
2372
2370
2373 # Restrict access to this api method to admins only.
2371 # Restrict access to this api method to super-admins, and repo admins only.
2372 repo = get_repo_or_error(repoid)
2374 if not has_superadmin_permission(apiuser):
2373 if not has_superadmin_permission(apiuser):
2375 raise JSONRPCForbidden()
2374 _perms = ('repository.admin',)
2375 validate_repo_permissions(apiuser, repoid, repo, _perms)
2376
2376
2377 try:
2377 try:
2378 repo = get_repo_or_error(repoid)
2379 settings_model = VcsSettingsModel(repo=repo)
2378 settings_model = VcsSettingsModel(repo=repo)
2380 settings = settings_model.get_global_settings()
2379 settings = settings_model.get_global_settings()
2381 settings.update(settings_model.get_repo_settings())
2380 settings.update(settings_model.get_repo_settings())
2382
2381
2383 # If only a single setting is requested fetch it from all settings.
2382 # If only a single setting is requested fetch it from all settings.
2384 key = Optional.extract(key)
2383 key = Optional.extract(key)
2385 if key is not None:
2384 if key is not None:
2386 settings = settings.get(key, None)
2385 settings = settings.get(key, None)
2387 except Exception:
2386 except Exception:
2388 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
2387 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
2389 log.exception(msg)
2388 log.exception(msg)
2390 raise JSONRPCError(msg)
2389 raise JSONRPCError(msg)
2391
2390
2392 return settings
2391 return settings
2393
2392
2394
2393
2395 @jsonrpc_method()
2394 @jsonrpc_method()
2396 def set_repo_settings(request, apiuser, repoid, settings):
2395 def set_repo_settings(request, apiuser, repoid, settings):
2397 """
2396 """
2398 Update repository settings. Returns true on success.
2397 Update repository settings. Returns true on success.
2399
2398
2400 :param apiuser: This is filled automatically from the |authtoken|.
2399 :param apiuser: This is filled automatically from the |authtoken|.
2401 :type apiuser: AuthUser
2400 :type apiuser: AuthUser
2402 :param repoid: The repository name or repository id.
2401 :param repoid: The repository name or repository id.
2403 :type repoid: str or int
2402 :type repoid: str or int
2404 :param settings: The new settings for the repository.
2403 :param settings: The new settings for the repository.
2405 :type: settings: dict
2404 :type: settings: dict
2406
2405
2407 Example output:
2406 Example output:
2408
2407
2409 .. code-block:: bash
2408 .. code-block:: bash
2410
2409
2411 {
2410 {
2412 "error": null,
2411 "error": null,
2413 "id": 237,
2412 "id": 237,
2414 "result": true
2413 "result": true
2415 }
2414 }
2416 """
2415 """
2417 # Restrict access to this api method to admins only.
2416 # Restrict access to this api method to super-admins, and repo admins only.
2417 repo = get_repo_or_error(repoid)
2418 if not has_superadmin_permission(apiuser):
2418 if not has_superadmin_permission(apiuser):
2419 raise JSONRPCForbidden()
2419 _perms = ('repository.admin',)
2420 validate_repo_permissions(apiuser, repoid, repo, _perms)
2420
2421
2421 if type(settings) is not dict:
2422 if type(settings) is not dict:
2422 raise JSONRPCError('Settings have to be a JSON Object.')
2423 raise JSONRPCError('Settings have to be a JSON Object.')
2423
2424
2424 try:
2425 try:
2425 settings_model = VcsSettingsModel(repo=repoid)
2426 settings_model = VcsSettingsModel(repo=repoid)
2426
2427
2427 # Merge global, repo and incoming settings.
2428 # Merge global, repo and incoming settings.
2428 new_settings = settings_model.get_global_settings()
2429 new_settings = settings_model.get_global_settings()
2429 new_settings.update(settings_model.get_repo_settings())
2430 new_settings.update(settings_model.get_repo_settings())
2430 new_settings.update(settings)
2431 new_settings.update(settings)
2431
2432
2432 # Update the settings.
2433 # Update the settings.
2433 inherit_global_settings = new_settings.get(
2434 inherit_global_settings = new_settings.get(
2434 'inherit_global_settings', False)
2435 'inherit_global_settings', False)
2435 settings_model.create_or_update_repo_settings(
2436 settings_model.create_or_update_repo_settings(
2436 new_settings, inherit_global_settings=inherit_global_settings)
2437 new_settings, inherit_global_settings=inherit_global_settings)
2437 Session().commit()
2438 Session().commit()
2438 except Exception:
2439 except Exception:
2439 msg = 'Failed to update settings for repository `{}`'.format(repoid)
2440 msg = 'Failed to update settings for repository `{}`'.format(repoid)
2440 log.exception(msg)
2441 log.exception(msg)
2441 raise JSONRPCError(msg)
2442 raise JSONRPCError(msg)
2442
2443
2443 # Indicate success.
2444 # Indicate success.
2444 return True
2445 return True
2445
2446
2446
2447
2447 @jsonrpc_method()
2448 @jsonrpc_method()
2448 def maintenance(request, apiuser, repoid):
2449 def maintenance(request, apiuser, repoid):
2449 """
2450 """
2450 Triggers a maintenance on the given repository.
2451 Triggers a maintenance on the given repository.
2451
2452
2452 This command can only be run using an |authtoken| with admin
2453 This command can only be run using an |authtoken| with admin
2453 rights to the specified repository. For more information,
2454 rights to the specified repository. For more information,
2454 see :ref:`config-token-ref`.
2455 see :ref:`config-token-ref`.
2455
2456
2456 This command takes the following options:
2457 This command takes the following options:
2457
2458
2458 :param apiuser: This is filled automatically from the |authtoken|.
2459 :param apiuser: This is filled automatically from the |authtoken|.
2459 :type apiuser: AuthUser
2460 :type apiuser: AuthUser
2460 :param repoid: The repository name or repository ID.
2461 :param repoid: The repository name or repository ID.
2461 :type repoid: str or int
2462 :type repoid: str or int
2462
2463
2463 Example output:
2464 Example output:
2464
2465
2465 .. code-block:: bash
2466 .. code-block:: bash
2466
2467
2467 id : <id_given_in_input>
2468 id : <id_given_in_input>
2468 result : {
2469 result : {
2469 "msg": "executed maintenance command",
2470 "msg": "executed maintenance command",
2470 "executed_actions": [
2471 "executed_actions": [
2471 <action_message>, <action_message2>...
2472 <action_message>, <action_message2>...
2472 ],
2473 ],
2473 "repository": "<repository name>"
2474 "repository": "<repository name>"
2474 }
2475 }
2475 error : null
2476 error : null
2476
2477
2477 Example error output:
2478 Example error output:
2478
2479
2479 .. code-block:: bash
2480 .. code-block:: bash
2480
2481
2481 id : <id_given_in_input>
2482 id : <id_given_in_input>
2482 result : null
2483 result : null
2483 error : {
2484 error : {
2484 "Unable to execute maintenance on `<reponame>`"
2485 "Unable to execute maintenance on `<reponame>`"
2485 }
2486 }
2486
2487
2487 """
2488 """
2488
2489
2489 repo = get_repo_or_error(repoid)
2490 repo = get_repo_or_error(repoid)
2490 if not has_superadmin_permission(apiuser):
2491 if not has_superadmin_permission(apiuser):
2491 _perms = ('repository.admin',)
2492 _perms = ('repository.admin',)
2492 validate_repo_permissions(apiuser, repoid, repo, _perms)
2493 validate_repo_permissions(apiuser, repoid, repo, _perms)
2493
2494
2494 try:
2495 try:
2495 maintenance = repo_maintenance.RepoMaintenance()
2496 maintenance = repo_maintenance.RepoMaintenance()
2496 executed_actions = maintenance.execute(repo)
2497 executed_actions = maintenance.execute(repo)
2497
2498
2498 return {
2499 return {
2499 'msg': 'executed maintenance command',
2500 'msg': 'executed maintenance command',
2500 'executed_actions': executed_actions,
2501 'executed_actions': executed_actions,
2501 'repository': repo.repo_name
2502 'repository': repo.repo_name
2502 }
2503 }
2503 except Exception:
2504 except Exception:
2504 log.exception("Exception occurred while trying to run maintenance")
2505 log.exception("Exception occurred while trying to run maintenance")
2505 raise JSONRPCError(
2506 raise JSONRPCError(
2506 'Unable to execute maintenance on `%s`' % repo.repo_name)
2507 'Unable to execute maintenance on `%s`' % repo.repo_name)
General Comments 0
You need to be logged in to leave comments. Login now