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