##// END OF EJS Templates
api: report better error when fetching a node that doesn't exist.
marcink -
r3490:dcd7ad80 default
parent child
Show More
@@ -1,2311 +1,2314
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import time
22 import time
23
23
24 import rhodecode
24 import rhodecode
25 from rhodecode.api import (
25 from rhodecode.api import (
26 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
26 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
27 from rhodecode.api.utils import (
27 from rhodecode.api.utils import (
28 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
28 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
29 get_user_group_or_error, get_user_or_error, validate_repo_permissions,
29 get_user_group_or_error, get_user_or_error, validate_repo_permissions,
30 get_perm_or_error, parse_args, get_origin, build_commit_data,
30 get_perm_or_error, parse_args, get_origin, build_commit_data,
31 validate_set_owner_permissions)
31 validate_set_owner_permissions)
32 from rhodecode.lib import audit_logger, rc_cache
32 from rhodecode.lib import audit_logger, rc_cache
33 from rhodecode.lib import repo_maintenance
33 from rhodecode.lib import repo_maintenance
34 from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi
34 from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi
35 from rhodecode.lib.celerylib.utils import get_task_id
35 from rhodecode.lib.celerylib.utils import get_task_id
36 from rhodecode.lib.utils2 import str2bool, time_to_datetime, safe_str, safe_int
36 from rhodecode.lib.utils2 import str2bool, time_to_datetime, safe_str, safe_int
37 from rhodecode.lib.ext_json import json
37 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
38 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
39 from rhodecode.lib.vcs import RepositoryError
39 from rhodecode.lib.vcs import RepositoryError
40 from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError
40 from rhodecode.model.changeset_status import ChangesetStatusModel
41 from rhodecode.model.changeset_status import ChangesetStatusModel
41 from rhodecode.model.comment import CommentsModel
42 from rhodecode.model.comment import CommentsModel
42 from rhodecode.model.db import (
43 from rhodecode.model.db import (
43 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup,
44 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup,
44 ChangesetComment)
45 ChangesetComment)
45 from rhodecode.model.repo import RepoModel
46 from rhodecode.model.repo import RepoModel
46 from rhodecode.model.scm import ScmModel, RepoList
47 from rhodecode.model.scm import ScmModel, RepoList
47 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
48 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
48 from rhodecode.model import validation_schema
49 from rhodecode.model import validation_schema
49 from rhodecode.model.validation_schema.schemas import repo_schema
50 from rhodecode.model.validation_schema.schemas import repo_schema
50
51
51 log = logging.getLogger(__name__)
52 log = logging.getLogger(__name__)
52
53
53
54
54 @jsonrpc_method()
55 @jsonrpc_method()
55 def get_repo(request, apiuser, repoid, cache=Optional(True)):
56 def get_repo(request, apiuser, repoid, cache=Optional(True)):
56 """
57 """
57 Gets an existing repository by its name or repository_id.
58 Gets an existing repository by its name or repository_id.
58
59
59 The members section so the output returns users groups or users
60 The members section so the output returns users groups or users
60 associated with that repository.
61 associated with that repository.
61
62
62 This command can only be run using an |authtoken| with admin rights,
63 This command can only be run using an |authtoken| with admin rights,
63 or users with at least read rights to the |repo|.
64 or users with at least read rights to the |repo|.
64
65
65 :param apiuser: This is filled automatically from the |authtoken|.
66 :param apiuser: This is filled automatically from the |authtoken|.
66 :type apiuser: AuthUser
67 :type apiuser: AuthUser
67 :param repoid: The repository name or repository id.
68 :param repoid: The repository name or repository id.
68 :type repoid: str or int
69 :type repoid: str or int
69 :param cache: use the cached value for last changeset
70 :param cache: use the cached value for last changeset
70 :type: cache: Optional(bool)
71 :type: cache: Optional(bool)
71
72
72 Example output:
73 Example output:
73
74
74 .. code-block:: bash
75 .. code-block:: bash
75
76
76 {
77 {
77 "error": null,
78 "error": null,
78 "id": <repo_id>,
79 "id": <repo_id>,
79 "result": {
80 "result": {
80 "clone_uri": null,
81 "clone_uri": null,
81 "created_on": "timestamp",
82 "created_on": "timestamp",
82 "description": "repo description",
83 "description": "repo description",
83 "enable_downloads": false,
84 "enable_downloads": false,
84 "enable_locking": false,
85 "enable_locking": false,
85 "enable_statistics": false,
86 "enable_statistics": false,
86 "followers": [
87 "followers": [
87 {
88 {
88 "active": true,
89 "active": true,
89 "admin": false,
90 "admin": false,
90 "api_key": "****************************************",
91 "api_key": "****************************************",
91 "api_keys": [
92 "api_keys": [
92 "****************************************"
93 "****************************************"
93 ],
94 ],
94 "email": "user@example.com",
95 "email": "user@example.com",
95 "emails": [
96 "emails": [
96 "user@example.com"
97 "user@example.com"
97 ],
98 ],
98 "extern_name": "rhodecode",
99 "extern_name": "rhodecode",
99 "extern_type": "rhodecode",
100 "extern_type": "rhodecode",
100 "firstname": "username",
101 "firstname": "username",
101 "ip_addresses": [],
102 "ip_addresses": [],
102 "language": null,
103 "language": null,
103 "last_login": "2015-09-16T17:16:35.854",
104 "last_login": "2015-09-16T17:16:35.854",
104 "lastname": "surname",
105 "lastname": "surname",
105 "user_id": <user_id>,
106 "user_id": <user_id>,
106 "username": "name"
107 "username": "name"
107 }
108 }
108 ],
109 ],
109 "fork_of": "parent-repo",
110 "fork_of": "parent-repo",
110 "landing_rev": [
111 "landing_rev": [
111 "rev",
112 "rev",
112 "tip"
113 "tip"
113 ],
114 ],
114 "last_changeset": {
115 "last_changeset": {
115 "author": "User <user@example.com>",
116 "author": "User <user@example.com>",
116 "branch": "default",
117 "branch": "default",
117 "date": "timestamp",
118 "date": "timestamp",
118 "message": "last commit message",
119 "message": "last commit message",
119 "parents": [
120 "parents": [
120 {
121 {
121 "raw_id": "commit-id"
122 "raw_id": "commit-id"
122 }
123 }
123 ],
124 ],
124 "raw_id": "commit-id",
125 "raw_id": "commit-id",
125 "revision": <revision number>,
126 "revision": <revision number>,
126 "short_id": "short id"
127 "short_id": "short id"
127 },
128 },
128 "lock_reason": null,
129 "lock_reason": null,
129 "locked_by": null,
130 "locked_by": null,
130 "locked_date": null,
131 "locked_date": null,
131 "owner": "owner-name",
132 "owner": "owner-name",
132 "permissions": [
133 "permissions": [
133 {
134 {
134 "name": "super-admin-name",
135 "name": "super-admin-name",
135 "origin": "super-admin",
136 "origin": "super-admin",
136 "permission": "repository.admin",
137 "permission": "repository.admin",
137 "type": "user"
138 "type": "user"
138 },
139 },
139 {
140 {
140 "name": "owner-name",
141 "name": "owner-name",
141 "origin": "owner",
142 "origin": "owner",
142 "permission": "repository.admin",
143 "permission": "repository.admin",
143 "type": "user"
144 "type": "user"
144 },
145 },
145 {
146 {
146 "name": "user-group-name",
147 "name": "user-group-name",
147 "origin": "permission",
148 "origin": "permission",
148 "permission": "repository.write",
149 "permission": "repository.write",
149 "type": "user_group"
150 "type": "user_group"
150 }
151 }
151 ],
152 ],
152 "private": true,
153 "private": true,
153 "repo_id": 676,
154 "repo_id": 676,
154 "repo_name": "user-group/repo-name",
155 "repo_name": "user-group/repo-name",
155 "repo_type": "hg"
156 "repo_type": "hg"
156 }
157 }
157 }
158 }
158 """
159 """
159
160
160 repo = get_repo_or_error(repoid)
161 repo = get_repo_or_error(repoid)
161 cache = Optional.extract(cache)
162 cache = Optional.extract(cache)
162
163
163 include_secrets = False
164 include_secrets = False
164 if has_superadmin_permission(apiuser):
165 if has_superadmin_permission(apiuser):
165 include_secrets = True
166 include_secrets = True
166 else:
167 else:
167 # check if we have at least read permission for this repo !
168 # check if we have at least read permission for this repo !
168 _perms = (
169 _perms = (
169 'repository.admin', 'repository.write', 'repository.read',)
170 'repository.admin', 'repository.write', 'repository.read',)
170 validate_repo_permissions(apiuser, repoid, repo, _perms)
171 validate_repo_permissions(apiuser, repoid, repo, _perms)
171
172
172 permissions = []
173 permissions = []
173 for _user in repo.permissions():
174 for _user in repo.permissions():
174 user_data = {
175 user_data = {
175 'name': _user.username,
176 'name': _user.username,
176 'permission': _user.permission,
177 'permission': _user.permission,
177 'origin': get_origin(_user),
178 'origin': get_origin(_user),
178 'type': "user",
179 'type': "user",
179 }
180 }
180 permissions.append(user_data)
181 permissions.append(user_data)
181
182
182 for _user_group in repo.permission_user_groups():
183 for _user_group in repo.permission_user_groups():
183 user_group_data = {
184 user_group_data = {
184 'name': _user_group.users_group_name,
185 'name': _user_group.users_group_name,
185 'permission': _user_group.permission,
186 'permission': _user_group.permission,
186 'origin': get_origin(_user_group),
187 'origin': get_origin(_user_group),
187 'type': "user_group",
188 'type': "user_group",
188 }
189 }
189 permissions.append(user_group_data)
190 permissions.append(user_group_data)
190
191
191 following_users = [
192 following_users = [
192 user.user.get_api_data(include_secrets=include_secrets)
193 user.user.get_api_data(include_secrets=include_secrets)
193 for user in repo.followers]
194 for user in repo.followers]
194
195
195 if not cache:
196 if not cache:
196 repo.update_commit_cache()
197 repo.update_commit_cache()
197 data = repo.get_api_data(include_secrets=include_secrets)
198 data = repo.get_api_data(include_secrets=include_secrets)
198 data['permissions'] = permissions
199 data['permissions'] = permissions
199 data['followers'] = following_users
200 data['followers'] = following_users
200 return data
201 return data
201
202
202
203
203 @jsonrpc_method()
204 @jsonrpc_method()
204 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
205 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
205 """
206 """
206 Lists all existing repositories.
207 Lists all existing repositories.
207
208
208 This command can only be run using an |authtoken| with admin rights,
209 This command can only be run using an |authtoken| with admin rights,
209 or users with at least read rights to |repos|.
210 or users with at least read rights to |repos|.
210
211
211 :param apiuser: This is filled automatically from the |authtoken|.
212 :param apiuser: This is filled automatically from the |authtoken|.
212 :type apiuser: AuthUser
213 :type apiuser: AuthUser
213 :param root: specify root repository group to fetch repositories.
214 :param root: specify root repository group to fetch repositories.
214 filters the returned repositories to be members of given root group.
215 filters the returned repositories to be members of given root group.
215 :type root: Optional(None)
216 :type root: Optional(None)
216 :param traverse: traverse given root into subrepositories. With this flag
217 :param traverse: traverse given root into subrepositories. With this flag
217 set to False, it will only return top-level repositories from `root`.
218 set to False, it will only return top-level repositories from `root`.
218 if root is empty it will return just top-level repositories.
219 if root is empty it will return just top-level repositories.
219 :type traverse: Optional(True)
220 :type traverse: Optional(True)
220
221
221
222
222 Example output:
223 Example output:
223
224
224 .. code-block:: bash
225 .. code-block:: bash
225
226
226 id : <id_given_in_input>
227 id : <id_given_in_input>
227 result: [
228 result: [
228 {
229 {
229 "repo_id" : "<repo_id>",
230 "repo_id" : "<repo_id>",
230 "repo_name" : "<reponame>"
231 "repo_name" : "<reponame>"
231 "repo_type" : "<repo_type>",
232 "repo_type" : "<repo_type>",
232 "clone_uri" : "<clone_uri>",
233 "clone_uri" : "<clone_uri>",
233 "private": : "<bool>",
234 "private": : "<bool>",
234 "created_on" : "<datetimecreated>",
235 "created_on" : "<datetimecreated>",
235 "description" : "<description>",
236 "description" : "<description>",
236 "landing_rev": "<landing_rev>",
237 "landing_rev": "<landing_rev>",
237 "owner": "<repo_owner>",
238 "owner": "<repo_owner>",
238 "fork_of": "<name_of_fork_parent>",
239 "fork_of": "<name_of_fork_parent>",
239 "enable_downloads": "<bool>",
240 "enable_downloads": "<bool>",
240 "enable_locking": "<bool>",
241 "enable_locking": "<bool>",
241 "enable_statistics": "<bool>",
242 "enable_statistics": "<bool>",
242 },
243 },
243 ...
244 ...
244 ]
245 ]
245 error: null
246 error: null
246 """
247 """
247
248
248 include_secrets = has_superadmin_permission(apiuser)
249 include_secrets = has_superadmin_permission(apiuser)
249 _perms = ('repository.read', 'repository.write', 'repository.admin',)
250 _perms = ('repository.read', 'repository.write', 'repository.admin',)
250 extras = {'user': apiuser}
251 extras = {'user': apiuser}
251
252
252 root = Optional.extract(root)
253 root = Optional.extract(root)
253 traverse = Optional.extract(traverse, binary=True)
254 traverse = Optional.extract(traverse, binary=True)
254
255
255 if root:
256 if root:
256 # verify parent existance, if it's empty return an error
257 # verify parent existance, if it's empty return an error
257 parent = RepoGroup.get_by_group_name(root)
258 parent = RepoGroup.get_by_group_name(root)
258 if not parent:
259 if not parent:
259 raise JSONRPCError(
260 raise JSONRPCError(
260 'Root repository group `{}` does not exist'.format(root))
261 'Root repository group `{}` does not exist'.format(root))
261
262
262 if traverse:
263 if traverse:
263 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
264 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
264 else:
265 else:
265 repos = RepoModel().get_repos_for_root(root=parent)
266 repos = RepoModel().get_repos_for_root(root=parent)
266 else:
267 else:
267 if traverse:
268 if traverse:
268 repos = RepoModel().get_all()
269 repos = RepoModel().get_all()
269 else:
270 else:
270 # return just top-level
271 # return just top-level
271 repos = RepoModel().get_repos_for_root(root=None)
272 repos = RepoModel().get_repos_for_root(root=None)
272
273
273 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
274 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
274 return [repo.get_api_data(include_secrets=include_secrets)
275 return [repo.get_api_data(include_secrets=include_secrets)
275 for repo in repo_list]
276 for repo in repo_list]
276
277
277
278
278 @jsonrpc_method()
279 @jsonrpc_method()
279 def get_repo_changeset(request, apiuser, repoid, revision,
280 def get_repo_changeset(request, apiuser, repoid, revision,
280 details=Optional('basic')):
281 details=Optional('basic')):
281 """
282 """
282 Returns information about a changeset.
283 Returns information about a changeset.
283
284
284 Additionally parameters define the amount of details returned by
285 Additionally parameters define the amount of details returned by
285 this function.
286 this function.
286
287
287 This command can only be run using an |authtoken| with admin rights,
288 This command can only be run using an |authtoken| with admin rights,
288 or users with at least read rights to the |repo|.
289 or users with at least read rights to the |repo|.
289
290
290 :param apiuser: This is filled automatically from the |authtoken|.
291 :param apiuser: This is filled automatically from the |authtoken|.
291 :type apiuser: AuthUser
292 :type apiuser: AuthUser
292 :param repoid: The repository name or repository id
293 :param repoid: The repository name or repository id
293 :type repoid: str or int
294 :type repoid: str or int
294 :param revision: revision for which listing should be done
295 :param revision: revision for which listing should be done
295 :type revision: str
296 :type revision: str
296 :param details: details can be 'basic|extended|full' full gives diff
297 :param details: details can be 'basic|extended|full' full gives diff
297 info details like the diff itself, and number of changed files etc.
298 info details like the diff itself, and number of changed files etc.
298 :type details: Optional(str)
299 :type details: Optional(str)
299
300
300 """
301 """
301 repo = get_repo_or_error(repoid)
302 repo = get_repo_or_error(repoid)
302 if not has_superadmin_permission(apiuser):
303 if not has_superadmin_permission(apiuser):
303 _perms = (
304 _perms = (
304 'repository.admin', 'repository.write', 'repository.read',)
305 'repository.admin', 'repository.write', 'repository.read',)
305 validate_repo_permissions(apiuser, repoid, repo, _perms)
306 validate_repo_permissions(apiuser, repoid, repo, _perms)
306
307
307 changes_details = Optional.extract(details)
308 changes_details = Optional.extract(details)
308 _changes_details_types = ['basic', 'extended', 'full']
309 _changes_details_types = ['basic', 'extended', 'full']
309 if changes_details not in _changes_details_types:
310 if changes_details not in _changes_details_types:
310 raise JSONRPCError(
311 raise JSONRPCError(
311 'ret_type must be one of %s' % (
312 'ret_type must be one of %s' % (
312 ','.join(_changes_details_types)))
313 ','.join(_changes_details_types)))
313
314
314 pre_load = ['author', 'branch', 'date', 'message', 'parents',
315 pre_load = ['author', 'branch', 'date', 'message', 'parents',
315 'status', '_commit', '_file_paths']
316 'status', '_commit', '_file_paths']
316
317
317 try:
318 try:
318 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
319 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
319 except TypeError as e:
320 except TypeError as e:
320 raise JSONRPCError(safe_str(e))
321 raise JSONRPCError(safe_str(e))
321 _cs_json = cs.__json__()
322 _cs_json = cs.__json__()
322 _cs_json['diff'] = build_commit_data(cs, changes_details)
323 _cs_json['diff'] = build_commit_data(cs, changes_details)
323 if changes_details == 'full':
324 if changes_details == 'full':
324 _cs_json['refs'] = cs._get_refs()
325 _cs_json['refs'] = cs._get_refs()
325 return _cs_json
326 return _cs_json
326
327
327
328
328 @jsonrpc_method()
329 @jsonrpc_method()
329 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
330 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
330 details=Optional('basic')):
331 details=Optional('basic')):
331 """
332 """
332 Returns a set of commits limited by the number starting
333 Returns a set of commits limited by the number starting
333 from the `start_rev` option.
334 from the `start_rev` option.
334
335
335 Additional parameters define the amount of details returned by this
336 Additional parameters define the amount of details returned by this
336 function.
337 function.
337
338
338 This command can only be run using an |authtoken| with admin rights,
339 This command can only be run using an |authtoken| with admin rights,
339 or users with at least read rights to |repos|.
340 or users with at least read rights to |repos|.
340
341
341 :param apiuser: This is filled automatically from the |authtoken|.
342 :param apiuser: This is filled automatically from the |authtoken|.
342 :type apiuser: AuthUser
343 :type apiuser: AuthUser
343 :param repoid: The repository name or repository ID.
344 :param repoid: The repository name or repository ID.
344 :type repoid: str or int
345 :type repoid: str or int
345 :param start_rev: The starting revision from where to get changesets.
346 :param start_rev: The starting revision from where to get changesets.
346 :type start_rev: str
347 :type start_rev: str
347 :param limit: Limit the number of commits to this amount
348 :param limit: Limit the number of commits to this amount
348 :type limit: str or int
349 :type limit: str or int
349 :param details: Set the level of detail returned. Valid option are:
350 :param details: Set the level of detail returned. Valid option are:
350 ``basic``, ``extended`` and ``full``.
351 ``basic``, ``extended`` and ``full``.
351 :type details: Optional(str)
352 :type details: Optional(str)
352
353
353 .. note::
354 .. note::
354
355
355 Setting the parameter `details` to the value ``full`` is extensive
356 Setting the parameter `details` to the value ``full`` is extensive
356 and returns details like the diff itself, and the number
357 and returns details like the diff itself, and the number
357 of changed files.
358 of changed files.
358
359
359 """
360 """
360 repo = get_repo_or_error(repoid)
361 repo = get_repo_or_error(repoid)
361 if not has_superadmin_permission(apiuser):
362 if not has_superadmin_permission(apiuser):
362 _perms = (
363 _perms = (
363 'repository.admin', 'repository.write', 'repository.read',)
364 'repository.admin', 'repository.write', 'repository.read',)
364 validate_repo_permissions(apiuser, repoid, repo, _perms)
365 validate_repo_permissions(apiuser, repoid, repo, _perms)
365
366
366 changes_details = Optional.extract(details)
367 changes_details = Optional.extract(details)
367 _changes_details_types = ['basic', 'extended', 'full']
368 _changes_details_types = ['basic', 'extended', 'full']
368 if changes_details not in _changes_details_types:
369 if changes_details not in _changes_details_types:
369 raise JSONRPCError(
370 raise JSONRPCError(
370 'ret_type must be one of %s' % (
371 'ret_type must be one of %s' % (
371 ','.join(_changes_details_types)))
372 ','.join(_changes_details_types)))
372
373
373 limit = int(limit)
374 limit = int(limit)
374 pre_load = ['author', 'branch', 'date', 'message', 'parents',
375 pre_load = ['author', 'branch', 'date', 'message', 'parents',
375 'status', '_commit', '_file_paths']
376 'status', '_commit', '_file_paths']
376
377
377 vcs_repo = repo.scm_instance()
378 vcs_repo = repo.scm_instance()
378 # SVN needs a special case to distinguish its index and commit id
379 # SVN needs a special case to distinguish its index and commit id
379 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
380 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
380 start_rev = vcs_repo.commit_ids[0]
381 start_rev = vcs_repo.commit_ids[0]
381
382
382 try:
383 try:
383 commits = vcs_repo.get_commits(
384 commits = vcs_repo.get_commits(
384 start_id=start_rev, pre_load=pre_load, translate_tags=False)
385 start_id=start_rev, pre_load=pre_load, translate_tags=False)
385 except TypeError as e:
386 except TypeError as e:
386 raise JSONRPCError(safe_str(e))
387 raise JSONRPCError(safe_str(e))
387 except Exception:
388 except Exception:
388 log.exception('Fetching of commits failed')
389 log.exception('Fetching of commits failed')
389 raise JSONRPCError('Error occurred during commit fetching')
390 raise JSONRPCError('Error occurred during commit fetching')
390
391
391 ret = []
392 ret = []
392 for cnt, commit in enumerate(commits):
393 for cnt, commit in enumerate(commits):
393 if cnt >= limit != -1:
394 if cnt >= limit != -1:
394 break
395 break
395 _cs_json = commit.__json__()
396 _cs_json = commit.__json__()
396 _cs_json['diff'] = build_commit_data(commit, changes_details)
397 _cs_json['diff'] = build_commit_data(commit, changes_details)
397 if changes_details == 'full':
398 if changes_details == 'full':
398 _cs_json['refs'] = {
399 _cs_json['refs'] = {
399 'branches': [commit.branch],
400 'branches': [commit.branch],
400 'bookmarks': getattr(commit, 'bookmarks', []),
401 'bookmarks': getattr(commit, 'bookmarks', []),
401 'tags': commit.tags
402 'tags': commit.tags
402 }
403 }
403 ret.append(_cs_json)
404 ret.append(_cs_json)
404 return ret
405 return ret
405
406
406
407
407 @jsonrpc_method()
408 @jsonrpc_method()
408 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
409 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
409 ret_type=Optional('all'), details=Optional('basic'),
410 ret_type=Optional('all'), details=Optional('basic'),
410 max_file_bytes=Optional(None)):
411 max_file_bytes=Optional(None)):
411 """
412 """
412 Returns a list of nodes and children in a flat list for a given
413 Returns a list of nodes and children in a flat list for a given
413 path at given revision.
414 path at given revision.
414
415
415 It's possible to specify ret_type to show only `files` or `dirs`.
416 It's possible to specify ret_type to show only `files` or `dirs`.
416
417
417 This command can only be run using an |authtoken| with admin rights,
418 This command can only be run using an |authtoken| with admin rights,
418 or users with at least read rights to |repos|.
419 or users with at least read rights to |repos|.
419
420
420 :param apiuser: This is filled automatically from the |authtoken|.
421 :param apiuser: This is filled automatically from the |authtoken|.
421 :type apiuser: AuthUser
422 :type apiuser: AuthUser
422 :param repoid: The repository name or repository ID.
423 :param repoid: The repository name or repository ID.
423 :type repoid: str or int
424 :type repoid: str or int
424 :param revision: The revision for which listing should be done.
425 :param revision: The revision for which listing should be done.
425 :type revision: str
426 :type revision: str
426 :param root_path: The path from which to start displaying.
427 :param root_path: The path from which to start displaying.
427 :type root_path: str
428 :type root_path: str
428 :param ret_type: Set the return type. Valid options are
429 :param ret_type: Set the return type. Valid options are
429 ``all`` (default), ``files`` and ``dirs``.
430 ``all`` (default), ``files`` and ``dirs``.
430 :type ret_type: Optional(str)
431 :type ret_type: Optional(str)
431 :param details: Returns extended information about nodes, such as
432 :param details: Returns extended information about nodes, such as
432 md5, binary, and or content.
433 md5, binary, and or content.
433 The valid options are ``basic`` and ``full``.
434 The valid options are ``basic`` and ``full``.
434 :type details: Optional(str)
435 :type details: Optional(str)
435 :param max_file_bytes: Only return file content under this file size bytes
436 :param max_file_bytes: Only return file content under this file size bytes
436 :type details: Optional(int)
437 :type details: Optional(int)
437
438
438 Example output:
439 Example output:
439
440
440 .. code-block:: bash
441 .. code-block:: bash
441
442
442 id : <id_given_in_input>
443 id : <id_given_in_input>
443 result: [
444 result: [
444 {
445 {
445 "binary": false,
446 "binary": false,
446 "content": "File line\nLine2\n",
447 "content": "File line\nLine2\n",
447 "extension": "md",
448 "extension": "md",
448 "lines": 2,
449 "lines": 2,
449 "md5": "059fa5d29b19c0657e384749480f6422",
450 "md5": "059fa5d29b19c0657e384749480f6422",
450 "mimetype": "text/x-minidsrc",
451 "mimetype": "text/x-minidsrc",
451 "name": "file.md",
452 "name": "file.md",
452 "size": 580,
453 "size": 580,
453 "type": "file"
454 "type": "file"
454 },
455 },
455 ...
456 ...
456 ]
457 ]
457 error: null
458 error: null
458 """
459 """
459
460
460 repo = get_repo_or_error(repoid)
461 repo = get_repo_or_error(repoid)
461 if not has_superadmin_permission(apiuser):
462 if not has_superadmin_permission(apiuser):
462 _perms = ('repository.admin', 'repository.write', 'repository.read',)
463 _perms = ('repository.admin', 'repository.write', 'repository.read',)
463 validate_repo_permissions(apiuser, repoid, repo, _perms)
464 validate_repo_permissions(apiuser, repoid, repo, _perms)
464
465
465 ret_type = Optional.extract(ret_type)
466 ret_type = Optional.extract(ret_type)
466 details = Optional.extract(details)
467 details = Optional.extract(details)
467 _extended_types = ['basic', 'full']
468 _extended_types = ['basic', 'full']
468 if details not in _extended_types:
469 if details not in _extended_types:
469 raise JSONRPCError('ret_type must be one of %s' % (','.join(_extended_types)))
470 raise JSONRPCError('ret_type must be one of %s' % (','.join(_extended_types)))
470 extended_info = False
471 extended_info = False
471 content = False
472 content = False
472 if details == 'basic':
473 if details == 'basic':
473 extended_info = True
474 extended_info = True
474
475
475 if details == 'full':
476 if details == 'full':
476 extended_info = content = True
477 extended_info = content = True
477
478
478 _map = {}
479 _map = {}
479 try:
480 try:
480 # check if repo is not empty by any chance, skip quicker if it is.
481 # check if repo is not empty by any chance, skip quicker if it is.
481 _scm = repo.scm_instance()
482 _scm = repo.scm_instance()
482 if _scm.is_empty():
483 if _scm.is_empty():
483 return []
484 return []
484
485
485 _d, _f = ScmModel().get_nodes(
486 _d, _f = ScmModel().get_nodes(
486 repo, revision, root_path, flat=False,
487 repo, revision, root_path, flat=False,
487 extended_info=extended_info, content=content,
488 extended_info=extended_info, content=content,
488 max_file_bytes=max_file_bytes)
489 max_file_bytes=max_file_bytes)
489 _map = {
490 _map = {
490 'all': _d + _f,
491 'all': _d + _f,
491 'files': _f,
492 'files': _f,
492 'dirs': _d,
493 'dirs': _d,
493 }
494 }
494 return _map[ret_type]
495 return _map[ret_type]
495 except KeyError:
496 except KeyError:
496 raise JSONRPCError(
497 raise JSONRPCError(
497 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
498 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
498 except Exception:
499 except Exception:
499 log.exception("Exception occurred while trying to get repo nodes")
500 log.exception("Exception occurred while trying to get repo nodes")
500 raise JSONRPCError(
501 raise JSONRPCError(
501 'failed to get repo: `%s` nodes' % repo.repo_name
502 'failed to get repo: `%s` nodes' % repo.repo_name
502 )
503 )
503
504
504
505
505 @jsonrpc_method()
506 @jsonrpc_method()
506 def get_repo_file(request, apiuser, repoid, commit_id, file_path,
507 def get_repo_file(request, apiuser, repoid, commit_id, file_path,
507 max_file_bytes=Optional(None), details=Optional('basic'),
508 max_file_bytes=Optional(None), details=Optional('basic'),
508 cache=Optional(True)):
509 cache=Optional(True)):
509 """
510 """
510 Returns a single file from repository at given revision.
511 Returns a single file from repository at given revision.
511
512
512 This command can only be run using an |authtoken| with admin rights,
513 This command can only be run using an |authtoken| with admin rights,
513 or users with at least read rights to |repos|.
514 or users with at least read rights to |repos|.
514
515
515 :param apiuser: This is filled automatically from the |authtoken|.
516 :param apiuser: This is filled automatically from the |authtoken|.
516 :type apiuser: AuthUser
517 :type apiuser: AuthUser
517 :param repoid: The repository name or repository ID.
518 :param repoid: The repository name or repository ID.
518 :type repoid: str or int
519 :type repoid: str or int
519 :param commit_id: The revision for which listing should be done.
520 :param commit_id: The revision for which listing should be done.
520 :type commit_id: str
521 :type commit_id: str
521 :param file_path: The path from which to start displaying.
522 :param file_path: The path from which to start displaying.
522 :type file_path: str
523 :type file_path: str
523 :param details: Returns different set of information about nodes.
524 :param details: Returns different set of information about nodes.
524 The valid options are ``minimal`` ``basic`` and ``full``.
525 The valid options are ``minimal`` ``basic`` and ``full``.
525 :type details: Optional(str)
526 :type details: Optional(str)
526 :param max_file_bytes: Only return file content under this file size bytes
527 :param max_file_bytes: Only return file content under this file size bytes
527 :type max_file_bytes: Optional(int)
528 :type max_file_bytes: Optional(int)
528 :param cache: Use internal caches for fetching files. If disabled fetching
529 :param cache: Use internal caches for fetching files. If disabled fetching
529 files is slower but more memory efficient
530 files is slower but more memory efficient
530 :type cache: Optional(bool)
531 :type cache: Optional(bool)
531 Example output:
532 Example output:
532
533
533 .. code-block:: bash
534 .. code-block:: bash
534
535
535 id : <id_given_in_input>
536 id : <id_given_in_input>
536 result: {
537 result: {
537 "binary": false,
538 "binary": false,
538 "extension": "py",
539 "extension": "py",
539 "lines": 35,
540 "lines": 35,
540 "content": "....",
541 "content": "....",
541 "md5": "76318336366b0f17ee249e11b0c99c41",
542 "md5": "76318336366b0f17ee249e11b0c99c41",
542 "mimetype": "text/x-python",
543 "mimetype": "text/x-python",
543 "name": "python.py",
544 "name": "python.py",
544 "size": 817,
545 "size": 817,
545 "type": "file",
546 "type": "file",
546 }
547 }
547 error: null
548 error: null
548 """
549 """
549
550
550 repo = get_repo_or_error(repoid)
551 repo = get_repo_or_error(repoid)
551 if not has_superadmin_permission(apiuser):
552 if not has_superadmin_permission(apiuser):
552 _perms = ('repository.admin', 'repository.write', 'repository.read',)
553 _perms = ('repository.admin', 'repository.write', 'repository.read',)
553 validate_repo_permissions(apiuser, repoid, repo, _perms)
554 validate_repo_permissions(apiuser, repoid, repo, _perms)
554
555
555 cache = Optional.extract(cache, binary=True)
556 cache = Optional.extract(cache, binary=True)
556 details = Optional.extract(details)
557 details = Optional.extract(details)
557 _extended_types = ['minimal', 'minimal+search', 'basic', 'full']
558 _extended_types = ['minimal', 'minimal+search', 'basic', 'full']
558 if details not in _extended_types:
559 if details not in _extended_types:
559 raise JSONRPCError(
560 raise JSONRPCError(
560 'ret_type must be one of %s, got %s' % (','.join(_extended_types)), details)
561 'ret_type must be one of %s, got %s' % (','.join(_extended_types)), details)
561 extended_info = False
562 extended_info = False
562 content = False
563 content = False
563
564
564 if details == 'minimal':
565 if details == 'minimal':
565 extended_info = False
566 extended_info = False
566
567
567 elif details == 'basic':
568 elif details == 'basic':
568 extended_info = True
569 extended_info = True
569
570
570 elif details == 'full':
571 elif details == 'full':
571 extended_info = content = True
572 extended_info = content = True
572
573
573 try:
574 try:
574 # check if repo is not empty by any chance, skip quicker if it is.
575 # check if repo is not empty by any chance, skip quicker if it is.
575 _scm = repo.scm_instance()
576 _scm = repo.scm_instance()
576 if _scm.is_empty():
577 if _scm.is_empty():
577 return None
578 return None
578
579
579 node = ScmModel().get_node(
580 node = ScmModel().get_node(
580 repo, commit_id, file_path, extended_info=extended_info,
581 repo, commit_id, file_path, extended_info=extended_info,
581 content=content, max_file_bytes=max_file_bytes, cache=cache)
582 content=content, max_file_bytes=max_file_bytes, cache=cache)
582
583 except NodeDoesNotExistError:
584 raise JSONRPCError('There is no file in repo: `{}` at path `{}` for commit: `{}`'.format(
585 repo.repo_name, file_path, commit_id))
583 except Exception:
586 except Exception:
584 log.exception("Exception occurred while trying to get repo %s file",
587 log.exception("Exception occurred while trying to get repo %s file",
585 repo.repo_name)
588 repo.repo_name)
586 raise JSONRPCError('failed to get repo: `{}` file at path {}'.format(
589 raise JSONRPCError('failed to get repo: `{}` file at path {}'.format(
587 repo.repo_name, file_path))
590 repo.repo_name, file_path))
588
591
589 return node
592 return node
590
593
591
594
592 @jsonrpc_method()
595 @jsonrpc_method()
593 def get_repo_fts_tree(request, apiuser, repoid, commit_id, root_path):
596 def get_repo_fts_tree(request, apiuser, repoid, commit_id, root_path):
594 """
597 """
595 Returns a list of tree nodes for path at given revision. This api is built
598 Returns a list of tree nodes for path at given revision. This api is built
596 strictly for usage in full text search building, and shouldn't be consumed
599 strictly for usage in full text search building, and shouldn't be consumed
597
600
598 This command can only be run using an |authtoken| with admin rights,
601 This command can only be run using an |authtoken| with admin rights,
599 or users with at least read rights to |repos|.
602 or users with at least read rights to |repos|.
600
603
601 """
604 """
602
605
603 repo = get_repo_or_error(repoid)
606 repo = get_repo_or_error(repoid)
604 if not has_superadmin_permission(apiuser):
607 if not has_superadmin_permission(apiuser):
605 _perms = ('repository.admin', 'repository.write', 'repository.read',)
608 _perms = ('repository.admin', 'repository.write', 'repository.read',)
606 validate_repo_permissions(apiuser, repoid, repo, _perms)
609 validate_repo_permissions(apiuser, repoid, repo, _perms)
607
610
608 repo_id = repo.repo_id
611 repo_id = repo.repo_id
609 cache_seconds = safe_int(rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
612 cache_seconds = safe_int(rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
610 cache_on = cache_seconds > 0
613 cache_on = cache_seconds > 0
611
614
612 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
615 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
613 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
616 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
614
617
615 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
618 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
616 condition=cache_on)
619 condition=cache_on)
617 def compute_fts_tree(repo_id, commit_id, root_path, cache_ver):
620 def compute_fts_tree(repo_id, commit_id, root_path, cache_ver):
618 return ScmModel().get_fts_data(repo_id, commit_id, root_path)
621 return ScmModel().get_fts_data(repo_id, commit_id, root_path)
619
622
620 try:
623 try:
621 # check if repo is not empty by any chance, skip quicker if it is.
624 # check if repo is not empty by any chance, skip quicker if it is.
622 _scm = repo.scm_instance()
625 _scm = repo.scm_instance()
623 if _scm.is_empty():
626 if _scm.is_empty():
624 return []
627 return []
625 except RepositoryError:
628 except RepositoryError:
626