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