##// END OF EJS Templates
cache: bump file-tree caches to next iteration
marcink -
r4036:6ca2afaa default
parent child Browse files
Show More
@@ -1,2332 +1,2332 b''
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.lib.vcs.exceptions import NodeDoesNotExistError
41 from rhodecode.model.changeset_status import ChangesetStatusModel
41 from rhodecode.model.changeset_status import ChangesetStatusModel
42 from rhodecode.model.comment import CommentsModel
42 from rhodecode.model.comment import CommentsModel
43 from rhodecode.model.db import (
43 from rhodecode.model.db import (
44 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup,
44 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup,
45 ChangesetComment)
45 ChangesetComment)
46 from rhodecode.model.permission import PermissionModel
46 from rhodecode.model.permission import PermissionModel
47 from rhodecode.model.repo import RepoModel
47 from rhodecode.model.repo import RepoModel
48 from rhodecode.model.scm import ScmModel, RepoList
48 from rhodecode.model.scm import ScmModel, RepoList
49 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
49 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
50 from rhodecode.model import validation_schema
50 from rhodecode.model import validation_schema
51 from rhodecode.model.validation_schema.schemas import repo_schema
51 from rhodecode.model.validation_schema.schemas import repo_schema
52
52
53 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
54
54
55
55
56 @jsonrpc_method()
56 @jsonrpc_method()
57 def get_repo(request, apiuser, repoid, cache=Optional(True)):
57 def get_repo(request, apiuser, repoid, cache=Optional(True)):
58 """
58 """
59 Gets an existing repository by its name or repository_id.
59 Gets an existing repository by its name or repository_id.
60
60
61 The members section so the output returns users groups or users
61 The members section so the output returns users groups or users
62 associated with that repository.
62 associated with that repository.
63
63
64 This command can only be run using an |authtoken| with admin rights,
64 This command can only be run using an |authtoken| with admin rights,
65 or users with at least read rights to the |repo|.
65 or users with at least read rights to the |repo|.
66
66
67 :param apiuser: This is filled automatically from the |authtoken|.
67 :param apiuser: This is filled automatically from the |authtoken|.
68 :type apiuser: AuthUser
68 :type apiuser: AuthUser
69 :param repoid: The repository name or repository id.
69 :param repoid: The repository name or repository id.
70 :type repoid: str or int
70 :type repoid: str or int
71 :param cache: use the cached value for last changeset
71 :param cache: use the cached value for last changeset
72 :type: cache: Optional(bool)
72 :type: cache: Optional(bool)
73
73
74 Example output:
74 Example output:
75
75
76 .. code-block:: bash
76 .. code-block:: bash
77
77
78 {
78 {
79 "error": null,
79 "error": null,
80 "id": <repo_id>,
80 "id": <repo_id>,
81 "result": {
81 "result": {
82 "clone_uri": null,
82 "clone_uri": null,
83 "created_on": "timestamp",
83 "created_on": "timestamp",
84 "description": "repo description",
84 "description": "repo description",
85 "enable_downloads": false,
85 "enable_downloads": false,
86 "enable_locking": false,
86 "enable_locking": false,
87 "enable_statistics": false,
87 "enable_statistics": false,
88 "followers": [
88 "followers": [
89 {
89 {
90 "active": true,
90 "active": true,
91 "admin": false,
91 "admin": false,
92 "api_key": "****************************************",
92 "api_key": "****************************************",
93 "api_keys": [
93 "api_keys": [
94 "****************************************"
94 "****************************************"
95 ],
95 ],
96 "email": "user@example.com",
96 "email": "user@example.com",
97 "emails": [
97 "emails": [
98 "user@example.com"
98 "user@example.com"
99 ],
99 ],
100 "extern_name": "rhodecode",
100 "extern_name": "rhodecode",
101 "extern_type": "rhodecode",
101 "extern_type": "rhodecode",
102 "firstname": "username",
102 "firstname": "username",
103 "ip_addresses": [],
103 "ip_addresses": [],
104 "language": null,
104 "language": null,
105 "last_login": "2015-09-16T17:16:35.854",
105 "last_login": "2015-09-16T17:16:35.854",
106 "lastname": "surname",
106 "lastname": "surname",
107 "user_id": <user_id>,
107 "user_id": <user_id>,
108 "username": "name"
108 "username": "name"
109 }
109 }
110 ],
110 ],
111 "fork_of": "parent-repo",
111 "fork_of": "parent-repo",
112 "landing_rev": [
112 "landing_rev": [
113 "rev",
113 "rev",
114 "tip"
114 "tip"
115 ],
115 ],
116 "last_changeset": {
116 "last_changeset": {
117 "author": "User <user@example.com>",
117 "author": "User <user@example.com>",
118 "branch": "default",
118 "branch": "default",
119 "date": "timestamp",
119 "date": "timestamp",
120 "message": "last commit message",
120 "message": "last commit message",
121 "parents": [
121 "parents": [
122 {
122 {
123 "raw_id": "commit-id"
123 "raw_id": "commit-id"
124 }
124 }
125 ],
125 ],
126 "raw_id": "commit-id",
126 "raw_id": "commit-id",
127 "revision": <revision number>,
127 "revision": <revision number>,
128 "short_id": "short id"
128 "short_id": "short id"
129 },
129 },
130 "lock_reason": null,
130 "lock_reason": null,
131 "locked_by": null,
131 "locked_by": null,
132 "locked_date": null,
132 "locked_date": null,
133 "owner": "owner-name",
133 "owner": "owner-name",
134 "permissions": [
134 "permissions": [
135 {
135 {
136 "name": "super-admin-name",
136 "name": "super-admin-name",
137 "origin": "super-admin",
137 "origin": "super-admin",
138 "permission": "repository.admin",
138 "permission": "repository.admin",
139 "type": "user"
139 "type": "user"
140 },
140 },
141 {
141 {
142 "name": "owner-name",
142 "name": "owner-name",
143 "origin": "owner",
143 "origin": "owner",
144 "permission": "repository.admin",
144 "permission": "repository.admin",
145 "type": "user"
145 "type": "user"
146 },
146 },
147 {
147 {
148 "name": "user-group-name",
148 "name": "user-group-name",
149 "origin": "permission",
149 "origin": "permission",
150 "permission": "repository.write",
150 "permission": "repository.write",
151 "type": "user_group"
151 "type": "user_group"
152 }
152 }
153 ],
153 ],
154 "private": true,
154 "private": true,
155 "repo_id": 676,
155 "repo_id": 676,
156 "repo_name": "user-group/repo-name",
156 "repo_name": "user-group/repo-name",
157 "repo_type": "hg"
157 "repo_type": "hg"
158 }
158 }
159 }
159 }
160 """
160 """
161
161
162 repo = get_repo_or_error(repoid)
162 repo = get_repo_or_error(repoid)
163 cache = Optional.extract(cache)
163 cache = Optional.extract(cache)
164
164
165 include_secrets = False
165 include_secrets = False
166 if has_superadmin_permission(apiuser):
166 if has_superadmin_permission(apiuser):
167 include_secrets = True
167 include_secrets = True
168 else:
168 else:
169 # check if we have at least read permission for this repo !
169 # check if we have at least read permission for this repo !
170 _perms = (
170 _perms = (
171 'repository.admin', 'repository.write', 'repository.read',)
171 'repository.admin', 'repository.write', 'repository.read',)
172 validate_repo_permissions(apiuser, repoid, repo, _perms)
172 validate_repo_permissions(apiuser, repoid, repo, _perms)
173
173
174 permissions = []
174 permissions = []
175 for _user in repo.permissions():
175 for _user in repo.permissions():
176 user_data = {
176 user_data = {
177 'name': _user.username,
177 'name': _user.username,
178 'permission': _user.permission,
178 'permission': _user.permission,
179 'origin': get_origin(_user),
179 'origin': get_origin(_user),
180 'type': "user",
180 'type': "user",
181 }
181 }
182 permissions.append(user_data)
182 permissions.append(user_data)
183
183
184 for _user_group in repo.permission_user_groups():
184 for _user_group in repo.permission_user_groups():
185 user_group_data = {
185 user_group_data = {
186 'name': _user_group.users_group_name,
186 'name': _user_group.users_group_name,
187 'permission': _user_group.permission,
187 'permission': _user_group.permission,
188 'origin': get_origin(_user_group),
188 'origin': get_origin(_user_group),
189 'type': "user_group",
189 'type': "user_group",
190 }
190 }
191 permissions.append(user_group_data)
191 permissions.append(user_group_data)
192
192
193 following_users = [
193 following_users = [
194 user.user.get_api_data(include_secrets=include_secrets)
194 user.user.get_api_data(include_secrets=include_secrets)
195 for user in repo.followers]
195 for user in repo.followers]
196
196
197 if not cache:
197 if not cache:
198 repo.update_commit_cache()
198 repo.update_commit_cache()
199 data = repo.get_api_data(include_secrets=include_secrets)
199 data = repo.get_api_data(include_secrets=include_secrets)
200 data['permissions'] = permissions
200 data['permissions'] = permissions
201 data['followers'] = following_users
201 data['followers'] = following_users
202 return data
202 return data
203
203
204
204
205 @jsonrpc_method()
205 @jsonrpc_method()
206 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
206 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
207 """
207 """
208 Lists all existing repositories.
208 Lists all existing repositories.
209
209
210 This command can only be run using an |authtoken| with admin rights,
210 This command can only be run using an |authtoken| with admin rights,
211 or users with at least read rights to |repos|.
211 or users with at least read rights to |repos|.
212
212
213 :param apiuser: This is filled automatically from the |authtoken|.
213 :param apiuser: This is filled automatically from the |authtoken|.
214 :type apiuser: AuthUser
214 :type apiuser: AuthUser
215 :param root: specify root repository group to fetch repositories.
215 :param root: specify root repository group to fetch repositories.
216 filters the returned repositories to be members of given root group.
216 filters the returned repositories to be members of given root group.
217 :type root: Optional(None)
217 :type root: Optional(None)
218 :param traverse: traverse given root into subrepositories. With this flag
218 :param traverse: traverse given root into subrepositories. With this flag
219 set to False, it will only return top-level repositories from `root`.
219 set to False, it will only return top-level repositories from `root`.
220 if root is empty it will return just top-level repositories.
220 if root is empty it will return just top-level repositories.
221 :type traverse: Optional(True)
221 :type traverse: Optional(True)
222
222
223
223
224 Example output:
224 Example output:
225
225
226 .. code-block:: bash
226 .. code-block:: bash
227
227
228 id : <id_given_in_input>
228 id : <id_given_in_input>
229 result: [
229 result: [
230 {
230 {
231 "repo_id" : "<repo_id>",
231 "repo_id" : "<repo_id>",
232 "repo_name" : "<reponame>"
232 "repo_name" : "<reponame>"
233 "repo_type" : "<repo_type>",
233 "repo_type" : "<repo_type>",
234 "clone_uri" : "<clone_uri>",
234 "clone_uri" : "<clone_uri>",
235 "private": : "<bool>",
235 "private": : "<bool>",
236 "created_on" : "<datetimecreated>",
236 "created_on" : "<datetimecreated>",
237 "description" : "<description>",
237 "description" : "<description>",
238 "landing_rev": "<landing_rev>",
238 "landing_rev": "<landing_rev>",
239 "owner": "<repo_owner>",
239 "owner": "<repo_owner>",
240 "fork_of": "<name_of_fork_parent>",
240 "fork_of": "<name_of_fork_parent>",
241 "enable_downloads": "<bool>",
241 "enable_downloads": "<bool>",
242 "enable_locking": "<bool>",
242 "enable_locking": "<bool>",
243 "enable_statistics": "<bool>",
243 "enable_statistics": "<bool>",
244 },
244 },
245 ...
245 ...
246 ]
246 ]
247 error: null
247 error: null
248 """
248 """
249
249
250 include_secrets = has_superadmin_permission(apiuser)
250 include_secrets = has_superadmin_permission(apiuser)
251 _perms = ('repository.read', 'repository.write', 'repository.admin',)
251 _perms = ('repository.read', 'repository.write', 'repository.admin',)
252 extras = {'user': apiuser}
252 extras = {'user': apiuser}
253
253
254 root = Optional.extract(root)
254 root = Optional.extract(root)
255 traverse = Optional.extract(traverse, binary=True)
255 traverse = Optional.extract(traverse, binary=True)
256
256
257 if root:
257 if root:
258 # verify parent existance, if it's empty return an error
258 # verify parent existance, if it's empty return an error
259 parent = RepoGroup.get_by_group_name(root)
259 parent = RepoGroup.get_by_group_name(root)
260 if not parent:
260 if not parent:
261 raise JSONRPCError(
261 raise JSONRPCError(
262 'Root repository group `{}` does not exist'.format(root))
262 'Root repository group `{}` does not exist'.format(root))
263
263
264 if traverse:
264 if traverse:
265 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
265 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
266 else:
266 else:
267 repos = RepoModel().get_repos_for_root(root=parent)
267 repos = RepoModel().get_repos_for_root(root=parent)
268 else:
268 else:
269 if traverse:
269 if traverse:
270 repos = RepoModel().get_all()
270 repos = RepoModel().get_all()
271 else:
271 else:
272 # return just top-level
272 # return just top-level
273 repos = RepoModel().get_repos_for_root(root=None)
273 repos = RepoModel().get_repos_for_root(root=None)
274
274
275 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
275 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
276 return [repo.get_api_data(include_secrets=include_secrets)
276 return [repo.get_api_data(include_secrets=include_secrets)
277 for repo in repo_list]
277 for repo in repo_list]
278
278
279
279
280 @jsonrpc_method()
280 @jsonrpc_method()
281 def get_repo_changeset(request, apiuser, repoid, revision,
281 def get_repo_changeset(request, apiuser, repoid, revision,
282 details=Optional('basic')):
282 details=Optional('basic')):
283 """
283 """
284 Returns information about a changeset.
284 Returns information about a changeset.
285
285
286 Additionally parameters define the amount of details returned by
286 Additionally parameters define the amount of details returned by
287 this function.
287 this function.
288
288
289 This command can only be run using an |authtoken| with admin rights,
289 This command can only be run using an |authtoken| with admin rights,
290 or users with at least read rights to the |repo|.
290 or users with at least read rights to the |repo|.
291
291
292 :param apiuser: This is filled automatically from the |authtoken|.
292 :param apiuser: This is filled automatically from the |authtoken|.
293 :type apiuser: AuthUser
293 :type apiuser: AuthUser
294 :param repoid: The repository name or repository id
294 :param repoid: The repository name or repository id
295 :type repoid: str or int
295 :type repoid: str or int
296 :param revision: revision for which listing should be done
296 :param revision: revision for which listing should be done
297 :type revision: str
297 :type revision: str
298 :param details: details can be 'basic|extended|full' full gives diff
298 :param details: details can be 'basic|extended|full' full gives diff
299 info details like the diff itself, and number of changed files etc.
299 info details like the diff itself, and number of changed files etc.
300 :type details: Optional(str)
300 :type details: Optional(str)
301
301
302 """
302 """
303 repo = get_repo_or_error(repoid)
303 repo = get_repo_or_error(repoid)
304 if not has_superadmin_permission(apiuser):
304 if not has_superadmin_permission(apiuser):
305 _perms = (
305 _perms = (
306 'repository.admin', 'repository.write', 'repository.read',)
306 'repository.admin', 'repository.write', 'repository.read',)
307 validate_repo_permissions(apiuser, repoid, repo, _perms)
307 validate_repo_permissions(apiuser, repoid, repo, _perms)
308
308
309 changes_details = Optional.extract(details)
309 changes_details = Optional.extract(details)
310 _changes_details_types = ['basic', 'extended', 'full']
310 _changes_details_types = ['basic', 'extended', 'full']
311 if changes_details not in _changes_details_types:
311 if changes_details not in _changes_details_types:
312 raise JSONRPCError(
312 raise JSONRPCError(
313 'ret_type must be one of %s' % (
313 'ret_type must be one of %s' % (
314 ','.join(_changes_details_types)))
314 ','.join(_changes_details_types)))
315
315
316 pre_load = ['author', 'branch', 'date', 'message', 'parents',
316 pre_load = ['author', 'branch', 'date', 'message', 'parents',
317 'status', '_commit', '_file_paths']
317 'status', '_commit', '_file_paths']
318
318
319 try:
319 try:
320 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
320 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
321 except TypeError as e:
321 except TypeError as e:
322 raise JSONRPCError(safe_str(e))
322 raise JSONRPCError(safe_str(e))
323 _cs_json = cs.__json__()
323 _cs_json = cs.__json__()
324 _cs_json['diff'] = build_commit_data(cs, changes_details)
324 _cs_json['diff'] = build_commit_data(cs, changes_details)
325 if changes_details == 'full':
325 if changes_details == 'full':
326 _cs_json['refs'] = cs._get_refs()
326 _cs_json['refs'] = cs._get_refs()
327 return _cs_json
327 return _cs_json
328
328
329
329
330 @jsonrpc_method()
330 @jsonrpc_method()
331 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
331 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
332 details=Optional('basic')):
332 details=Optional('basic')):
333 """
333 """
334 Returns a set of commits limited by the number starting
334 Returns a set of commits limited by the number starting
335 from the `start_rev` option.
335 from the `start_rev` option.
336
336
337 Additional parameters define the amount of details returned by this
337 Additional parameters define the amount of details returned by this
338 function.
338 function.
339
339
340 This command can only be run using an |authtoken| with admin rights,
340 This command can only be run using an |authtoken| with admin rights,
341 or users with at least read rights to |repos|.
341 or users with at least read rights to |repos|.
342
342
343 :param apiuser: This is filled automatically from the |authtoken|.
343 :param apiuser: This is filled automatically from the |authtoken|.
344 :type apiuser: AuthUser
344 :type apiuser: AuthUser
345 :param repoid: The repository name or repository ID.
345 :param repoid: The repository name or repository ID.
346 :type repoid: str or int
346 :type repoid: str or int
347 :param start_rev: The starting revision from where to get changesets.
347 :param start_rev: The starting revision from where to get changesets.
348 :type start_rev: str
348 :type start_rev: str
349 :param limit: Limit the number of commits to this amount
349 :param limit: Limit the number of commits to this amount
350 :type limit: str or int
350 :type limit: str or int
351 :param details: Set the level of detail returned. Valid option are:
351 :param details: Set the level of detail returned. Valid option are:
352 ``basic``, ``extended`` and ``full``.
352 ``basic``, ``extended`` and ``full``.
353 :type details: Optional(str)
353 :type details: Optional(str)
354
354
355 .. note::
355 .. note::
356
356
357 Setting the parameter `details` to the value ``full`` is extensive
357 Setting the parameter `details` to the value ``full`` is extensive
358 and returns details like the diff itself, and the number
358 and returns details like the diff itself, and the number
359 of changed files.
359 of changed files.
360
360
361 """
361 """
362 repo = get_repo_or_error(repoid)
362 repo = get_repo_or_error(repoid)
363 if not has_superadmin_permission(apiuser):
363 if not has_superadmin_permission(apiuser):
364 _perms = (
364 _perms = (
365 'repository.admin', 'repository.write', 'repository.read',)
365 'repository.admin', 'repository.write', 'repository.read',)
366 validate_repo_permissions(apiuser, repoid, repo, _perms)
366 validate_repo_permissions(apiuser, repoid, repo, _perms)
367
367
368 changes_details = Optional.extract(details)
368 changes_details = Optional.extract(details)
369 _changes_details_types = ['basic', 'extended', 'full']
369 _changes_details_types = ['basic', 'extended', 'full']
370 if changes_details not in _changes_details_types:
370 if changes_details not in _changes_details_types:
371 raise JSONRPCError(
371 raise JSONRPCError(
372 'ret_type must be one of %s' % (
372 'ret_type must be one of %s' % (
373 ','.join(_changes_details_types)))
373 ','.join(_changes_details_types)))
374
374
375 limit = int(limit)
375 limit = int(limit)
376 pre_load = ['author', 'branch', 'date', 'message', 'parents',
376 pre_load = ['author', 'branch', 'date', 'message', 'parents',
377 'status', '_commit', '_file_paths']
377 'status', '_commit', '_file_paths']
378
378
379 vcs_repo = repo.scm_instance()
379 vcs_repo = repo.scm_instance()
380 # SVN needs a special case to distinguish its index and commit id
380 # SVN needs a special case to distinguish its index and commit id
381 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
381 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
382 start_rev = vcs_repo.commit_ids[0]
382 start_rev = vcs_repo.commit_ids[0]
383
383
384 try:
384 try:
385 commits = vcs_repo.get_commits(
385 commits = vcs_repo.get_commits(
386 start_id=start_rev, pre_load=pre_load, translate_tags=False)
386 start_id=start_rev, pre_load=pre_load, translate_tags=False)
387 except TypeError as e:
387 except TypeError as e:
388 raise JSONRPCError(safe_str(e))
388 raise JSONRPCError(safe_str(e))
389 except Exception:
389 except Exception:
390 log.exception('Fetching of commits failed')
390 log.exception('Fetching of commits failed')
391 raise JSONRPCError('Error occurred during commit fetching')
391 raise JSONRPCError('Error occurred during commit fetching')
392
392
393 ret = []
393 ret = []
394 for cnt, commit in enumerate(commits):
394 for cnt, commit in enumerate(commits):
395 if cnt >= limit != -1:
395 if cnt >= limit != -1:
396 break
396 break
397 _cs_json = commit.__json__()
397 _cs_json = commit.__json__()
398 _cs_json['diff'] = build_commit_data(commit, changes_details)
398 _cs_json['diff'] = build_commit_data(commit, changes_details)
399 if changes_details == 'full':
399 if changes_details == 'full':
400 _cs_json['refs'] = {
400 _cs_json['refs'] = {
401 'branches': [commit.branch],
401 'branches': [commit.branch],
402 'bookmarks': getattr(commit, 'bookmarks', []),
402 'bookmarks': getattr(commit, 'bookmarks', []),
403 'tags': commit.tags
403 'tags': commit.tags
404 }
404 }
405 ret.append(_cs_json)
405 ret.append(_cs_json)
406 return ret
406 return ret
407
407
408
408
409 @jsonrpc_method()
409 @jsonrpc_method()
410 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
410 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
411 ret_type=Optional('all'), details=Optional('basic'),
411 ret_type=Optional('all'), details=Optional('basic'),
412 max_file_bytes=Optional(None)):
412 max_file_bytes=Optional(None)):
413 """
413 """
414 Returns a list of nodes and children in a flat list for a given
414 Returns a list of nodes and children in a flat list for a given
415 path at given revision.
415 path at given revision.
416
416
417 It's possible to specify ret_type to show only `files` or `dirs`.
417 It's possible to specify ret_type to show only `files` or `dirs`.
418
418
419 This command can only be run using an |authtoken| with admin rights,
419 This command can only be run using an |authtoken| with admin rights,
420 or users with at least read rights to |repos|.
420 or users with at least read rights to |repos|.
421
421
422 :param apiuser: This is filled automatically from the |authtoken|.
422 :param apiuser: This is filled automatically from the |authtoken|.
423 :type apiuser: AuthUser
423 :type apiuser: AuthUser
424 :param repoid: The repository name or repository ID.
424 :param repoid: The repository name or repository ID.
425 :type repoid: str or int
425 :type repoid: str or int
426 :param revision: The revision for which listing should be done.
426 :param revision: The revision for which listing should be done.
427 :type revision: str
427 :type revision: str
428 :param root_path: The path from which to start displaying.
428 :param root_path: The path from which to start displaying.
429 :type root_path: str
429 :type root_path: str
430 :param ret_type: Set the return type. Valid options are
430 :param ret_type: Set the return type. Valid options are
431 ``all`` (default), ``files`` and ``dirs``.
431 ``all`` (default), ``files`` and ``dirs``.
432 :type ret_type: Optional(str)
432 :type ret_type: Optional(str)
433 :param details: Returns extended information about nodes, such as
433 :param details: Returns extended information about nodes, such as
434 md5, binary, and or content.
434 md5, binary, and or content.
435 The valid options are ``basic`` and ``full``.
435 The valid options are ``basic`` and ``full``.
436 :type details: Optional(str)
436 :type details: Optional(str)
437 :param max_file_bytes: Only return file content under this file size bytes
437 :param max_file_bytes: Only return file content under this file size bytes
438 :type details: Optional(int)
438 :type details: Optional(int)
439
439
440 Example output:
440 Example output:
441
441
442 .. code-block:: bash
442 .. code-block:: bash
443
443
444 id : <id_given_in_input>
444 id : <id_given_in_input>
445 result: [
445 result: [
446 {
446 {
447 "binary": false,
447 "binary": false,
448 "content": "File line",
448 "content": "File line",
449 "extension": "md",
449 "extension": "md",
450 "lines": 2,
450 "lines": 2,
451 "md5": "059fa5d29b19c0657e384749480f6422",
451 "md5": "059fa5d29b19c0657e384749480f6422",
452 "mimetype": "text/x-minidsrc",
452 "mimetype": "text/x-minidsrc",
453 "name": "file.md",
453 "name": "file.md",
454 "size": 580,
454 "size": 580,
455 "type": "file"
455 "type": "file"
456 },
456 },
457 ...
457 ...
458 ]
458 ]
459 error: null
459 error: null
460 """
460 """
461
461
462 repo = get_repo_or_error(repoid)
462 repo = get_repo_or_error(repoid)
463 if not has_superadmin_permission(apiuser):
463 if not has_superadmin_permission(apiuser):
464 _perms = ('repository.admin', 'repository.write', 'repository.read',)
464 _perms = ('repository.admin', 'repository.write', 'repository.read',)
465 validate_repo_permissions(apiuser, repoid, repo, _perms)
465 validate_repo_permissions(apiuser, repoid, repo, _perms)
466
466
467 ret_type = Optional.extract(ret_type)
467 ret_type = Optional.extract(ret_type)
468 details = Optional.extract(details)
468 details = Optional.extract(details)
469 _extended_types = ['basic', 'full']
469 _extended_types = ['basic', 'full']
470 if details not in _extended_types:
470 if details not in _extended_types:
471 raise JSONRPCError('ret_type must be one of %s' % (','.join(_extended_types)))
471 raise JSONRPCError('ret_type must be one of %s' % (','.join(_extended_types)))
472 extended_info = False
472 extended_info = False
473 content = False
473 content = False
474 if details == 'basic':
474 if details == 'basic':
475 extended_info = True
475 extended_info = True
476
476
477 if details == 'full':
477 if details == 'full':
478 extended_info = content = True
478 extended_info = content = True
479
479
480 _map = {}
480 _map = {}
481 try:
481 try:
482 # check if repo is not empty by any chance, skip quicker if it is.
482 # check if repo is not empty by any chance, skip quicker if it is.
483 _scm = repo.scm_instance()
483 _scm = repo.scm_instance()
484 if _scm.is_empty():
484 if _scm.is_empty():
485 return []
485 return []
486
486
487 _d, _f = ScmModel().get_nodes(
487 _d, _f = ScmModel().get_nodes(
488 repo, revision, root_path, flat=False,
488 repo, revision, root_path, flat=False,
489 extended_info=extended_info, content=content,
489 extended_info=extended_info, content=content,
490 max_file_bytes=max_file_bytes)
490 max_file_bytes=max_file_bytes)
491 _map = {
491 _map = {
492 'all': _d + _f,
492 'all': _d + _f,
493 'files': _f,
493 'files': _f,
494 'dirs': _d,
494 'dirs': _d,
495 }
495 }
496 return _map[ret_type]
496 return _map[ret_type]
497 except KeyError:
497 except KeyError:
498 raise JSONRPCError(
498 raise JSONRPCError(
499 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
499 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
500 except Exception:
500 except Exception:
501 log.exception("Exception occurred while trying to get repo nodes")
501 log.exception("Exception occurred while trying to get repo nodes")
502 raise JSONRPCError(
502 raise JSONRPCError(
503 'failed to get repo: `%s` nodes' % repo.repo_name
503 'failed to get repo: `%s` nodes' % repo.repo_name
504 )
504 )
505
505
506
506
507 @jsonrpc_method()
507 @jsonrpc_method()
508 def get_repo_file(request, apiuser, repoid, commit_id, file_path,
508 def get_repo_file(request, apiuser, repoid, commit_id, file_path,
509 max_file_bytes=Optional(None), details=Optional('basic'),
509 max_file_bytes=Optional(None), details=Optional('basic'),
510 cache=Optional(True)):
510 cache=Optional(True)):
511 """
511 """
512 Returns a single file from repository at given revision.
512 Returns a single file from repository at given revision.
513
513
514 This command can only be run using an |authtoken| with admin rights,
514 This command can only be run using an |authtoken| with admin rights,
515 or users with at least read rights to |repos|.
515 or users with at least read rights to |repos|.
516
516
517 :param apiuser: This is filled automatically from the |authtoken|.
517 :param apiuser: This is filled automatically from the |authtoken|.
518 :type apiuser: AuthUser
518 :type apiuser: AuthUser
519 :param repoid: The repository name or repository ID.
519 :param repoid: The repository name or repository ID.
520 :type repoid: str or int
520 :type repoid: str or int
521 :param commit_id: The revision for which listing should be done.
521 :param commit_id: The revision for which listing should be done.
522 :type commit_id: str
522 :type commit_id: str
523 :param file_path: The path from which to start displaying.
523 :param file_path: The path from which to start displaying.
524 :type file_path: str
524 :type file_path: str
525 :param details: Returns different set of information about nodes.
525 :param details: Returns different set of information about nodes.
526 The valid options are ``minimal`` ``basic`` and ``full``.
526 The valid options are ``minimal`` ``basic`` and ``full``.
527 :type details: Optional(str)
527 :type details: Optional(str)
528 :param max_file_bytes: Only return file content under this file size bytes
528 :param max_file_bytes: Only return file content under this file size bytes
529 :type max_file_bytes: Optional(int)
529 :type max_file_bytes: Optional(int)
530 :param cache: Use internal caches for fetching files. If disabled fetching
530 :param cache: Use internal caches for fetching files. If disabled fetching
531 files is slower but more memory efficient
531 files is slower but more memory efficient
532 :type cache: Optional(bool)
532 :type cache: Optional(bool)
533
533
534 Example output:
534 Example output:
535
535
536 .. code-block:: bash
536 .. code-block:: bash
537
537
538 id : <id_given_in_input>
538 id : <id_given_in_input>
539 result: {
539 result: {
540 "binary": false,
540 "binary": false,
541 "extension": "py",
541 "extension": "py",
542 "lines": 35,
542 "lines": 35,
543 "content": "....",
543 "content": "....",
544 "md5": "76318336366b0f17ee249e11b0c99c41",
544 "md5": "76318336366b0f17ee249e11b0c99c41",
545 "mimetype": "text/x-python",
545 "mimetype": "text/x-python",
546 "name": "python.py",
546 "name": "python.py",
547 "size": 817,
547 "size": 817,
548 "type": "file",
548 "type": "file",
549 }
549 }
550 error: null
550 error: null
551 """
551 """
552
552
553 repo = get_repo_or_error(repoid)
553 repo = get_repo_or_error(repoid)
554 if not has_superadmin_permission(apiuser):
554 if not has_superadmin_permission(apiuser):
555 _perms = ('repository.admin', 'repository.write', 'repository.read',)
555 _perms = ('repository.admin', 'repository.write', 'repository.read',)
556 validate_repo_permissions(apiuser, repoid, repo, _perms)
556 validate_repo_permissions(apiuser, repoid, repo, _perms)
557
557
558 cache = Optional.extract(cache, binary=True)
558 cache = Optional.extract(cache, binary=True)
559 details = Optional.extract(details)
559 details = Optional.extract(details)
560 _extended_types = ['minimal', 'minimal+search', 'basic', 'full']
560 _extended_types = ['minimal', 'minimal+search', 'basic', 'full']
561 if details not in _extended_types:
561 if details not in _extended_types:
562 raise JSONRPCError(
562 raise JSONRPCError(
563 'ret_type must be one of %s, got %s' % (','.join(_extended_types)), details)
563 'ret_type must be one of %s, got %s' % (','.join(_extended_types)), details)
564 extended_info = False
564 extended_info = False
565 content = False
565 content = False
566
566
567 if details == 'minimal':
567 if details == 'minimal':
568 extended_info = False
568 extended_info = False
569
569
570 elif details == 'basic':
570 elif details == 'basic':
571 extended_info = True
571 extended_info = True
572
572
573 elif details == 'full':
573 elif details == 'full':
574 extended_info = content = True
574 extended_info = content = True
575
575
576 try:
576 try:
577 # check if repo is not empty by any chance, skip quicker if it is.
577 # check if repo is not empty by any chance, skip quicker if it is.
578 _scm = repo.scm_instance()
578 _scm = repo.scm_instance()
579 if _scm.is_empty():
579 if _scm.is_empty():
580 return None
580 return None
581
581
582 node = ScmModel().get_node(
582 node = ScmModel().get_node(
583 repo, commit_id, file_path, extended_info=extended_info,
583 repo, commit_id, file_path, extended_info=extended_info,
584 content=content, max_file_bytes=max_file_bytes, cache=cache)
584 content=content, max_file_bytes=max_file_bytes, cache=cache)
585 except NodeDoesNotExistError:
585 except NodeDoesNotExistError:
586 raise JSONRPCError('There is no file in repo: `{}` at path `{}` for commit: `{}`'.format(
586 raise JSONRPCError('There is no file in repo: `{}` at path `{}` for commit: `{}`'.format(
587 repo.repo_name, file_path, commit_id))
587 repo.repo_name, file_path, commit_id))
588 except Exception:
588 except Exception:
589 log.exception("Exception occurred while trying to get repo %s file",
589 log.exception("Exception occurred while trying to get repo %s file",
590 repo.repo_name)
590 repo.repo_name)
591 raise JSONRPCError('failed to get repo: `{}` file at path {}'.format(
591 raise JSONRPCError('failed to get repo: `{}` file at path {}'.format(
592 repo.repo_name, file_path))
592 repo.repo_name, file_path))
593
593
594 return node
594 return node
595
595
596
596
597 @jsonrpc_method()
597 @jsonrpc_method()
598 def get_repo_fts_tree(request, apiuser, repoid, commit_id, root_path):
598 def get_repo_fts_tree(request, apiuser, repoid, commit_id, root_path):
599 """
599 """
600 Returns a list of tree nodes for path at given revision. This api is built
600 Returns a list of tree nodes for path at given revision. This api is built
601 strictly for usage in full text search building, and shouldn't be consumed
601 strictly for usage in full text search building, and shouldn't be consumed
602
602
603 This command can only be run using an |authtoken| with admin rights,
603 This command can only be run using an |authtoken| with admin rights,
604 or users with at least read rights to |repos|.
604 or users with at least read rights to |repos|.
605
605
606 """
606 """
607
607
608 repo = get_repo_or_error(repoid)
608 repo = get_repo_or_error(repoid)
609 if not has_superadmin_permission(apiuser):
609 if not has_superadmin_permission(apiuser):
610 _perms = ('repository.admin', 'repository.write', 'repository.read',)
610 _perms = ('repository.admin', 'repository.write', 'repository.read',)
611 validate_repo_permissions(apiuser, repoid, repo, _perms)
611 validate_repo_permissions(apiuser, repoid, repo, _perms)
612
612
613 repo_id = repo.repo_id
613 repo_id = repo.repo_id
614 cache_seconds = safe_int(rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
614 cache_seconds = safe_int(rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
615 cache_on = cache_seconds > 0
615 cache_on = cache_seconds > 0
616
616
617 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
617 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
618 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
618 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
619
619
620 def compute_fts_tree(repo_id, commit_id, root_path, cache_ver):
620 def compute_fts_tree(cache_ver, repo_id, commit_id, root_path):
621 return ScmModel().get_fts_data(repo_id, commit_id, root_path)
621 return ScmModel().get_fts_data(repo_id, commit_id, root_path)
622
622
623 try:
623 try:
624 # 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.
625 _scm = repo.scm_instance()
625 _scm = repo.scm_instance()
626 if _scm.is_empty():
626 if _scm.is_empty():
627 return []
627 return []
628 except RepositoryError:
628 except RepositoryError:
629 log.exception("Exception occurred while trying to get repo nodes")
629 log.exception("Exception occurred while trying to get repo nodes")
630 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
630 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
631
631
632 try:
632 try:
633 # we need to resolve commit_id to a FULL sha for cache to work correctly.
633 # we need to resolve commit_id to a FULL sha for cache to work correctly.
634 # sending 'master' is a pointer that needs to be translated to current commit.
634 # sending 'master' is a pointer that needs to be translated to current commit.
635 commit_id = _scm.get_commit(commit_id=commit_id).raw_id
635 commit_id = _scm.get_commit(commit_id=commit_id).raw_id
636 log.debug(
636 log.debug(
637 'Computing FTS REPO TREE for repo_id %s commit_id `%s` '
637 'Computing FTS REPO TREE for repo_id %s commit_id `%s` '
638 'with caching: %s[TTL: %ss]' % (
638 'with caching: %s[TTL: %ss]' % (
639 repo_id, commit_id, cache_on, cache_seconds or 0))
639 repo_id, commit_id, cache_on, cache_seconds or 0))
640
640
641 tree_files = compute_fts_tree(repo_id, commit_id, root_path, 'v1')
641 tree_files = compute_fts_tree(rc_cache.FILE_TREE_CACHE_VER, repo_id, commit_id, root_path)
642 return tree_files
642 return tree_files
643
643
644 except Exception:
644 except Exception:
645 log.exception("Exception occurred while trying to get repo nodes")
645 log.exception("Exception occurred while trying to get repo nodes")
646 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
646 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
647
647
648
648
649 @jsonrpc_method()
649 @jsonrpc_method()
650 def get_repo_refs(request, apiuser, repoid):
650 def get_repo_refs(request, apiuser, repoid):
651 """
651 """
652 Returns a dictionary of current references. It returns
652 Returns a dictionary of current references. It returns
653 bookmarks, branches, closed_branches, and tags for given repository
653 bookmarks, branches, closed_branches, and tags for given repository
654
654
655 It's possible to specify ret_type to show only `files` or `dirs`.
655 It's possible to specify ret_type to show only `files` or `dirs`.
656
656
657 This command can only be run using an |authtoken| with admin rights,
657 This command can only be run using an |authtoken| with admin rights,
658 or users with at least read rights to |repos|.
658 or users with at least read rights to |repos|.
659
659
660 :param apiuser: This is filled automatically from the |authtoken|.
660 :param apiuser: This is filled automatically from the |authtoken|.
661 :type apiuser: AuthUser
661 :type apiuser: AuthUser
662 :param repoid: The repository name or repository ID.
662 :param repoid: The repository name or repository ID.
663 :type repoid: str or int
663 :type repoid: str or int
664
664
665 Example output:
665 Example output:
666
666
667 .. code-block:: bash
667 .. code-block:: bash
668
668
669 id : <id_given_in_input>
669 id : <id_given_in_input>
670 "result": {
670 "result": {
671 "bookmarks": {
671 "bookmarks": {
672 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
672 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
673 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
673 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
674 },
674 },
675 "branches": {
675 "branches": {
676 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
676 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
677 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
677 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
678 },
678 },
679 "branches_closed": {},
679 "branches_closed": {},
680 "tags": {
680 "tags": {
681 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
681 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
682 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
682 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
683 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
683 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
684 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
684 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
685 }
685 }
686 }
686 }
687 error: null
687 error: null
688 """
688 """
689
689
690 repo = get_repo_or_error(repoid)
690 repo = get_repo_or_error(repoid)
691 if not has_superadmin_permission(apiuser):
691 if not has_superadmin_permission(apiuser):
692 _perms = ('repository.admin', 'repository.write', 'repository.read',)
692 _perms = ('repository.admin', 'repository.write', 'repository.read',)
693 validate_repo_permissions(apiuser, repoid, repo, _perms)
693 validate_repo_permissions(apiuser, repoid, repo, _perms)
694
694
695 try:
695 try:
696 # check if repo is not empty by any chance, skip quicker if it is.
696 # check if repo is not empty by any chance, skip quicker if it is.
697 vcs_instance = repo.scm_instance()
697 vcs_instance = repo.scm_instance()
698 refs = vcs_instance.refs()
698 refs = vcs_instance.refs()
699 return refs
699 return refs
700 except Exception:
700 except Exception:
701 log.exception("Exception occurred while trying to get repo refs")
701 log.exception("Exception occurred while trying to get repo refs")
702 raise JSONRPCError(
702 raise JSONRPCError(
703 'failed to get repo: `%s` references' % repo.repo_name
703 'failed to get repo: `%s` references' % repo.repo_name
704 )
704 )
705
705
706
706
707 @jsonrpc_method()
707 @jsonrpc_method()
708 def create_repo(
708 def create_repo(
709 request, apiuser, repo_name, repo_type,
709 request, apiuser, repo_name, repo_type,
710 owner=Optional(OAttr('apiuser')),
710 owner=Optional(OAttr('apiuser')),
711 description=Optional(''),
711 description=Optional(''),
712 private=Optional(False),
712 private=Optional(False),
713 clone_uri=Optional(None),
713 clone_uri=Optional(None),
714 push_uri=Optional(None),
714 push_uri=Optional(None),
715 landing_rev=Optional(None),
715 landing_rev=Optional(None),
716 enable_statistics=Optional(False),
716 enable_statistics=Optional(False),
717 enable_locking=Optional(False),
717 enable_locking=Optional(False),
718 enable_downloads=Optional(False),
718 enable_downloads=Optional(False),
719 copy_permissions=Optional(False)):
719 copy_permissions=Optional(False)):
720 """
720 """
721 Creates a repository.
721 Creates a repository.
722
722
723 * If the repository name contains "/", repository will be created inside
723 * If the repository name contains "/", repository will be created inside
724 a repository group or nested repository groups
724 a repository group or nested repository groups
725
725
726 For example "foo/bar/repo1" will create |repo| called "repo1" inside
726 For example "foo/bar/repo1" will create |repo| called "repo1" inside
727 group "foo/bar". You have to have permissions to access and write to
727 group "foo/bar". You have to have permissions to access and write to
728 the last repository group ("bar" in this example)
728 the last repository group ("bar" in this example)
729
729
730 This command can only be run using an |authtoken| with at least
730 This command can only be run using an |authtoken| with at least
731 permissions to create repositories, or write permissions to
731 permissions to create repositories, or write permissions to
732 parent repository groups.
732 parent repository groups.
733
733
734 :param apiuser: This is filled automatically from the |authtoken|.
734 :param apiuser: This is filled automatically from the |authtoken|.
735 :type apiuser: AuthUser
735 :type apiuser: AuthUser
736 :param repo_name: Set the repository name.
736 :param repo_name: Set the repository name.
737 :type repo_name: str
737 :type repo_name: str
738 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
738 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
739 :type repo_type: str
739 :type repo_type: str
740 :param owner: user_id or username
740 :param owner: user_id or username
741 :type owner: Optional(str)
741 :type owner: Optional(str)
742 :param description: Set the repository description.
742 :param description: Set the repository description.
743 :type description: Optional(str)
743 :type description: Optional(str)
744 :param private: set repository as private
744 :param private: set repository as private
745 :type private: bool
745 :type private: bool
746 :param clone_uri: set clone_uri
746 :param clone_uri: set clone_uri
747 :type clone_uri: str
747 :type clone_uri: str
748 :param push_uri: set push_uri
748 :param push_uri: set push_uri
749 :type push_uri: str
749 :type push_uri: str
750 :param landing_rev: <rev_type>:<rev>, e.g branch:default, book:dev, rev:abcd
750 :param landing_rev: <rev_type>:<rev>, e.g branch:default, book:dev, rev:abcd
751 :type landing_rev: str
751 :type landing_rev: str
752 :param enable_locking:
752 :param enable_locking:
753 :type enable_locking: bool
753 :type enable_locking: bool
754 :param enable_downloads:
754 :param enable_downloads:
755 :type enable_downloads: bool
755 :type enable_downloads: bool
756 :param enable_statistics:
756 :param enable_statistics:
757 :type enable_statistics: bool
757 :type enable_statistics: bool
758 :param copy_permissions: Copy permission from group in which the
758 :param copy_permissions: Copy permission from group in which the
759 repository is being created.
759 repository is being created.
760 :type copy_permissions: bool
760 :type copy_permissions: bool
761
761
762
762
763 Example output:
763 Example output:
764
764
765 .. code-block:: bash
765 .. code-block:: bash
766
766
767 id : <id_given_in_input>
767 id : <id_given_in_input>
768 result: {
768 result: {
769 "msg": "Created new repository `<reponame>`",
769 "msg": "Created new repository `<reponame>`",
770 "success": true,
770 "success": true,
771 "task": "<celery task id or None if done sync>"
771 "task": "<celery task id or None if done sync>"
772 }
772 }
773 error: null
773 error: null
774
774
775
775
776 Example error output:
776 Example error output:
777
777
778 .. code-block:: bash
778 .. code-block:: bash
779
779
780 id : <id_given_in_input>
780 id : <id_given_in_input>
781 result : null
781 result : null
782 error : {
782 error : {
783 'failed to create repository `<repo_name>`'
783 'failed to create repository `<repo_name>`'
784 }
784 }
785
785
786 """
786 """
787
787
788 owner = validate_set_owner_permissions(apiuser, owner)
788 owner = validate_set_owner_permissions(apiuser, owner)
789
789
790 description = Optional.extract(description)
790 description = Optional.extract(description)
791 copy_permissions = Optional.extract(copy_permissions)
791 copy_permissions = Optional.extract(copy_permissions)
792 clone_uri = Optional.extract(clone_uri)
792 clone_uri = Optional.extract(clone_uri)
793 push_uri = Optional.extract(push_uri)
793 push_uri = Optional.extract(push_uri)
794
794
795 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
795 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
796 if isinstance(private, Optional):
796 if isinstance(private, Optional):
797 private = defs.get('repo_private') or Optional.extract(private)
797 private = defs.get('repo_private') or Optional.extract(private)
798 if isinstance(repo_type, Optional):
798 if isinstance(repo_type, Optional):
799 repo_type = defs.get('repo_type')
799 repo_type = defs.get('repo_type')
800 if isinstance(enable_statistics, Optional):
800 if isinstance(enable_statistics, Optional):
801 enable_statistics = defs.get('repo_enable_statistics')
801 enable_statistics = defs.get('repo_enable_statistics')
802 if isinstance(enable_locking, Optional):
802 if isinstance(enable_locking, Optional):
803 enable_locking = defs.get('repo_enable_locking')
803 enable_locking = defs.get('repo_enable_locking')
804 if isinstance(enable_downloads, Optional):
804 if isinstance(enable_downloads, Optional):
805 enable_downloads = defs.get('repo_enable_downloads')
805 enable_downloads = defs.get('repo_enable_downloads')
806
806
807 landing_ref, _label = ScmModel.backend_landing_ref(repo_type)
807 landing_ref, _label = ScmModel.backend_landing_ref(repo_type)
808 ref_choices, _labels = ScmModel().get_repo_landing_revs(request.translate)
808 ref_choices, _labels = ScmModel().get_repo_landing_revs(request.translate)
809 ref_choices = list(set(ref_choices + [landing_ref]))
809 ref_choices = list(set(ref_choices + [landing_ref]))
810
810
811 landing_commit_ref = Optional.extract(landing_rev) or landing_ref
811 landing_commit_ref = Optional.extract(landing_rev) or landing_ref
812
812
813 schema = repo_schema.RepoSchema().bind(
813 schema = repo_schema.RepoSchema().bind(
814 repo_type_options=rhodecode.BACKENDS.keys(),
814 repo_type_options=rhodecode.BACKENDS.keys(),
815 repo_ref_options=ref_choices,
815 repo_ref_options=ref_choices,
816 repo_type=repo_type,
816 repo_type=repo_type,
817 # user caller
817 # user caller
818 user=apiuser)
818 user=apiuser)
819
819
820 try:
820 try:
821 schema_data = schema.deserialize(dict(
821 schema_data = schema.deserialize(dict(
822 repo_name=repo_name,
822 repo_name=repo_name,
823 repo_type=repo_type,
823 repo_type=repo_type,
824 repo_owner=owner.username,
824 repo_owner=owner.username,
825 repo_description=description,
825 repo_description=description,
826 repo_landing_commit_ref=landing_commit_ref,
826 repo_landing_commit_ref=landing_commit_ref,
827 repo_clone_uri=clone_uri,
827 repo_clone_uri=clone_uri,
828 repo_push_uri=push_uri,
828 repo_push_uri=push_uri,
829 repo_private=private,
829 repo_private=private,
830 repo_copy_permissions=copy_permissions,
830 repo_copy_permissions=copy_permissions,
831 repo_enable_statistics=enable_statistics,
831 repo_enable_statistics=enable_statistics,
832 repo_enable_downloads=enable_downloads,
832 repo_enable_downloads=enable_downloads,
833 repo_enable_locking=enable_locking))
833 repo_enable_locking=enable_locking))
834 except validation_schema.Invalid as err:
834 except validation_schema.Invalid as err:
835 raise JSONRPCValidationError(colander_exc=err)
835 raise JSONRPCValidationError(colander_exc=err)
836
836
837 try:
837 try:
838 data = {
838 data = {
839 'owner': owner,
839 'owner': owner,
840 'repo_name': schema_data['repo_group']['repo_name_without_group'],
840 'repo_name': schema_data['repo_group']['repo_name_without_group'],
841 'repo_name_full': schema_data['repo_name'],
841 'repo_name_full': schema_data['repo_name'],
842 'repo_group': schema_data['repo_group']['repo_group_id'],
842 'repo_group': schema_data['repo_group']['repo_group_id'],
843 'repo_type': schema_data['repo_type'],
843 'repo_type': schema_data['repo_type'],
844 'repo_description': schema_data['repo_description'],
844 'repo_description': schema_data['repo_description'],
845 'repo_private': schema_data['repo_private'],
845 'repo_private': schema_data['repo_private'],
846 'clone_uri': schema_data['repo_clone_uri'],
846 'clone_uri': schema_data['repo_clone_uri'],
847 'push_uri': schema_data['repo_push_uri'],
847 'push_uri': schema_data['repo_push_uri'],
848 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
848 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
849 'enable_statistics': schema_data['repo_enable_statistics'],
849 'enable_statistics': schema_data['repo_enable_statistics'],
850 'enable_locking': schema_data['repo_enable_locking'],
850 'enable_locking': schema_data['repo_enable_locking'],
851 'enable_downloads': schema_data['repo_enable_downloads'],
851 'enable_downloads': schema_data['repo_enable_downloads'],
852 'repo_copy_permissions': schema_data['repo_copy_permissions'],
852 'repo_copy_permissions': schema_data['repo_copy_permissions'],
853 }
853 }
854
854
855 task = RepoModel().create(form_data=data, cur_user=owner.user_id)
855 task = RepoModel().create(form_data=data, cur_user=owner.user_id)
856 task_id = get_task_id(task)
856 task_id = get_task_id(task)
857 # no commit, it's done in RepoModel, or async via celery
857 # no commit, it's done in RepoModel, or async via celery
858 return {
858 return {
859 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
859 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
860 'success': True, # cannot return the repo data here since fork
860 'success': True, # cannot return the repo data here since fork
861 # can be done async
861 # can be done async
862 'task': task_id
862 'task': task_id
863 }
863 }
864 except Exception:
864 except Exception:
865 log.exception(
865 log.exception(
866 u"Exception while trying to create the repository %s",
866 u"Exception while trying to create the repository %s",
867 schema_data['repo_name'])
867 schema_data['repo_name'])
868 raise JSONRPCError(
868 raise JSONRPCError(
869 'failed to create repository `%s`' % (schema_data['repo_name'],))
869 'failed to create repository `%s`' % (schema_data['repo_name'],))
870
870
871
871
872 @jsonrpc_method()
872 @jsonrpc_method()
873 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
873 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
874 description=Optional('')):
874 description=Optional('')):
875 """
875 """
876 Adds an extra field to a repository.
876 Adds an extra field to a repository.
877
877
878 This command can only be run using an |authtoken| with at least
878 This command can only be run using an |authtoken| with at least
879 write permissions to the |repo|.
879 write permissions to the |repo|.
880
880
881 :param apiuser: This is filled automatically from the |authtoken|.
881 :param apiuser: This is filled automatically from the |authtoken|.
882 :type apiuser: AuthUser
882 :type apiuser: AuthUser
883 :param repoid: Set the repository name or repository id.
883 :param repoid: Set the repository name or repository id.
884 :type repoid: str or int
884 :type repoid: str or int
885 :param key: Create a unique field key for this repository.
885 :param key: Create a unique field key for this repository.
886 :type key: str
886 :type key: str
887 :param label:
887 :param label:
888 :type label: Optional(str)
888 :type label: Optional(str)
889 :param description:
889 :param description:
890 :type description: Optional(str)
890 :type description: Optional(str)
891 """
891 """
892 repo = get_repo_or_error(repoid)
892 repo = get_repo_or_error(repoid)
893 if not has_superadmin_permission(apiuser):
893 if not has_superadmin_permission(apiuser):
894 _perms = ('repository.admin',)
894 _perms = ('repository.admin',)
895 validate_repo_permissions(apiuser, repoid, repo, _perms)
895 validate_repo_permissions(apiuser, repoid, repo, _perms)
896
896
897 label = Optional.extract(label) or key
897 label = Optional.extract(label) or key
898 description = Optional.extract(description)
898 description = Optional.extract(description)
899
899
900 field = RepositoryField.get_by_key_name(key, repo)
900 field = RepositoryField.get_by_key_name(key, repo)
901 if field:
901 if field:
902 raise JSONRPCError('Field with key '
902 raise JSONRPCError('Field with key '
903 '`%s` exists for repo `%s`' % (key, repoid))
903 '`%s` exists for repo `%s`' % (key, repoid))
904
904
905 try:
905 try:
906 RepoModel().add_repo_field(repo, key, field_label=label,
906 RepoModel().add_repo_field(repo, key, field_label=label,
907 field_desc=description)
907 field_desc=description)
908 Session().commit()
908 Session().commit()
909 return {
909 return {
910 'msg': "Added new repository field `%s`" % (key,),
910 'msg': "Added new repository field `%s`" % (key,),
911 'success': True,
911 'success': True,
912 }
912 }
913 except Exception:
913 except Exception:
914 log.exception("Exception occurred while trying to add field to repo")
914 log.exception("Exception occurred while trying to add field to repo")
915 raise JSONRPCError(
915 raise JSONRPCError(
916 'failed to create new field for repository `%s`' % (repoid,))
916 'failed to create new field for repository `%s`' % (repoid,))
917
917
918
918
919 @jsonrpc_method()
919 @jsonrpc_method()
920 def remove_field_from_repo(request, apiuser, repoid, key):
920 def remove_field_from_repo(request, apiuser, repoid, key):
921 """
921 """
922 Removes an extra field from a repository.
922 Removes an extra field from a repository.
923
923
924 This command can only be run using an |authtoken| with at least
924 This command can only be run using an |authtoken| with at least
925 write permissions to the |repo|.
925 write permissions to the |repo|.
926
926
927 :param apiuser: This is filled automatically from the |authtoken|.
927 :param apiuser: This is filled automatically from the |authtoken|.
928 :type apiuser: AuthUser
928 :type apiuser: AuthUser
929 :param repoid: Set the repository name or repository ID.
929 :param repoid: Set the repository name or repository ID.
930 :type repoid: str or int
930 :type repoid: str or int
931 :param key: Set the unique field key for this repository.
931 :param key: Set the unique field key for this repository.
932 :type key: str
932 :type key: str
933 """
933 """
934
934
935 repo = get_repo_or_error(repoid)
935 repo = get_repo_or_error(repoid)
936 if not has_superadmin_permission(apiuser):
936 if not has_superadmin_permission(apiuser):
937 _perms = ('repository.admin',)
937 _perms = ('repository.admin',)
938 validate_repo_permissions(apiuser, repoid, repo, _perms)
938 validate_repo_permissions(apiuser, repoid, repo, _perms)
939
939
940 field = RepositoryField.get_by_key_name(key, repo)
940 field = RepositoryField.get_by_key_name(key, repo)
941 if not field:
941 if not field:
942 raise JSONRPCError('Field with key `%s` does not '
942 raise JSONRPCError('Field with key `%s` does not '
943 'exists for repo `%s`' % (key, repoid))
943 'exists for repo `%s`' % (key, repoid))
944
944
945 try:
945 try:
946 RepoModel().delete_repo_field(repo, field_key=key)
946 RepoModel().delete_repo_field(repo, field_key=key)
947 Session().commit()
947 Session().commit()
948 return {
948 return {
949 'msg': "Deleted repository field `%s`" % (key,),
949 'msg': "Deleted repository field `%s`" % (key,),
950 'success': True,
950 'success': True,
951 }
951 }
952 except Exception:
952 except Exception:
953 log.exception(
953 log.exception(
954 "Exception occurred while trying to delete field from repo")
954 "Exception occurred while trying to delete field from repo")
955 raise JSONRPCError(
955 raise JSONRPCError(
956 'failed to delete field for repository `%s`' % (repoid,))
956 'failed to delete field for repository `%s`' % (repoid,))
957
957
958
958
959 @jsonrpc_method()
959 @jsonrpc_method()
960 def update_repo(
960 def update_repo(
961 request, apiuser, repoid, repo_name=Optional(None),
961 request, apiuser, repoid, repo_name=Optional(None),
962 owner=Optional(OAttr('apiuser')), description=Optional(''),
962 owner=Optional(OAttr('apiuser')), description=Optional(''),
963 private=Optional(False),
963 private=Optional(False),
964 clone_uri=Optional(None), push_uri=Optional(None),
964 clone_uri=Optional(None), push_uri=Optional(None),
965 landing_rev=Optional(None), fork_of=Optional(None),
965 landing_rev=Optional(None), fork_of=Optional(None),
966 enable_statistics=Optional(False),
966 enable_statistics=Optional(False),
967 enable_locking=Optional(False),
967 enable_locking=Optional(False),
968 enable_downloads=Optional(False), fields=Optional('')):
968 enable_downloads=Optional(False), fields=Optional('')):
969 """
969 """
970 Updates a repository with the given information.
970 Updates a repository with the given information.
971
971
972 This command can only be run using an |authtoken| with at least
972 This command can only be run using an |authtoken| with at least
973 admin permissions to the |repo|.
973 admin permissions to the |repo|.
974
974
975 * If the repository name contains "/", repository will be updated
975 * If the repository name contains "/", repository will be updated
976 accordingly with a repository group or nested repository groups
976 accordingly with a repository group or nested repository groups
977
977
978 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
978 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
979 called "repo-test" and place it inside group "foo/bar".
979 called "repo-test" and place it inside group "foo/bar".
980 You have to have permissions to access and write to the last repository
980 You have to have permissions to access and write to the last repository
981 group ("bar" in this example)
981 group ("bar" in this example)
982
982
983 :param apiuser: This is filled automatically from the |authtoken|.
983 :param apiuser: This is filled automatically from the |authtoken|.
984 :type apiuser: AuthUser
984 :type apiuser: AuthUser
985 :param repoid: repository name or repository ID.
985 :param repoid: repository name or repository ID.
986 :type repoid: str or int
986 :type repoid: str or int
987 :param repo_name: Update the |repo| name, including the
987 :param repo_name: Update the |repo| name, including the
988 repository group it's in.
988 repository group it's in.
989 :type repo_name: str
989 :type repo_name: str
990 :param owner: Set the |repo| owner.
990 :param owner: Set the |repo| owner.
991 :type owner: str
991 :type owner: str
992 :param fork_of: Set the |repo| as fork of another |repo|.
992 :param fork_of: Set the |repo| as fork of another |repo|.
993 :type fork_of: str
993 :type fork_of: str
994 :param description: Update the |repo| description.
994 :param description: Update the |repo| description.
995 :type description: str
995 :type description: str
996 :param private: Set the |repo| as private. (True | False)
996 :param private: Set the |repo| as private. (True | False)
997 :type private: bool
997 :type private: bool
998 :param clone_uri: Update the |repo| clone URI.
998 :param clone_uri: Update the |repo| clone URI.
999 :type clone_uri: str
999 :type clone_uri: str
1000 :param landing_rev: Set the |repo| landing revision. e.g branch:default, book:dev, rev:abcd
1000 :param landing_rev: Set the |repo| landing revision. e.g branch:default, book:dev, rev:abcd
1001 :type landing_rev: str
1001 :type landing_rev: str
1002 :param enable_statistics: Enable statistics on the |repo|, (True | False).
1002 :param enable_statistics: Enable statistics on the |repo|, (True | False).
1003 :type enable_statistics: bool
1003 :type enable_statistics: bool
1004 :param enable_locking: Enable |repo| locking.
1004 :param enable_locking: Enable |repo| locking.
1005 :type enable_locking: bool
1005 :type enable_locking: bool
1006 :param enable_downloads: Enable downloads from the |repo|, (True | False).
1006 :param enable_downloads: Enable downloads from the |repo|, (True | False).
1007 :type enable_downloads: bool
1007 :type enable_downloads: bool
1008 :param fields: Add extra fields to the |repo|. Use the following
1008 :param fields: Add extra fields to the |repo|. Use the following
1009 example format: ``field_key=field_val,field_key2=fieldval2``.
1009 example format: ``field_key=field_val,field_key2=fieldval2``.
1010 Escape ', ' with \,
1010 Escape ', ' with \,
1011 :type fields: str
1011 :type fields: str
1012 """
1012 """
1013
1013
1014 repo = get_repo_or_error(repoid)
1014 repo = get_repo_or_error(repoid)
1015
1015
1016 include_secrets = False
1016 include_secrets = False
1017 if not has_superadmin_permission(apiuser):
1017 if not has_superadmin_permission(apiuser):
1018 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
1018 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
1019 else:
1019 else:
1020 include_secrets = True
1020 include_secrets = True
1021
1021
1022 updates = dict(
1022 updates = dict(
1023 repo_name=repo_name
1023 repo_name=repo_name
1024 if not isinstance(repo_name, Optional) else repo.repo_name,
1024 if not isinstance(repo_name, Optional) else repo.repo_name,
1025
1025
1026 fork_id=fork_of
1026 fork_id=fork_of
1027 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
1027 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
1028
1028
1029 user=owner
1029 user=owner
1030 if not isinstance(owner, Optional) else repo.user.username,
1030 if not isinstance(owner, Optional) else repo.user.username,
1031
1031
1032 repo_description=description
1032 repo_description=description
1033 if not isinstance(description, Optional) else repo.description,
1033 if not isinstance(description, Optional) else repo.description,
1034
1034
1035 repo_private=private
1035 repo_private=private
1036 if not isinstance(private, Optional) else repo.private,
1036 if not isinstance(private, Optional) else repo.private,
1037
1037
1038 clone_uri=clone_uri
1038 clone_uri=clone_uri
1039 if not isinstance(clone_uri, Optional) else repo.clone_uri,
1039 if not isinstance(clone_uri, Optional) else repo.clone_uri,
1040
1040
1041 push_uri=push_uri
1041 push_uri=push_uri
1042 if not isinstance(push_uri, Optional) else repo.push_uri,
1042 if not isinstance(push_uri, Optional) else repo.push_uri,
1043
1043
1044 repo_landing_rev=landing_rev
1044 repo_landing_rev=landing_rev
1045 if not isinstance(landing_rev, Optional) else repo._landing_revision,
1045 if not isinstance(landing_rev, Optional) else repo._landing_revision,
1046
1046
1047 repo_enable_statistics=enable_statistics
1047 repo_enable_statistics=enable_statistics
1048 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
1048 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
1049
1049
1050 repo_enable_locking=enable_locking
1050 repo_enable_locking=enable_locking
1051 if not isinstance(enable_locking, Optional) else repo.enable_locking,
1051 if not isinstance(enable_locking, Optional) else repo.enable_locking,
1052
1052
1053 repo_enable_downloads=enable_downloads
1053 repo_enable_downloads=enable_downloads
1054 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
1054 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
1055
1055
1056 landing_ref, _label = ScmModel.backend_landing_ref(repo.repo_type)
1056 landing_ref, _label = ScmModel.backend_landing_ref(repo.repo_type)
1057 ref_choices, _labels = ScmModel().get_repo_landing_revs(
1057 ref_choices, _labels = ScmModel().get_repo_landing_revs(
1058 request.translate, repo=repo)
1058 request.translate, repo=repo)
1059 ref_choices = list(set(ref_choices + [landing_ref]))
1059 ref_choices = list(set(ref_choices + [landing_ref]))
1060
1060
1061 old_values = repo.get_api_data()
1061 old_values = repo.get_api_data()
1062 repo_type = repo.repo_type
1062 repo_type = repo.repo_type
1063 schema = repo_schema.RepoSchema().bind(
1063 schema = repo_schema.RepoSchema().bind(
1064 repo_type_options=rhodecode.BACKENDS.keys(),
1064 repo_type_options=rhodecode.BACKENDS.keys(),
1065 repo_ref_options=ref_choices,
1065 repo_ref_options=ref_choices,
1066 repo_type=repo_type,
1066 repo_type=repo_type,
1067 # user caller
1067 # user caller
1068 user=apiuser,
1068 user=apiuser,
1069 old_values=old_values)
1069 old_values=old_values)
1070 try:
1070 try:
1071 schema_data = schema.deserialize(dict(
1071 schema_data = schema.deserialize(dict(
1072 # we save old value, users cannot change type
1072 # we save old value, users cannot change type
1073 repo_type=repo_type,
1073 repo_type=repo_type,
1074
1074
1075 repo_name=updates['repo_name'],
1075 repo_name=updates['repo_name'],
1076 repo_owner=updates['user'],
1076 repo_owner=updates['user'],
1077 repo_description=updates['repo_description'],
1077 repo_description=updates['repo_description'],
1078 repo_clone_uri=updates['clone_uri'],
1078 repo_clone_uri=updates['clone_uri'],
1079 repo_push_uri=updates['push_uri'],
1079 repo_push_uri=updates['push_uri'],
1080 repo_fork_of=updates['fork_id'],
1080 repo_fork_of=updates['fork_id'],
1081 repo_private=updates['repo_private'],
1081 repo_private=updates['repo_private'],
1082 repo_landing_commit_ref=updates['repo_landing_rev'],
1082 repo_landing_commit_ref=updates['repo_landing_rev'],
1083 repo_enable_statistics=updates['repo_enable_statistics'],
1083 repo_enable_statistics=updates['repo_enable_statistics'],
1084 repo_enable_downloads=updates['repo_enable_downloads'],
1084 repo_enable_downloads=updates['repo_enable_downloads'],
1085 repo_enable_locking=updates['repo_enable_locking']))
1085 repo_enable_locking=updates['repo_enable_locking']))
1086 except validation_schema.Invalid as err:
1086 except validation_schema.Invalid as err:
1087 raise JSONRPCValidationError(colander_exc=err)
1087 raise JSONRPCValidationError(colander_exc=err)
1088
1088
1089 # save validated data back into the updates dict
1089 # save validated data back into the updates dict
1090 validated_updates = dict(
1090 validated_updates = dict(
1091 repo_name=schema_data['repo_group']['repo_name_without_group'],
1091 repo_name=schema_data['repo_group']['repo_name_without_group'],
1092 repo_group=schema_data['repo_group']['repo_group_id'],
1092 repo_group=schema_data['repo_group']['repo_group_id'],
1093
1093
1094 user=schema_data['repo_owner'],
1094 user=schema_data['repo_owner'],
1095 repo_description=schema_data['repo_description'],
1095 repo_description=schema_data['repo_description'],
1096 repo_private=schema_data['repo_private'],
1096 repo_private=schema_data['repo_private'],
1097 clone_uri=schema_data['repo_clone_uri'],
1097 clone_uri=schema_data['repo_clone_uri'],
1098 push_uri=schema_data['repo_push_uri'],
1098 push_uri=schema_data['repo_push_uri'],
1099 repo_landing_rev=schema_data['repo_landing_commit_ref'],
1099 repo_landing_rev=schema_data['repo_landing_commit_ref'],
1100 repo_enable_statistics=schema_data['repo_enable_statistics'],
1100 repo_enable_statistics=schema_data['repo_enable_statistics'],
1101 repo_enable_locking=schema_data['repo_enable_locking'],
1101 repo_enable_locking=schema_data['repo_enable_locking'],
1102 repo_enable_downloads=schema_data['repo_enable_downloads'],
1102 repo_enable_downloads=schema_data['repo_enable_downloads'],
1103 )
1103 )
1104
1104
1105 if schema_data['repo_fork_of']:
1105 if schema_data['repo_fork_of']:
1106 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
1106 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
1107 validated_updates['fork_id'] = fork_repo.repo_id
1107 validated_updates['fork_id'] = fork_repo.repo_id
1108
1108
1109 # extra fields
1109 # extra fields
1110 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
1110 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
1111 if fields:
1111 if fields:
1112 validated_updates.update(fields)
1112 validated_updates.update(fields)
1113
1113
1114 try:
1114 try:
1115 RepoModel().update(repo, **validated_updates)
1115 RepoModel().update(repo, **validated_updates)
1116 audit_logger.store_api(
1116 audit_logger.store_api(
1117 'repo.edit', action_data={'old_data': old_values},
1117 'repo.edit', action_data={'old_data': old_values},
1118 user=apiuser, repo=repo)
1118 user=apiuser, repo=repo)
1119 Session().commit()
1119 Session().commit()
1120 return {
1120 return {
1121 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
1121 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
1122 'repository': repo.get_api_data(include_secrets=include_secrets)
1122 'repository': repo.get_api_data(include_secrets=include_secrets)
1123 }
1123 }
1124 except Exception:
1124 except Exception:
1125 log.exception(
1125 log.exception(
1126 u"Exception while trying to update the repository %s",
1126 u"Exception while trying to update the repository %s",
1127 repoid)
1127 repoid)
1128 raise JSONRPCError('failed to update repo `%s`' % repoid)
1128 raise JSONRPCError('failed to update repo `%s`' % repoid)
1129
1129
1130
1130
1131 @jsonrpc_method()
1131 @jsonrpc_method()
1132 def fork_repo(request, apiuser, repoid, fork_name,
1132 def fork_repo(request, apiuser, repoid, fork_name,
1133 owner=Optional(OAttr('apiuser')),
1133 owner=Optional(OAttr('apiuser')),
1134 description=Optional(''),
1134 description=Optional(''),
1135 private=Optional(False),
1135 private=Optional(False),
1136 clone_uri=Optional(None),
1136 clone_uri=Optional(None),
1137 landing_rev=Optional(None),
1137 landing_rev=Optional(None),
1138 copy_permissions=Optional(False)):
1138 copy_permissions=Optional(False)):
1139 """
1139 """
1140 Creates a fork of the specified |repo|.
1140 Creates a fork of the specified |repo|.
1141
1141
1142 * If the fork_name contains "/", fork will be created inside
1142 * If the fork_name contains "/", fork will be created inside
1143 a repository group or nested repository groups
1143 a repository group or nested repository groups
1144
1144
1145 For example "foo/bar/fork-repo" will create fork called "fork-repo"
1145 For example "foo/bar/fork-repo" will create fork called "fork-repo"
1146 inside group "foo/bar". You have to have permissions to access and
1146 inside group "foo/bar". You have to have permissions to access and
1147 write to the last repository group ("bar" in this example)
1147 write to the last repository group ("bar" in this example)
1148
1148
1149 This command can only be run using an |authtoken| with minimum
1149 This command can only be run using an |authtoken| with minimum
1150 read permissions of the forked repo, create fork permissions for an user.
1150 read permissions of the forked repo, create fork permissions for an user.
1151
1151
1152 :param apiuser: This is filled automatically from the |authtoken|.
1152 :param apiuser: This is filled automatically from the |authtoken|.
1153 :type apiuser: AuthUser
1153 :type apiuser: AuthUser
1154 :param repoid: Set repository name or repository ID.
1154 :param repoid: Set repository name or repository ID.
1155 :type repoid: str or int
1155 :type repoid: str or int
1156 :param fork_name: Set the fork name, including it's repository group membership.
1156 :param fork_name: Set the fork name, including it's repository group membership.
1157 :type fork_name: str
1157 :type fork_name: str
1158 :param owner: Set the fork owner.
1158 :param owner: Set the fork owner.
1159 :type owner: str
1159 :type owner: str
1160 :param description: Set the fork description.
1160 :param description: Set the fork description.
1161 :type description: str
1161 :type description: str
1162 :param copy_permissions: Copy permissions from parent |repo|. The
1162 :param copy_permissions: Copy permissions from parent |repo|. The
1163 default is False.
1163 default is False.
1164 :type copy_permissions: bool
1164 :type copy_permissions: bool
1165 :param private: Make the fork private. The default is False.
1165 :param private: Make the fork private. The default is False.
1166 :type private: bool
1166 :type private: bool
1167 :param landing_rev: Set the landing revision. E.g branch:default, book:dev, rev:abcd
1167 :param landing_rev: Set the landing revision. E.g branch:default, book:dev, rev:abcd
1168
1168
1169 Example output:
1169 Example output:
1170
1170
1171 .. code-block:: bash
1171 .. code-block:: bash
1172
1172
1173 id : <id_for_response>
1173 id : <id_for_response>
1174 api_key : "<api_key>"
1174 api_key : "<api_key>"
1175 args: {
1175 args: {
1176 "repoid" : "<reponame or repo_id>",
1176 "repoid" : "<reponame or repo_id>",
1177 "fork_name": "<forkname>",
1177 "fork_name": "<forkname>",
1178 "owner": "<username or user_id = Optional(=apiuser)>",
1178 "owner": "<username or user_id = Optional(=apiuser)>",
1179 "description": "<description>",
1179 "description": "<description>",
1180 "copy_permissions": "<bool>",
1180 "copy_permissions": "<bool>",
1181 "private": "<bool>",
1181 "private": "<bool>",
1182 "landing_rev": "<landing_rev>"
1182 "landing_rev": "<landing_rev>"
1183 }
1183 }
1184
1184
1185 Example error output:
1185 Example error output:
1186
1186
1187 .. code-block:: bash
1187 .. code-block:: bash
1188
1188
1189 id : <id_given_in_input>
1189 id : <id_given_in_input>
1190 result: {
1190 result: {
1191 "msg": "Created fork of `<reponame>` as `<forkname>`",
1191 "msg": "Created fork of `<reponame>` as `<forkname>`",
1192 "success": true,
1192 "success": true,
1193 "task": "<celery task id or None if done sync>"
1193 "task": "<celery task id or None if done sync>"
1194 }
1194 }
1195 error: null
1195 error: null
1196
1196
1197 """
1197 """
1198
1198
1199 repo = get_repo_or_error(repoid)
1199 repo = get_repo_or_error(repoid)
1200 repo_name = repo.repo_name
1200 repo_name = repo.repo_name
1201
1201
1202 if not has_superadmin_permission(apiuser):
1202 if not has_superadmin_permission(apiuser):
1203 # check if we have at least read permission for
1203 # check if we have at least read permission for
1204 # this repo that we fork !
1204 # this repo that we fork !
1205 _perms = (
1205 _perms = (
1206 'repository.admin', 'repository.write', 'repository.read')
1206 'repository.admin', 'repository.write', 'repository.read')
1207 validate_repo_permissions(apiuser, repoid, repo, _perms)
1207 validate_repo_permissions(apiuser, repoid, repo, _perms)
1208
1208
1209 # check if the regular user has at least fork permissions as well
1209 # check if the regular user has at least fork permissions as well
1210 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1210 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1211 raise JSONRPCForbidden()
1211 raise JSONRPCForbidden()
1212
1212
1213 # check if user can set owner parameter
1213 # check if user can set owner parameter
1214 owner = validate_set_owner_permissions(apiuser, owner)
1214 owner = validate_set_owner_permissions(apiuser, owner)
1215
1215
1216 description = Optional.extract(description)
1216 description = Optional.extract(description)
1217 copy_permissions = Optional.extract(copy_permissions)
1217 copy_permissions = Optional.extract(copy_permissions)
1218 clone_uri = Optional.extract(clone_uri)
1218 clone_uri = Optional.extract(clone_uri)
1219
1219
1220 landing_ref, _label = ScmModel.backend_landing_ref(repo.repo_type)
1220 landing_ref, _label = ScmModel.backend_landing_ref(repo.repo_type)
1221 ref_choices, _labels = ScmModel().get_repo_landing_revs(request.translate)
1221 ref_choices, _labels = ScmModel().get_repo_landing_revs(request.translate)
1222 ref_choices = list(set(ref_choices + [landing_ref]))
1222 ref_choices = list(set(ref_choices + [landing_ref]))
1223 landing_commit_ref = Optional.extract(landing_rev) or landing_ref
1223 landing_commit_ref = Optional.extract(landing_rev) or landing_ref
1224
1224
1225 private = Optional.extract(private)
1225 private = Optional.extract(private)
1226
1226
1227 schema = repo_schema.RepoSchema().bind(
1227 schema = repo_schema.RepoSchema().bind(
1228 repo_type_options=rhodecode.BACKENDS.keys(),
1228 repo_type_options=rhodecode.BACKENDS.keys(),
1229 repo_ref_options=ref_choices,
1229 repo_ref_options=ref_choices,
1230 repo_type=repo.repo_type,
1230 repo_type=repo.repo_type,
1231 # user caller
1231 # user caller
1232 user=apiuser)
1232 user=apiuser)
1233
1233
1234 try:
1234 try:
1235 schema_data = schema.deserialize(dict(
1235 schema_data = schema.deserialize(dict(
1236 repo_name=fork_name,
1236 repo_name=fork_name,
1237 repo_type=repo.repo_type,
1237 repo_type=repo.repo_type,
1238 repo_owner=owner.username,
1238 repo_owner=owner.username,
1239 repo_description=description,
1239 repo_description=description,
1240 repo_landing_commit_ref=landing_commit_ref,
1240 repo_landing_commit_ref=landing_commit_ref,
1241 repo_clone_uri=clone_uri,
1241 repo_clone_uri=clone_uri,
1242 repo_private=private,
1242 repo_private=private,
1243 repo_copy_permissions=copy_permissions))
1243 repo_copy_permissions=copy_permissions))
1244 except validation_schema.Invalid as err:
1244 except validation_schema.Invalid as err:
1245 raise JSONRPCValidationError(colander_exc=err)
1245 raise JSONRPCValidationError(colander_exc=err)
1246
1246
1247 try:
1247 try:
1248 data = {
1248 data = {
1249 'fork_parent_id': repo.repo_id,
1249 'fork_parent_id': repo.repo_id,
1250
1250
1251 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1251 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1252 'repo_name_full': schema_data['repo_name'],
1252 'repo_name_full': schema_data['repo_name'],
1253 'repo_group': schema_data['repo_group']['repo_group_id'],
1253 'repo_group': schema_data['repo_group']['repo_group_id'],
1254 'repo_type': schema_data['repo_type'],
1254 'repo_type': schema_data['repo_type'],
1255 'description': schema_data['repo_description'],
1255 'description': schema_data['repo_description'],
1256 'private': schema_data['repo_private'],
1256 'private': schema_data['repo_private'],
1257 'copy_permissions': schema_data['repo_copy_permissions'],
1257 'copy_permissions': schema_data['repo_copy_permissions'],
1258 'landing_rev': schema_data['repo_landing_commit_ref'],
1258 'landing_rev': schema_data['repo_landing_commit_ref'],
1259 }
1259 }
1260
1260
1261 task = RepoModel().create_fork(data, cur_user=owner.user_id)
1261 task = RepoModel().create_fork(data, cur_user=owner.user_id)
1262 # no commit, it's done in RepoModel, or async via celery
1262 # no commit, it's done in RepoModel, or async via celery
1263 task_id = get_task_id(task)
1263 task_id = get_task_id(task)
1264
1264
1265 return {
1265 return {
1266 'msg': 'Created fork of `%s` as `%s`' % (
1266 'msg': 'Created fork of `%s` as `%s`' % (
1267 repo.repo_name, schema_data['repo_name']),
1267 repo.repo_name, schema_data['repo_name']),
1268 'success': True, # cannot return the repo data here since fork
1268 'success': True, # cannot return the repo data here since fork
1269 # can be done async
1269 # can be done async
1270 'task': task_id
1270 'task': task_id
1271 }
1271 }
1272 except Exception:
1272 except Exception:
1273 log.exception(
1273 log.exception(
1274 u"Exception while trying to create fork %s",
1274 u"Exception while trying to create fork %s",
1275 schema_data['repo_name'])
1275 schema_data['repo_name'])
1276 raise JSONRPCError(
1276 raise JSONRPCError(
1277 'failed to fork repository `%s` as `%s`' % (
1277 'failed to fork repository `%s` as `%s`' % (
1278 repo_name, schema_data['repo_name']))
1278 repo_name, schema_data['repo_name']))
1279
1279
1280
1280
1281 @jsonrpc_method()
1281 @jsonrpc_method()
1282 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1282 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1283 """
1283 """
1284 Deletes a repository.
1284 Deletes a repository.
1285
1285
1286 * When the `forks` parameter is set it's possible to detach or delete
1286 * When the `forks` parameter is set it's possible to detach or delete
1287 forks of deleted repository.
1287 forks of deleted repository.
1288
1288
1289 This command can only be run using an |authtoken| with admin
1289 This command can only be run using an |authtoken| with admin
1290 permissions on the |repo|.
1290 permissions on the |repo|.
1291
1291
1292 :param apiuser: This is filled automatically from the |authtoken|.
1292 :param apiuser: This is filled automatically from the |authtoken|.
1293 :type apiuser: AuthUser
1293 :type apiuser: AuthUser
1294 :param repoid: Set the repository name or repository ID.
1294 :param repoid: Set the repository name or repository ID.
1295 :type repoid: str or int
1295 :type repoid: str or int
1296 :param forks: Set to `detach` or `delete` forks from the |repo|.
1296 :param forks: Set to `detach` or `delete` forks from the |repo|.
1297 :type forks: Optional(str)
1297 :type forks: Optional(str)
1298
1298
1299 Example error output:
1299 Example error output:
1300
1300
1301 .. code-block:: bash
1301 .. code-block:: bash
1302
1302
1303 id : <id_given_in_input>
1303 id : <id_given_in_input>
1304 result: {
1304 result: {
1305 "msg": "Deleted repository `<reponame>`",
1305 "msg": "Deleted repository `<reponame>`",
1306 "success": true
1306 "success": true
1307 }
1307 }
1308 error: null
1308 error: null
1309 """
1309 """
1310
1310
1311 repo = get_repo_or_error(repoid)
1311 repo = get_repo_or_error(repoid)
1312 repo_name = repo.repo_name
1312 repo_name = repo.repo_name
1313 if not has_superadmin_permission(apiuser):
1313 if not has_superadmin_permission(apiuser):
1314 _perms = ('repository.admin',)
1314 _perms = ('repository.admin',)
1315 validate_repo_permissions(apiuser, repoid, repo, _perms)
1315 validate_repo_permissions(apiuser, repoid, repo, _perms)
1316
1316
1317 try:
1317 try:
1318 handle_forks = Optional.extract(forks)
1318 handle_forks = Optional.extract(forks)
1319 _forks_msg = ''
1319 _forks_msg = ''
1320 _forks = [f for f in repo.forks]
1320 _forks = [f for f in repo.forks]
1321 if handle_forks == 'detach':
1321 if handle_forks == 'detach':
1322 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1322 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1323 elif handle_forks == 'delete':
1323 elif handle_forks == 'delete':
1324 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1324 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1325 elif _forks:
1325 elif _forks:
1326 raise JSONRPCError(
1326 raise JSONRPCError(
1327 'Cannot delete `%s` it still contains attached forks' %
1327 'Cannot delete `%s` it still contains attached forks' %
1328 (repo.repo_name,)
1328 (repo.repo_name,)
1329 )
1329 )
1330 old_data = repo.get_api_data()
1330 old_data = repo.get_api_data()
1331 RepoModel().delete(repo, forks=forks)
1331 RepoModel().delete(repo, forks=forks)
1332
1332
1333 repo = audit_logger.RepoWrap(repo_id=None,
1333 repo = audit_logger.RepoWrap(repo_id=None,
1334 repo_name=repo.repo_name)
1334 repo_name=repo.repo_name)
1335
1335
1336 audit_logger.store_api(
1336 audit_logger.store_api(
1337 'repo.delete', action_data={'old_data': old_data},
1337 'repo.delete', action_data={'old_data': old_data},
1338 user=apiuser, repo=repo)
1338 user=apiuser, repo=repo)
1339
1339
1340 ScmModel().mark_for_invalidation(repo_name, delete=True)
1340 ScmModel().mark_for_invalidation(repo_name, delete=True)
1341 Session().commit()
1341 Session().commit()
1342 return {
1342 return {
1343 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg),
1343 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg),
1344 'success': True
1344 'success': True
1345 }
1345 }
1346 except Exception:
1346 except Exception:
1347 log.exception("Exception occurred while trying to delete repo")
1347 log.exception("Exception occurred while trying to delete repo")
1348 raise JSONRPCError(
1348 raise JSONRPCError(
1349 'failed to delete repository `%s`' % (repo_name,)
1349 'failed to delete repository `%s`' % (repo_name,)
1350 )
1350 )
1351
1351
1352
1352
1353 #TODO: marcink, change name ?
1353 #TODO: marcink, change name ?
1354 @jsonrpc_method()
1354 @jsonrpc_method()
1355 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1355 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1356 """
1356 """
1357 Invalidates the cache for the specified repository.
1357 Invalidates the cache for the specified repository.
1358
1358
1359 This command can only be run using an |authtoken| with admin rights to
1359 This command can only be run using an |authtoken| with admin rights to
1360 the specified repository.
1360 the specified repository.
1361
1361
1362 This command takes the following options:
1362 This command takes the following options:
1363
1363
1364 :param apiuser: This is filled automatically from |authtoken|.
1364 :param apiuser: This is filled automatically from |authtoken|.
1365 :type apiuser: AuthUser
1365 :type apiuser: AuthUser
1366 :param repoid: Sets the repository name or repository ID.
1366 :param repoid: Sets the repository name or repository ID.
1367 :type repoid: str or int
1367 :type repoid: str or int
1368 :param delete_keys: This deletes the invalidated keys instead of
1368 :param delete_keys: This deletes the invalidated keys instead of
1369 just flagging them.
1369 just flagging them.
1370 :type delete_keys: Optional(``True`` | ``False``)
1370 :type delete_keys: Optional(``True`` | ``False``)
1371
1371
1372 Example output:
1372 Example output:
1373
1373
1374 .. code-block:: bash
1374 .. code-block:: bash
1375
1375
1376 id : <id_given_in_input>
1376 id : <id_given_in_input>
1377 result : {
1377 result : {
1378 'msg': Cache for repository `<repository name>` was invalidated,
1378 'msg': Cache for repository `<repository name>` was invalidated,
1379 'repository': <repository name>
1379 'repository': <repository name>
1380 }
1380 }
1381 error : null
1381 error : null
1382
1382
1383 Example error output:
1383 Example error output:
1384
1384
1385 .. code-block:: bash
1385 .. code-block:: bash
1386
1386
1387 id : <id_given_in_input>
1387 id : <id_given_in_input>
1388 result : null
1388 result : null
1389 error : {
1389 error : {
1390 'Error occurred during cache invalidation action'
1390 'Error occurred during cache invalidation action'
1391 }
1391 }
1392
1392
1393 """
1393 """
1394
1394
1395 repo = get_repo_or_error(repoid)
1395 repo = get_repo_or_error(repoid)
1396 if not has_superadmin_permission(apiuser):
1396 if not has_superadmin_permission(apiuser):
1397 _perms = ('repository.admin', 'repository.write',)
1397 _perms = ('repository.admin', 'repository.write',)
1398 validate_repo_permissions(apiuser, repoid, repo, _perms)
1398 validate_repo_permissions(apiuser, repoid, repo, _perms)
1399
1399
1400 delete = Optional.extract(delete_keys)
1400 delete = Optional.extract(delete_keys)
1401 try:
1401 try:
1402 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1402 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1403 return {
1403 return {
1404 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1404 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1405 'repository': repo.repo_name
1405 'repository': repo.repo_name
1406 }
1406 }
1407 except Exception:
1407 except Exception:
1408 log.exception(
1408 log.exception(
1409 "Exception occurred while trying to invalidate repo cache")
1409 "Exception occurred while trying to invalidate repo cache")
1410 raise JSONRPCError(
1410 raise JSONRPCError(
1411 'Error occurred during cache invalidation action'
1411 'Error occurred during cache invalidation action'
1412 )
1412 )
1413
1413
1414
1414
1415 #TODO: marcink, change name ?
1415 #TODO: marcink, change name ?
1416 @jsonrpc_method()
1416 @jsonrpc_method()
1417 def lock(request, apiuser, repoid, locked=Optional(None),
1417 def lock(request, apiuser, repoid, locked=Optional(None),
1418 userid=Optional(OAttr('apiuser'))):
1418 userid=Optional(OAttr('apiuser'))):
1419 """
1419 """
1420 Sets the lock state of the specified |repo| by the given user.
1420 Sets the lock state of the specified |repo| by the given user.
1421 From more information, see :ref:`repo-locking`.
1421 From more information, see :ref:`repo-locking`.
1422
1422
1423 * If the ``userid`` option is not set, the repository is locked to the
1423 * If the ``userid`` option is not set, the repository is locked to the
1424 user who called the method.
1424 user who called the method.
1425 * If the ``locked`` parameter is not set, the current lock state of the
1425 * If the ``locked`` parameter is not set, the current lock state of the
1426 repository is displayed.
1426 repository is displayed.
1427
1427
1428 This command can only be run using an |authtoken| with admin rights to
1428 This command can only be run using an |authtoken| with admin rights to
1429 the specified repository.
1429 the specified repository.
1430
1430
1431 This command takes the following options:
1431 This command takes the following options:
1432
1432
1433 :param apiuser: This is filled automatically from the |authtoken|.
1433 :param apiuser: This is filled automatically from the |authtoken|.
1434 :type apiuser: AuthUser
1434 :type apiuser: AuthUser
1435 :param repoid: Sets the repository name or repository ID.
1435 :param repoid: Sets the repository name or repository ID.
1436 :type repoid: str or int
1436 :type repoid: str or int
1437 :param locked: Sets the lock state.
1437 :param locked: Sets the lock state.
1438 :type locked: Optional(``True`` | ``False``)
1438 :type locked: Optional(``True`` | ``False``)
1439 :param userid: Set the repository lock to this user.
1439 :param userid: Set the repository lock to this user.
1440 :type userid: Optional(str or int)
1440 :type userid: Optional(str or int)
1441
1441
1442 Example error output:
1442 Example error output:
1443
1443
1444 .. code-block:: bash
1444 .. code-block:: bash
1445
1445
1446 id : <id_given_in_input>
1446 id : <id_given_in_input>
1447 result : {
1447 result : {
1448 'repo': '<reponame>',
1448 'repo': '<reponame>',
1449 'locked': <bool: lock state>,
1449 'locked': <bool: lock state>,
1450 'locked_since': <int: lock timestamp>,
1450 'locked_since': <int: lock timestamp>,
1451 'locked_by': <username of person who made the lock>,
1451 'locked_by': <username of person who made the lock>,
1452 'lock_reason': <str: reason for locking>,
1452 'lock_reason': <str: reason for locking>,
1453 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1453 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1454 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1454 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1455 or
1455 or
1456 'msg': 'Repo `<repository name>` not locked.'
1456 'msg': 'Repo `<repository name>` not locked.'
1457 or
1457 or
1458 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1458 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1459 }
1459 }
1460 error : null
1460 error : null
1461
1461
1462 Example error output:
1462 Example error output:
1463
1463
1464 .. code-block:: bash
1464 .. code-block:: bash
1465
1465
1466 id : <id_given_in_input>
1466 id : <id_given_in_input>
1467 result : null
1467 result : null
1468 error : {
1468 error : {
1469 'Error occurred locking repository `<reponame>`'
1469 'Error occurred locking repository `<reponame>`'
1470 }
1470 }
1471 """
1471 """
1472
1472
1473 repo = get_repo_or_error(repoid)
1473 repo = get_repo_or_error(repoid)
1474 if not has_superadmin_permission(apiuser):
1474 if not has_superadmin_permission(apiuser):
1475 # check if we have at least write permission for this repo !
1475 # check if we have at least write permission for this repo !
1476 _perms = ('repository.admin', 'repository.write',)
1476 _perms = ('repository.admin', 'repository.write',)
1477 validate_repo_permissions(apiuser, repoid, repo, _perms)
1477 validate_repo_permissions(apiuser, repoid, repo, _perms)
1478
1478
1479 # make sure normal user does not pass someone else userid,
1479 # make sure normal user does not pass someone else userid,
1480 # he is not allowed to do that
1480 # he is not allowed to do that
1481 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1481 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1482 raise JSONRPCError('userid is not the same as your user')
1482 raise JSONRPCError('userid is not the same as your user')
1483
1483
1484 if isinstance(userid, Optional):
1484 if isinstance(userid, Optional):
1485 userid = apiuser.user_id
1485 userid = apiuser.user_id
1486
1486
1487 user = get_user_or_error(userid)
1487 user = get_user_or_error(userid)
1488
1488
1489 if isinstance(locked, Optional):
1489 if isinstance(locked, Optional):
1490 lockobj = repo.locked
1490 lockobj = repo.locked
1491
1491
1492 if lockobj[0] is None:
1492 if lockobj[0] is None:
1493 _d = {
1493 _d = {
1494 'repo': repo.repo_name,
1494 'repo': repo.repo_name,
1495 'locked': False,
1495 'locked': False,
1496 'locked_since': None,
1496 'locked_since': None,
1497 'locked_by': None,
1497 'locked_by': None,
1498 'lock_reason': None,
1498 'lock_reason': None,
1499 'lock_state_changed': False,
1499 'lock_state_changed': False,
1500 'msg': 'Repo `%s` not locked.' % repo.repo_name
1500 'msg': 'Repo `%s` not locked.' % repo.repo_name
1501 }
1501 }
1502 return _d
1502 return _d
1503 else:
1503 else:
1504 _user_id, _time, _reason = lockobj
1504 _user_id, _time, _reason = lockobj
1505 lock_user = get_user_or_error(userid)
1505 lock_user = get_user_or_error(userid)
1506 _d = {
1506 _d = {
1507 'repo': repo.repo_name,
1507 'repo': repo.repo_name,
1508 'locked': True,
1508 'locked': True,
1509 'locked_since': _time,
1509 'locked_since': _time,
1510 'locked_by': lock_user.username,
1510 'locked_by': lock_user.username,
1511 'lock_reason': _reason,
1511 'lock_reason': _reason,
1512 'lock_state_changed': False,
1512 'lock_state_changed': False,
1513 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1513 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1514 % (repo.repo_name, lock_user.username,
1514 % (repo.repo_name, lock_user.username,
1515 json.dumps(time_to_datetime(_time))))
1515 json.dumps(time_to_datetime(_time))))
1516 }
1516 }
1517 return _d
1517 return _d
1518
1518
1519 # force locked state through a flag
1519 # force locked state through a flag
1520 else:
1520 else:
1521 locked = str2bool(locked)
1521 locked = str2bool(locked)
1522 lock_reason = Repository.LOCK_API
1522 lock_reason = Repository.LOCK_API
1523 try:
1523 try:
1524 if locked:
1524 if locked:
1525 lock_time = time.time()
1525 lock_time = time.time()
1526 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1526 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1527 else:
1527 else:
1528 lock_time = None
1528 lock_time = None
1529 Repository.unlock(repo)
1529 Repository.unlock(repo)
1530 _d = {
1530 _d = {
1531 'repo': repo.repo_name,
1531 'repo': repo.repo_name,
1532 'locked': locked,
1532 'locked': locked,
1533 'locked_since': lock_time,
1533 'locked_since': lock_time,
1534 'locked_by': user.username,
1534 'locked_by': user.username,
1535 'lock_reason': lock_reason,
1535 'lock_reason': lock_reason,
1536 'lock_state_changed': True,
1536 'lock_state_changed': True,
1537 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1537 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1538 % (user.username, repo.repo_name, locked))
1538 % (user.username, repo.repo_name, locked))
1539 }
1539 }
1540 return _d
1540 return _d
1541 except Exception:
1541 except Exception:
1542 log.exception(
1542 log.exception(
1543 "Exception occurred while trying to lock repository")
1543 "Exception occurred while trying to lock repository")
1544 raise JSONRPCError(
1544 raise JSONRPCError(
1545 'Error occurred locking repository `%s`' % repo.repo_name
1545 'Error occurred locking repository `%s`' % repo.repo_name
1546 )
1546 )
1547
1547
1548
1548
1549 @jsonrpc_method()
1549 @jsonrpc_method()
1550 def comment_commit(
1550 def comment_commit(
1551 request, apiuser, repoid, commit_id, message, status=Optional(None),
1551 request, apiuser, repoid, commit_id, message, status=Optional(None),
1552 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1552 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1553 resolves_comment_id=Optional(None),
1553 resolves_comment_id=Optional(None),
1554 userid=Optional(OAttr('apiuser'))):
1554 userid=Optional(OAttr('apiuser'))):
1555 """
1555 """
1556 Set a commit comment, and optionally change the status of the commit.
1556 Set a commit comment, and optionally change the status of the commit.
1557
1557
1558 :param apiuser: This is filled automatically from the |authtoken|.
1558 :param apiuser: This is filled automatically from the |authtoken|.
1559 :type apiuser: AuthUser
1559 :type apiuser: AuthUser
1560 :param repoid: Set the repository name or repository ID.
1560 :param repoid: Set the repository name or repository ID.
1561 :type repoid: str or int
1561 :type repoid: str or int
1562 :param commit_id: Specify the commit_id for which to set a comment.
1562 :param commit_id: Specify the commit_id for which to set a comment.
1563 :type commit_id: str
1563 :type commit_id: str
1564 :param message: The comment text.
1564 :param message: The comment text.
1565 :type message: str
1565 :type message: str
1566 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1566 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1567 'approved', 'rejected', 'under_review'
1567 'approved', 'rejected', 'under_review'
1568 :type status: str
1568 :type status: str
1569 :param comment_type: Comment type, one of: 'note', 'todo'
1569 :param comment_type: Comment type, one of: 'note', 'todo'
1570 :type comment_type: Optional(str), default: 'note'
1570 :type comment_type: Optional(str), default: 'note'
1571 :param userid: Set the user name of the comment creator.
1571 :param userid: Set the user name of the comment creator.
1572 :type userid: Optional(str or int)
1572 :type userid: Optional(str or int)
1573
1573
1574 Example error output:
1574 Example error output:
1575
1575
1576 .. code-block:: bash
1576 .. code-block:: bash
1577
1577
1578 {
1578 {
1579 "id" : <id_given_in_input>,
1579 "id" : <id_given_in_input>,
1580 "result" : {
1580 "result" : {
1581 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1581 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1582 "status_change": null or <status>,
1582 "status_change": null or <status>,
1583 "success": true
1583 "success": true
1584 },
1584 },
1585 "error" : null
1585 "error" : null
1586 }
1586 }
1587
1587
1588 """
1588 """
1589 repo = get_repo_or_error(repoid)
1589 repo = get_repo_or_error(repoid)
1590 if not has_superadmin_permission(apiuser):
1590 if not has_superadmin_permission(apiuser):
1591 _perms = ('repository.read', 'repository.write', 'repository.admin')
1591 _perms = ('repository.read', 'repository.write', 'repository.admin')
1592 validate_repo_permissions(apiuser, repoid, repo, _perms)
1592 validate_repo_permissions(apiuser, repoid, repo, _perms)
1593
1593
1594 try:
1594 try:
1595 commit_id = repo.scm_instance().get_commit(commit_id=commit_id).raw_id
1595 commit_id = repo.scm_instance().get_commit(commit_id=commit_id).raw_id
1596 except Exception as e:
1596 except Exception as e:
1597 log.exception('Failed to fetch commit')
1597 log.exception('Failed to fetch commit')
1598 raise JSONRPCError(safe_str(e))
1598 raise JSONRPCError(safe_str(e))
1599
1599
1600 if isinstance(userid, Optional):
1600 if isinstance(userid, Optional):
1601 userid = apiuser.user_id
1601 userid = apiuser.user_id
1602
1602
1603 user = get_user_or_error(userid)
1603 user = get_user_or_error(userid)
1604 status = Optional.extract(status)
1604 status = Optional.extract(status)
1605 comment_type = Optional.extract(comment_type)
1605 comment_type = Optional.extract(comment_type)
1606 resolves_comment_id = Optional.extract(resolves_comment_id)
1606 resolves_comment_id = Optional.extract(resolves_comment_id)
1607
1607
1608 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1608 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1609 if status and status not in allowed_statuses:
1609 if status and status not in allowed_statuses:
1610 raise JSONRPCError('Bad status, must be on '
1610 raise JSONRPCError('Bad status, must be on '
1611 'of %s got %s' % (allowed_statuses, status,))
1611 'of %s got %s' % (allowed_statuses, status,))
1612
1612
1613 if resolves_comment_id:
1613 if resolves_comment_id:
1614 comment = ChangesetComment.get(resolves_comment_id)
1614 comment = ChangesetComment.get(resolves_comment_id)
1615 if not comment:
1615 if not comment:
1616 raise JSONRPCError(
1616 raise JSONRPCError(
1617 'Invalid resolves_comment_id `%s` for this commit.'
1617 'Invalid resolves_comment_id `%s` for this commit.'
1618 % resolves_comment_id)
1618 % resolves_comment_id)
1619 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1619 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1620 raise JSONRPCError(
1620 raise JSONRPCError(
1621 'Comment `%s` is wrong type for setting status to resolved.'
1621 'Comment `%s` is wrong type for setting status to resolved.'
1622 % resolves_comment_id)
1622 % resolves_comment_id)
1623
1623
1624 try:
1624 try:
1625 rc_config = SettingsModel().get_all_settings()
1625 rc_config = SettingsModel().get_all_settings()
1626 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1626 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1627 status_change_label = ChangesetStatus.get_status_lbl(status)
1627 status_change_label = ChangesetStatus.get_status_lbl(status)
1628 comment = CommentsModel().create(
1628 comment = CommentsModel().create(
1629 message, repo, user, commit_id=commit_id,
1629 message, repo, user, commit_id=commit_id,
1630 status_change=status_change_label,
1630 status_change=status_change_label,
1631 status_change_type=status,
1631 status_change_type=status,
1632 renderer=renderer,
1632 renderer=renderer,
1633 comment_type=comment_type,
1633 comment_type=comment_type,
1634 resolves_comment_id=resolves_comment_id,
1634 resolves_comment_id=resolves_comment_id,
1635 auth_user=apiuser
1635 auth_user=apiuser
1636 )
1636 )
1637 if status:
1637 if status:
1638 # also do a status change
1638 # also do a status change
1639 try:
1639 try:
1640 ChangesetStatusModel().set_status(
1640 ChangesetStatusModel().set_status(
1641 repo, status, user, comment, revision=commit_id,
1641 repo, status, user, comment, revision=commit_id,
1642 dont_allow_on_closed_pull_request=True
1642 dont_allow_on_closed_pull_request=True
1643 )
1643 )
1644 except StatusChangeOnClosedPullRequestError:
1644 except StatusChangeOnClosedPullRequestError:
1645 log.exception(
1645 log.exception(
1646 "Exception occurred while trying to change repo commit status")
1646 "Exception occurred while trying to change repo commit status")
1647 msg = ('Changing status on a changeset associated with '
1647 msg = ('Changing status on a changeset associated with '
1648 'a closed pull request is not allowed')
1648 'a closed pull request is not allowed')
1649 raise JSONRPCError(msg)
1649 raise JSONRPCError(msg)
1650
1650
1651 Session().commit()
1651 Session().commit()
1652 return {
1652 return {
1653 'msg': (
1653 'msg': (
1654 'Commented on commit `%s` for repository `%s`' % (
1654 'Commented on commit `%s` for repository `%s`' % (
1655 comment.revision, repo.repo_name)),
1655 comment.revision, repo.repo_name)),
1656 'status_change': status,
1656 'status_change': status,
1657 'success': True,
1657 'success': True,
1658 }
1658 }
1659 except JSONRPCError:
1659 except JSONRPCError:
1660 # catch any inside errors, and re-raise them to prevent from
1660 # catch any inside errors, and re-raise them to prevent from
1661 # below global catch to silence them
1661 # below global catch to silence them
1662 raise
1662 raise
1663 except Exception:
1663 except Exception:
1664 log.exception("Exception occurred while trying to comment on commit")
1664 log.exception("Exception occurred while trying to comment on commit")
1665 raise JSONRPCError(
1665 raise JSONRPCError(
1666 'failed to set comment on repository `%s`' % (repo.repo_name,)
1666 'failed to set comment on repository `%s`' % (repo.repo_name,)
1667 )
1667 )
1668
1668
1669
1669
1670 @jsonrpc_method()
1670 @jsonrpc_method()
1671 def get_repo_comments(request, apiuser, repoid,
1671 def get_repo_comments(request, apiuser, repoid,
1672 commit_id=Optional(None), comment_type=Optional(None),
1672 commit_id=Optional(None), comment_type=Optional(None),
1673 userid=Optional(None)):
1673 userid=Optional(None)):
1674 """
1674 """
1675 Get all comments for a repository
1675 Get all comments for a repository
1676
1676
1677 :param apiuser: This is filled automatically from the |authtoken|.
1677 :param apiuser: This is filled automatically from the |authtoken|.
1678 :type apiuser: AuthUser
1678 :type apiuser: AuthUser
1679 :param repoid: Set the repository name or repository ID.
1679 :param repoid: Set the repository name or repository ID.
1680 :type repoid: str or int
1680 :type repoid: str or int
1681 :param commit_id: Optionally filter the comments by the commit_id
1681 :param commit_id: Optionally filter the comments by the commit_id
1682 :type commit_id: Optional(str), default: None
1682 :type commit_id: Optional(str), default: None
1683 :param comment_type: Optionally filter the comments by the comment_type
1683 :param comment_type: Optionally filter the comments by the comment_type
1684 one of: 'note', 'todo'
1684 one of: 'note', 'todo'
1685 :type comment_type: Optional(str), default: None
1685 :type comment_type: Optional(str), default: None
1686 :param userid: Optionally filter the comments by the author of comment
1686 :param userid: Optionally filter the comments by the author of comment
1687 :type userid: Optional(str or int), Default: None
1687 :type userid: Optional(str or int), Default: None
1688
1688
1689 Example error output:
1689 Example error output:
1690
1690
1691 .. code-block:: bash
1691 .. code-block:: bash
1692
1692
1693 {
1693 {
1694 "id" : <id_given_in_input>,
1694 "id" : <id_given_in_input>,
1695 "result" : [
1695 "result" : [
1696 {
1696 {
1697 "comment_author": <USER_DETAILS>,
1697 "comment_author": <USER_DETAILS>,
1698 "comment_created_on": "2017-02-01T14:38:16.309",
1698 "comment_created_on": "2017-02-01T14:38:16.309",
1699 "comment_f_path": "file.txt",
1699 "comment_f_path": "file.txt",
1700 "comment_id": 282,
1700 "comment_id": 282,
1701 "comment_lineno": "n1",
1701 "comment_lineno": "n1",
1702 "comment_resolved_by": null,
1702 "comment_resolved_by": null,
1703 "comment_status": [],
1703 "comment_status": [],
1704 "comment_text": "This file needs a header",
1704 "comment_text": "This file needs a header",
1705 "comment_type": "todo"
1705 "comment_type": "todo"
1706 }
1706 }
1707 ],
1707 ],
1708 "error" : null
1708 "error" : null
1709 }
1709 }
1710
1710
1711 """
1711 """
1712 repo = get_repo_or_error(repoid)
1712 repo = get_repo_or_error(repoid)
1713 if not has_superadmin_permission(apiuser):
1713 if not has_superadmin_permission(apiuser):
1714 _perms = ('repository.read', 'repository.write', 'repository.admin')
1714 _perms = ('repository.read', 'repository.write', 'repository.admin')
1715 validate_repo_permissions(apiuser, repoid, repo, _perms)
1715 validate_repo_permissions(apiuser, repoid, repo, _perms)
1716
1716
1717 commit_id = Optional.extract(commit_id)
1717 commit_id = Optional.extract(commit_id)
1718
1718
1719 userid = Optional.extract(userid)
1719 userid = Optional.extract(userid)
1720 if userid:
1720 if userid:
1721 user = get_user_or_error(userid)
1721 user = get_user_or_error(userid)
1722 else:
1722 else:
1723 user = None
1723 user = None
1724
1724
1725 comment_type = Optional.extract(comment_type)
1725 comment_type = Optional.extract(comment_type)
1726 if comment_type and comment_type not in ChangesetComment.COMMENT_TYPES:
1726 if comment_type and comment_type not in ChangesetComment.COMMENT_TYPES:
1727 raise JSONRPCError(
1727 raise JSONRPCError(
1728 'comment_type must be one of `{}` got {}'.format(
1728 'comment_type must be one of `{}` got {}'.format(
1729 ChangesetComment.COMMENT_TYPES, comment_type)
1729 ChangesetComment.COMMENT_TYPES, comment_type)
1730 )
1730 )
1731
1731
1732 comments = CommentsModel().get_repository_comments(
1732 comments = CommentsModel().get_repository_comments(
1733 repo=repo, comment_type=comment_type, user=user, commit_id=commit_id)
1733 repo=repo, comment_type=comment_type, user=user, commit_id=commit_id)
1734 return comments
1734 return comments
1735
1735
1736
1736
1737 @jsonrpc_method()
1737 @jsonrpc_method()
1738 def grant_user_permission(request, apiuser, repoid, userid, perm):
1738 def grant_user_permission(request, apiuser, repoid, userid, perm):
1739 """
1739 """
1740 Grant permissions for the specified user on the given repository,
1740 Grant permissions for the specified user on the given repository,
1741 or update existing permissions if found.
1741 or update existing permissions if found.
1742
1742
1743 This command can only be run using an |authtoken| with admin
1743 This command can only be run using an |authtoken| with admin
1744 permissions on the |repo|.
1744 permissions on the |repo|.
1745
1745
1746 :param apiuser: This is filled automatically from the |authtoken|.
1746 :param apiuser: This is filled automatically from the |authtoken|.
1747 :type apiuser: AuthUser
1747 :type apiuser: AuthUser
1748 :param repoid: Set the repository name or repository ID.
1748 :param repoid: Set the repository name or repository ID.
1749 :type repoid: str or int
1749 :type repoid: str or int
1750 :param userid: Set the user name.
1750 :param userid: Set the user name.
1751 :type userid: str
1751 :type userid: str
1752 :param perm: Set the user permissions, using the following format
1752 :param perm: Set the user permissions, using the following format
1753 ``(repository.(none|read|write|admin))``
1753 ``(repository.(none|read|write|admin))``
1754 :type perm: str
1754 :type perm: str
1755
1755
1756 Example output:
1756 Example output:
1757
1757
1758 .. code-block:: bash
1758 .. code-block:: bash
1759
1759
1760 id : <id_given_in_input>
1760 id : <id_given_in_input>
1761 result: {
1761 result: {
1762 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1762 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1763 "success": true
1763 "success": true
1764 }
1764 }
1765 error: null
1765 error: null
1766 """
1766 """
1767
1767
1768 repo = get_repo_or_error(repoid)
1768 repo = get_repo_or_error(repoid)
1769 user = get_user_or_error(userid)
1769 user = get_user_or_error(userid)
1770 perm = get_perm_or_error(perm)
1770 perm = get_perm_or_error(perm)
1771 if not has_superadmin_permission(apiuser):
1771 if not has_superadmin_permission(apiuser):
1772 _perms = ('repository.admin',)
1772 _perms = ('repository.admin',)
1773 validate_repo_permissions(apiuser, repoid, repo, _perms)
1773 validate_repo_permissions(apiuser, repoid, repo, _perms)
1774
1774
1775 perm_additions = [[user.user_id, perm.permission_name, "user"]]
1775 perm_additions = [[user.user_id, perm.permission_name, "user"]]
1776 try:
1776 try:
1777 changes = RepoModel().update_permissions(
1777 changes = RepoModel().update_permissions(
1778 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1778 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1779
1779
1780 action_data = {
1780 action_data = {
1781 'added': changes['added'],
1781 'added': changes['added'],
1782 'updated': changes['updated'],
1782 'updated': changes['updated'],
1783 'deleted': changes['deleted'],
1783 'deleted': changes['deleted'],
1784 }
1784 }
1785 audit_logger.store_api(
1785 audit_logger.store_api(
1786 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1786 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1787 Session().commit()
1787 Session().commit()
1788 PermissionModel().flush_user_permission_caches(changes)
1788 PermissionModel().flush_user_permission_caches(changes)
1789
1789
1790 return {
1790 return {
1791 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1791 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1792 perm.permission_name, user.username, repo.repo_name
1792 perm.permission_name, user.username, repo.repo_name
1793 ),
1793 ),
1794 'success': True
1794 'success': True
1795 }
1795 }
1796 except Exception:
1796 except Exception:
1797 log.exception("Exception occurred while trying edit permissions for repo")
1797 log.exception("Exception occurred while trying edit permissions for repo")
1798 raise JSONRPCError(
1798 raise JSONRPCError(
1799 'failed to edit permission for user: `%s` in repo: `%s`' % (
1799 'failed to edit permission for user: `%s` in repo: `%s`' % (
1800 userid, repoid
1800 userid, repoid
1801 )
1801 )
1802 )
1802 )
1803
1803
1804
1804
1805 @jsonrpc_method()
1805 @jsonrpc_method()
1806 def revoke_user_permission(request, apiuser, repoid, userid):
1806 def revoke_user_permission(request, apiuser, repoid, userid):
1807 """
1807 """
1808 Revoke permission for a user on the specified repository.
1808 Revoke permission for a user on the specified repository.
1809
1809
1810 This command can only be run using an |authtoken| with admin
1810 This command can only be run using an |authtoken| with admin
1811 permissions on the |repo|.
1811 permissions on the |repo|.
1812
1812
1813 :param apiuser: This is filled automatically from the |authtoken|.
1813 :param apiuser: This is filled automatically from the |authtoken|.
1814 :type apiuser: AuthUser
1814 :type apiuser: AuthUser
1815 :param repoid: Set the repository name or repository ID.
1815 :param repoid: Set the repository name or repository ID.
1816 :type repoid: str or int
1816 :type repoid: str or int
1817 :param userid: Set the user name of revoked user.
1817 :param userid: Set the user name of revoked user.
1818 :type userid: str or int
1818 :type userid: str or int
1819
1819
1820 Example error output:
1820 Example error output:
1821
1821
1822 .. code-block:: bash
1822 .. code-block:: bash
1823
1823
1824 id : <id_given_in_input>
1824 id : <id_given_in_input>
1825 result: {
1825 result: {
1826 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1826 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1827 "success": true
1827 "success": true
1828 }
1828 }
1829 error: null
1829 error: null
1830 """
1830 """
1831
1831
1832 repo = get_repo_or_error(repoid)
1832 repo = get_repo_or_error(repoid)
1833 user = get_user_or_error(userid)
1833 user = get_user_or_error(userid)
1834 if not has_superadmin_permission(apiuser):
1834 if not has_superadmin_permission(apiuser):
1835 _perms = ('repository.admin',)
1835 _perms = ('repository.admin',)
1836 validate_repo_permissions(apiuser, repoid, repo, _perms)
1836 validate_repo_permissions(apiuser, repoid, repo, _perms)
1837
1837
1838 perm_deletions = [[user.user_id, None, "user"]]
1838 perm_deletions = [[user.user_id, None, "user"]]
1839 try:
1839 try:
1840 changes = RepoModel().update_permissions(
1840 changes = RepoModel().update_permissions(
1841 repo=repo, perm_deletions=perm_deletions, cur_user=user)
1841 repo=repo, perm_deletions=perm_deletions, cur_user=user)
1842
1842
1843 action_data = {
1843 action_data = {
1844 'added': changes['added'],
1844 'added': changes['added'],
1845 'updated': changes['updated'],
1845 'updated': changes['updated'],
1846 'deleted': changes['deleted'],
1846 'deleted': changes['deleted'],
1847 }
1847 }
1848 audit_logger.store_api(
1848 audit_logger.store_api(
1849 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1849 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1850 Session().commit()
1850 Session().commit()
1851 PermissionModel().flush_user_permission_caches(changes)
1851 PermissionModel().flush_user_permission_caches(changes)
1852
1852
1853 return {
1853 return {
1854 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1854 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1855 user.username, repo.repo_name
1855 user.username, repo.repo_name
1856 ),
1856 ),
1857 'success': True
1857 'success': True
1858 }
1858 }
1859 except Exception:
1859 except Exception:
1860 log.exception("Exception occurred while trying revoke permissions to repo")
1860 log.exception("Exception occurred while trying revoke permissions to repo")
1861 raise JSONRPCError(
1861 raise JSONRPCError(
1862 'failed to edit permission for user: `%s` in repo: `%s`' % (
1862 'failed to edit permission for user: `%s` in repo: `%s`' % (
1863 userid, repoid
1863 userid, repoid
1864 )
1864 )
1865 )
1865 )
1866
1866
1867
1867
1868 @jsonrpc_method()
1868 @jsonrpc_method()
1869 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1869 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1870 """
1870 """
1871 Grant permission for a user group on the specified repository,
1871 Grant permission for a user group on the specified repository,
1872 or update existing permissions.
1872 or update existing permissions.
1873
1873
1874 This command can only be run using an |authtoken| with admin
1874 This command can only be run using an |authtoken| with admin
1875 permissions on the |repo|.
1875 permissions on the |repo|.
1876
1876
1877 :param apiuser: This is filled automatically from the |authtoken|.
1877 :param apiuser: This is filled automatically from the |authtoken|.
1878 :type apiuser: AuthUser
1878 :type apiuser: AuthUser
1879 :param repoid: Set the repository name or repository ID.
1879 :param repoid: Set the repository name or repository ID.
1880 :type repoid: str or int
1880 :type repoid: str or int
1881 :param usergroupid: Specify the ID of the user group.
1881 :param usergroupid: Specify the ID of the user group.
1882 :type usergroupid: str or int
1882 :type usergroupid: str or int
1883 :param perm: Set the user group permissions using the following
1883 :param perm: Set the user group permissions using the following
1884 format: (repository.(none|read|write|admin))
1884 format: (repository.(none|read|write|admin))
1885 :type perm: str
1885 :type perm: str
1886
1886
1887 Example output:
1887 Example output:
1888
1888
1889 .. code-block:: bash
1889 .. code-block:: bash
1890
1890
1891 id : <id_given_in_input>
1891 id : <id_given_in_input>
1892 result : {
1892 result : {
1893 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1893 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1894 "success": true
1894 "success": true
1895
1895
1896 }
1896 }
1897 error : null
1897 error : null
1898
1898
1899 Example error output:
1899 Example error output:
1900
1900
1901 .. code-block:: bash
1901 .. code-block:: bash
1902
1902
1903 id : <id_given_in_input>
1903 id : <id_given_in_input>
1904 result : null
1904 result : null
1905 error : {
1905 error : {
1906 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1906 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1907 }
1907 }
1908
1908
1909 """
1909 """
1910
1910
1911 repo = get_repo_or_error(repoid)
1911 repo = get_repo_or_error(repoid)
1912 perm = get_perm_or_error(perm)
1912 perm = get_perm_or_error(perm)
1913 if not has_superadmin_permission(apiuser):
1913 if not has_superadmin_permission(apiuser):
1914 _perms = ('repository.admin',)
1914 _perms = ('repository.admin',)
1915 validate_repo_permissions(apiuser, repoid, repo, _perms)
1915 validate_repo_permissions(apiuser, repoid, repo, _perms)
1916
1916
1917 user_group = get_user_group_or_error(usergroupid)
1917 user_group = get_user_group_or_error(usergroupid)
1918 if not has_superadmin_permission(apiuser):
1918 if not has_superadmin_permission(apiuser):
1919 # check if we have at least read permission for this user group !
1919 # check if we have at least read permission for this user group !
1920 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1920 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1921 if not HasUserGroupPermissionAnyApi(*_perms)(
1921 if not HasUserGroupPermissionAnyApi(*_perms)(
1922 user=apiuser, user_group_name=user_group.users_group_name):
1922 user=apiuser, user_group_name=user_group.users_group_name):
1923 raise JSONRPCError(
1923 raise JSONRPCError(
1924 'user group `%s` does not exist' % (usergroupid,))
1924 'user group `%s` does not exist' % (usergroupid,))
1925
1925
1926 perm_additions = [[user_group.users_group_id, perm.permission_name, "user_group"]]
1926 perm_additions = [[user_group.users_group_id, perm.permission_name, "user_group"]]
1927 try:
1927 try:
1928 changes = RepoModel().update_permissions(
1928 changes = RepoModel().update_permissions(
1929 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1929 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1930 action_data = {
1930 action_data = {
1931 'added': changes['added'],
1931 'added': changes['added'],
1932 'updated': changes['updated'],
1932 'updated': changes['updated'],
1933 'deleted': changes['deleted'],
1933 'deleted': changes['deleted'],
1934 }
1934 }
1935 audit_logger.store_api(
1935 audit_logger.store_api(
1936 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1936 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1937 Session().commit()
1937 Session().commit()
1938 PermissionModel().flush_user_permission_caches(changes)
1938 PermissionModel().flush_user_permission_caches(changes)
1939
1939
1940 return {
1940 return {
1941 'msg': 'Granted perm: `%s` for user group: `%s` in '
1941 'msg': 'Granted perm: `%s` for user group: `%s` in '
1942 'repo: `%s`' % (
1942 'repo: `%s`' % (
1943 perm.permission_name, user_group.users_group_name,
1943 perm.permission_name, user_group.users_group_name,
1944 repo.repo_name
1944 repo.repo_name
1945 ),
1945 ),
1946 'success': True
1946 'success': True
1947 }
1947 }
1948 except Exception:
1948 except Exception:
1949 log.exception(
1949 log.exception(
1950 "Exception occurred while trying change permission on repo")
1950 "Exception occurred while trying change permission on repo")
1951 raise JSONRPCError(
1951 raise JSONRPCError(
1952 'failed to edit permission for user group: `%s` in '
1952 'failed to edit permission for user group: `%s` in '
1953 'repo: `%s`' % (
1953 'repo: `%s`' % (
1954 usergroupid, repo.repo_name
1954 usergroupid, repo.repo_name
1955 )
1955 )
1956 )
1956 )
1957
1957
1958
1958
1959 @jsonrpc_method()
1959 @jsonrpc_method()
1960 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1960 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1961 """
1961 """
1962 Revoke the permissions of a user group on a given repository.
1962 Revoke the permissions of a user group on a given repository.
1963
1963
1964 This command can only be run using an |authtoken| with admin
1964 This command can only be run using an |authtoken| with admin
1965 permissions on the |repo|.
1965 permissions on the |repo|.
1966
1966
1967 :param apiuser: This is filled automatically from the |authtoken|.
1967 :param apiuser: This is filled automatically from the |authtoken|.
1968 :type apiuser: AuthUser
1968 :type apiuser: AuthUser
1969 :param repoid: Set the repository name or repository ID.
1969 :param repoid: Set the repository name or repository ID.
1970 :type repoid: str or int
1970 :type repoid: str or int
1971 :param usergroupid: Specify the user group ID.
1971 :param usergroupid: Specify the user group ID.
1972 :type usergroupid: str or int
1972 :type usergroupid: str or int
1973
1973
1974 Example output:
1974 Example output:
1975
1975
1976 .. code-block:: bash
1976 .. code-block:: bash
1977
1977
1978 id : <id_given_in_input>
1978 id : <id_given_in_input>
1979 result: {
1979 result: {
1980 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1980 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1981 "success": true
1981 "success": true
1982 }
1982 }
1983 error: null
1983 error: null
1984 """
1984 """
1985
1985
1986 repo = get_repo_or_error(repoid)
1986 repo = get_repo_or_error(repoid)
1987 if not has_superadmin_permission(apiuser):
1987 if not has_superadmin_permission(apiuser):
1988 _perms = ('repository.admin',)
1988 _perms = ('repository.admin',)
1989 validate_repo_permissions(apiuser, repoid, repo, _perms)
1989 validate_repo_permissions(apiuser, repoid, repo, _perms)
1990
1990
1991 user_group = get_user_group_or_error(usergroupid)
1991 user_group = get_user_group_or_error(usergroupid)
1992 if not has_superadmin_permission(apiuser):
1992 if not has_superadmin_permission(apiuser):
1993 # check if we have at least read permission for this user group !
1993 # check if we have at least read permission for this user group !
1994 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1994 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1995 if not HasUserGroupPermissionAnyApi(*_perms)(
1995 if not HasUserGroupPermissionAnyApi(*_perms)(
1996 user=apiuser, user_group_name=user_group.users_group_name):
1996 user=apiuser, user_group_name=user_group.users_group_name):
1997 raise JSONRPCError(
1997 raise JSONRPCError(
1998 'user group `%s` does not exist' % (usergroupid,))
1998 'user group `%s` does not exist' % (usergroupid,))
1999
1999
2000 perm_deletions = [[user_group.users_group_id, None, "user_group"]]
2000 perm_deletions = [[user_group.users_group_id, None, "user_group"]]
2001 try:
2001 try:
2002 changes = RepoModel().update_permissions(
2002 changes = RepoModel().update_permissions(
2003 repo=repo, perm_deletions=perm_deletions, cur_user=apiuser)
2003 repo=repo, perm_deletions=perm_deletions, cur_user=apiuser)
2004 action_data = {
2004 action_data = {
2005 'added': changes['added'],
2005 'added': changes['added'],
2006 'updated': changes['updated'],
2006 'updated': changes['updated'],
2007 'deleted': changes['deleted'],
2007 'deleted': changes['deleted'],
2008 }
2008 }
2009 audit_logger.store_api(
2009 audit_logger.store_api(
2010 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
2010 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
2011 Session().commit()
2011 Session().commit()
2012 PermissionModel().flush_user_permission_caches(changes)
2012 PermissionModel().flush_user_permission_caches(changes)
2013
2013
2014 return {
2014 return {
2015 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
2015 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
2016 user_group.users_group_name, repo.repo_name
2016 user_group.users_group_name, repo.repo_name
2017 ),
2017 ),
2018 'success': True
2018 'success': True
2019 }
2019 }
2020 except Exception:
2020 except Exception:
2021 log.exception("Exception occurred while trying revoke "
2021 log.exception("Exception occurred while trying revoke "
2022 "user group permission on repo")
2022 "user group permission on repo")
2023 raise JSONRPCError(
2023 raise JSONRPCError(
2024 'failed to edit permission for user group: `%s` in '
2024 'failed to edit permission for user group: `%s` in '
2025 'repo: `%s`' % (
2025 'repo: `%s`' % (
2026 user_group.users_group_name, repo.repo_name
2026 user_group.users_group_name, repo.repo_name
2027 )
2027 )
2028 )
2028 )
2029
2029
2030
2030
2031 @jsonrpc_method()
2031 @jsonrpc_method()
2032 def pull(request, apiuser, repoid, remote_uri=Optional(None)):
2032 def pull(request, apiuser, repoid, remote_uri=Optional(None)):
2033 """
2033 """
2034 Triggers a pull on the given repository from a remote location. You
2034 Triggers a pull on the given repository from a remote location. You
2035 can use this to keep remote repositories up-to-date.
2035 can use this to keep remote repositories up-to-date.
2036
2036
2037 This command can only be run using an |authtoken| with admin
2037 This command can only be run using an |authtoken| with admin
2038 rights to the specified repository. For more information,
2038 rights to the specified repository. For more information,
2039 see :ref:`config-token-ref`.
2039 see :ref:`config-token-ref`.
2040
2040
2041 This command takes the following options:
2041 This command takes the following options:
2042
2042
2043 :param apiuser: This is filled automatically from the |authtoken|.
2043 :param apiuser: This is filled automatically from the |authtoken|.
2044 :type apiuser: AuthUser
2044 :type apiuser: AuthUser
2045 :param repoid: The repository name or repository ID.
2045 :param repoid: The repository name or repository ID.
2046 :type repoid: str or int
2046 :type repoid: str or int
2047 :param remote_uri: Optional remote URI to pass in for pull
2047 :param remote_uri: Optional remote URI to pass in for pull
2048 :type remote_uri: str
2048 :type remote_uri: str
2049
2049
2050 Example output:
2050 Example output:
2051
2051
2052 .. code-block:: bash
2052 .. code-block:: bash
2053
2053
2054 id : <id_given_in_input>
2054 id : <id_given_in_input>
2055 result : {
2055 result : {
2056 "msg": "Pulled from url `<remote_url>` on repo `<repository name>`"
2056 "msg": "Pulled from url `<remote_url>` on repo `<repository name>`"
2057 "repository": "<repository name>"
2057 "repository": "<repository name>"
2058 }
2058 }
2059 error : null
2059 error : null
2060
2060
2061 Example error output:
2061 Example error output:
2062
2062
2063 .. code-block:: bash
2063 .. code-block:: bash
2064
2064
2065 id : <id_given_in_input>
2065 id : <id_given_in_input>
2066 result : null
2066 result : null
2067 error : {
2067 error : {
2068 "Unable to push changes from `<remote_url>`"
2068 "Unable to push changes from `<remote_url>`"
2069 }
2069 }
2070
2070
2071 """
2071 """
2072
2072
2073 repo = get_repo_or_error(repoid)
2073 repo = get_repo_or_error(repoid)
2074 remote_uri = Optional.extract(remote_uri)
2074 remote_uri = Optional.extract(remote_uri)
2075 remote_uri_display = remote_uri or repo.clone_uri_hidden
2075 remote_uri_display = remote_uri or repo.clone_uri_hidden
2076 if not has_superadmin_permission(apiuser):
2076 if not has_superadmin_permission(apiuser):
2077 _perms = ('repository.admin',)
2077 _perms = ('repository.admin',)
2078 validate_repo_permissions(apiuser, repoid, repo, _perms)
2078 validate_repo_permissions(apiuser, repoid, repo, _perms)
2079
2079
2080 try:
2080 try:
2081 ScmModel().pull_changes(
2081 ScmModel().pull_changes(
2082 repo.repo_name, apiuser.username, remote_uri=remote_uri)
2082 repo.repo_name, apiuser.username, remote_uri=remote_uri)
2083 return {
2083 return {
2084 'msg': 'Pulled from url `%s` on repo `%s`' % (
2084 'msg': 'Pulled from url `%s` on repo `%s`' % (
2085 remote_uri_display, repo.repo_name),
2085 remote_uri_display, repo.repo_name),
2086 'repository': repo.repo_name
2086 'repository': repo.repo_name
2087 }
2087 }
2088 except Exception:
2088 except Exception:
2089 log.exception("Exception occurred while trying to "
2089 log.exception("Exception occurred while trying to "
2090 "pull changes from remote location")
2090 "pull changes from remote location")
2091 raise JSONRPCError(
2091 raise JSONRPCError(
2092 'Unable to pull changes from `%s`' % remote_uri_display
2092 'Unable to pull changes from `%s`' % remote_uri_display
2093 )
2093 )
2094
2094
2095
2095
2096 @jsonrpc_method()
2096 @jsonrpc_method()
2097 def strip(request, apiuser, repoid, revision, branch):
2097 def strip(request, apiuser, repoid, revision, branch):
2098 """
2098 """
2099 Strips the given revision from the specified repository.
2099 Strips the given revision from the specified repository.
2100
2100
2101 * This will remove the revision and all of its decendants.
2101 * This will remove the revision and all of its decendants.
2102
2102
2103 This command can only be run using an |authtoken| with admin rights to
2103 This command can only be run using an |authtoken| with admin rights to
2104 the specified repository.
2104 the specified repository.
2105
2105
2106 This command takes the following options:
2106 This command takes the following options:
2107
2107
2108 :param apiuser: This is filled automatically from the |authtoken|.
2108 :param apiuser: This is filled automatically from the |authtoken|.
2109 :type apiuser: AuthUser
2109 :type apiuser: AuthUser
2110 :param repoid: The repository name or repository ID.
2110 :param repoid: The repository name or repository ID.
2111 :type repoid: str or int
2111 :type repoid: str or int
2112 :param revision: The revision you wish to strip.
2112 :param revision: The revision you wish to strip.
2113 :type revision: str
2113 :type revision: str
2114 :param branch: The branch from which to strip the revision.
2114 :param branch: The branch from which to strip the revision.
2115 :type branch: str
2115 :type branch: str
2116
2116
2117 Example output:
2117 Example output:
2118
2118
2119 .. code-block:: bash
2119 .. code-block:: bash
2120
2120
2121 id : <id_given_in_input>
2121 id : <id_given_in_input>
2122 result : {
2122 result : {
2123 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
2123 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
2124 "repository": "<repository name>"
2124 "repository": "<repository name>"
2125 }
2125 }
2126 error : null
2126 error : null
2127
2127
2128 Example error output:
2128 Example error output:
2129
2129
2130 .. code-block:: bash
2130 .. code-block:: bash
2131
2131
2132 id : <id_given_in_input>
2132 id : <id_given_in_input>
2133 result : null
2133 result : null
2134 error : {
2134 error : {
2135 "Unable to strip commit <commit_hash> from repo `<repository name>`"
2135 "Unable to strip commit <commit_hash> from repo `<repository name>`"
2136 }
2136 }
2137
2137
2138 """
2138 """
2139
2139
2140 repo = get_repo_or_error(repoid)
2140 repo = get_repo_or_error(repoid)
2141 if not has_superadmin_permission(apiuser):
2141 if not has_superadmin_permission(apiuser):
2142 _perms = ('repository.admin',)
2142 _perms = ('repository.admin',)
2143 validate_repo_permissions(apiuser, repoid, repo, _perms)
2143 validate_repo_permissions(apiuser, repoid, repo, _perms)
2144
2144
2145 try:
2145 try:
2146 ScmModel().strip(repo, revision, branch)
2146 ScmModel().strip(repo, revision, branch)
2147 audit_logger.store_api(
2147 audit_logger.store_api(
2148 'repo.commit.strip', action_data={'commit_id': revision},
2148 'repo.commit.strip', action_data={'commit_id': revision},
2149 repo=repo,
2149 repo=repo,
2150 user=apiuser, commit=True)
2150 user=apiuser, commit=True)
2151
2151
2152 return {
2152 return {
2153 'msg': 'Stripped commit %s from repo `%s`' % (
2153 'msg': 'Stripped commit %s from repo `%s`' % (
2154 revision, repo.repo_name),
2154 revision, repo.repo_name),
2155 'repository': repo.repo_name
2155 'repository': repo.repo_name
2156 }
2156 }
2157 except Exception:
2157 except Exception:
2158 log.exception("Exception while trying to strip")
2158 log.exception("Exception while trying to strip")
2159 raise JSONRPCError(
2159 raise JSONRPCError(
2160 'Unable to strip commit %s from repo `%s`' % (
2160 'Unable to strip commit %s from repo `%s`' % (
2161 revision, repo.repo_name)
2161 revision, repo.repo_name)
2162 )
2162 )
2163
2163
2164
2164
2165 @jsonrpc_method()
2165 @jsonrpc_method()
2166 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
2166 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
2167 """
2167 """
2168 Returns all settings for a repository. If key is given it only returns the
2168 Returns all settings for a repository. If key is given it only returns the
2169 setting identified by the key or null.
2169 setting identified by the key or null.
2170
2170
2171 :param apiuser: This is filled automatically from the |authtoken|.
2171 :param apiuser: This is filled automatically from the |authtoken|.
2172 :type apiuser: AuthUser
2172 :type apiuser: AuthUser
2173 :param repoid: The repository name or repository id.
2173 :param repoid: The repository name or repository id.
2174 :type repoid: str or int
2174 :type repoid: str or int
2175 :param key: Key of the setting to return.
2175 :param key: Key of the setting to return.
2176 :type: key: Optional(str)
2176 :type: key: Optional(str)
2177
2177
2178 Example output:
2178 Example output:
2179
2179
2180 .. code-block:: bash
2180 .. code-block:: bash
2181
2181
2182 {
2182 {
2183 "error": null,
2183 "error": null,
2184 "id": 237,
2184 "id": 237,
2185 "result": {
2185 "result": {
2186 "extensions_largefiles": true,
2186 "extensions_largefiles": true,
2187 "extensions_evolve": true,
2187 "extensions_evolve": true,
2188 "hooks_changegroup_push_logger": true,
2188 "hooks_changegroup_push_logger": true,
2189 "hooks_changegroup_repo_size": false,
2189 "hooks_changegroup_repo_size": false,
2190 "hooks_outgoing_pull_logger": true,
2190 "hooks_outgoing_pull_logger": true,
2191 "phases_publish": "True",
2191 "phases_publish": "True",
2192 "rhodecode_hg_use_rebase_for_merging": true,
2192 "rhodecode_hg_use_rebase_for_merging": true,
2193 "rhodecode_pr_merge_enabled": true,
2193 "rhodecode_pr_merge_enabled": true,
2194 "rhodecode_use_outdated_comments": true
2194 "rhodecode_use_outdated_comments": true
2195 }
2195 }
2196 }
2196 }
2197 """
2197 """
2198
2198
2199 # Restrict access to this api method to admins only.
2199 # Restrict access to this api method to admins only.
2200 if not has_superadmin_permission(apiuser):
2200 if not has_superadmin_permission(apiuser):
2201 raise JSONRPCForbidden()
2201 raise JSONRPCForbidden()
2202
2202
2203 try:
2203 try:
2204 repo = get_repo_or_error(repoid)
2204 repo = get_repo_or_error(repoid)
2205 settings_model = VcsSettingsModel(repo=repo)
2205 settings_model = VcsSettingsModel(repo=repo)
2206 settings = settings_model.get_global_settings()
2206 settings = settings_model.get_global_settings()
2207 settings.update(settings_model.get_repo_settings())
2207 settings.update(settings_model.get_repo_settings())
2208
2208
2209 # If only a single setting is requested fetch it from all settings.
2209 # If only a single setting is requested fetch it from all settings.
2210 key = Optional.extract(key)
2210 key = Optional.extract(key)
2211 if key is not None:
2211 if key is not None:
2212 settings = settings.get(key, None)
2212 settings = settings.get(key, None)
2213 except Exception:
2213 except Exception:
2214 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
2214 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
2215 log.exception(msg)
2215 log.exception(msg)
2216 raise JSONRPCError(msg)
2216 raise JSONRPCError(msg)
2217
2217
2218 return settings
2218 return settings
2219
2219
2220
2220
2221 @jsonrpc_method()
2221 @jsonrpc_method()
2222 def set_repo_settings(request, apiuser, repoid, settings):
2222 def set_repo_settings(request, apiuser, repoid, settings):
2223 """
2223 """
2224 Update repository settings. Returns true on success.
2224 Update repository settings. Returns true on success.
2225
2225
2226 :param apiuser: This is filled automatically from the |authtoken|.
2226 :param apiuser: This is filled automatically from the |authtoken|.
2227 :type apiuser: AuthUser
2227 :type apiuser: AuthUser
2228 :param repoid: The repository name or repository id.
2228 :param repoid: The repository name or repository id.
2229 :type repoid: str or int
2229 :type repoid: str or int
2230 :param settings: The new settings for the repository.
2230 :param settings: The new settings for the repository.
2231 :type: settings: dict
2231 :type: settings: dict
2232
2232
2233 Example output:
2233 Example output:
2234
2234
2235 .. code-block:: bash
2235 .. code-block:: bash
2236
2236
2237 {
2237 {
2238 "error": null,
2238 "error": null,
2239 "id": 237,
2239 "id": 237,
2240 "result": true
2240 "result": true
2241 }
2241 }
2242 """
2242 """
2243 # Restrict access to this api method to admins only.
2243 # Restrict access to this api method to admins only.
2244 if not has_superadmin_permission(apiuser):
2244 if not has_superadmin_permission(apiuser):
2245 raise JSONRPCForbidden()
2245 raise JSONRPCForbidden()
2246
2246
2247 if type(settings) is not dict:
2247 if type(settings) is not dict:
2248 raise JSONRPCError('Settings have to be a JSON Object.')
2248 raise JSONRPCError('Settings have to be a JSON Object.')
2249
2249
2250 try:
2250 try:
2251 settings_model = VcsSettingsModel(repo=repoid)
2251 settings_model = VcsSettingsModel(repo=repoid)
2252
2252
2253 # Merge global, repo and incoming settings.
2253 # Merge global, repo and incoming settings.
2254 new_settings = settings_model.get_global_settings()
2254 new_settings = settings_model.get_global_settings()
2255 new_settings.update(settings_model.get_repo_settings())
2255 new_settings.update(settings_model.get_repo_settings())
2256 new_settings.update(settings)
2256 new_settings.update(settings)
2257
2257
2258 # Update the settings.
2258 # Update the settings.
2259 inherit_global_settings = new_settings.get(
2259 inherit_global_settings = new_settings.get(
2260 'inherit_global_settings', False)
2260 'inherit_global_settings', False)
2261 settings_model.create_or_update_repo_settings(
2261 settings_model.create_or_update_repo_settings(
2262 new_settings, inherit_global_settings=inherit_global_settings)
2262 new_settings, inherit_global_settings=inherit_global_settings)
2263 Session().commit()
2263 Session().commit()
2264 except Exception:
2264 except Exception:
2265 msg = 'Failed to update settings for repository `{}`'.format(repoid)
2265 msg = 'Failed to update settings for repository `{}`'.format(repoid)
2266 log.exception(msg)
2266 log.exception(msg)
2267 raise JSONRPCError(msg)
2267 raise JSONRPCError(msg)
2268
2268
2269 # Indicate success.
2269 # Indicate success.
2270 return True
2270 return True
2271
2271
2272
2272
2273 @jsonrpc_method()
2273 @jsonrpc_method()
2274 def maintenance(request, apiuser, repoid):
2274 def maintenance(request, apiuser, repoid):
2275 """
2275 """
2276 Triggers a maintenance on the given repository.
2276 Triggers a maintenance on the given repository.
2277
2277
2278 This command can only be run using an |authtoken| with admin
2278 This command can only be run using an |authtoken| with admin
2279 rights to the specified repository. For more information,
2279 rights to the specified repository. For more information,
2280 see :ref:`config-token-ref`.
2280 see :ref:`config-token-ref`.
2281
2281
2282 This command takes the following options:
2282 This command takes the following options:
2283
2283
2284 :param apiuser: This is filled automatically from the |authtoken|.
2284 :param apiuser: This is filled automatically from the |authtoken|.
2285 :type apiuser: AuthUser
2285 :type apiuser: AuthUser
2286 :param repoid: The repository name or repository ID.
2286 :param repoid: The repository name or repository ID.
2287 :type repoid: str or int
2287 :type repoid: str or int
2288
2288
2289 Example output:
2289 Example output:
2290
2290
2291 .. code-block:: bash
2291 .. code-block:: bash
2292
2292
2293 id : <id_given_in_input>
2293 id : <id_given_in_input>
2294 result : {
2294 result : {
2295 "msg": "executed maintenance command",
2295 "msg": "executed maintenance command",
2296 "executed_actions": [
2296 "executed_actions": [
2297 <action_message>, <action_message2>...
2297 <action_message>, <action_message2>...
2298 ],
2298 ],
2299 "repository": "<repository name>"
2299 "repository": "<repository name>"
2300 }
2300 }
2301 error : null
2301 error : null
2302
2302
2303 Example error output:
2303 Example error output:
2304
2304
2305 .. code-block:: bash
2305 .. code-block:: bash
2306
2306
2307 id : <id_given_in_input>
2307 id : <id_given_in_input>
2308 result : null
2308 result : null
2309 error : {
2309 error : {
2310 "Unable to execute maintenance on `<reponame>`"
2310 "Unable to execute maintenance on `<reponame>`"
2311 }
2311 }
2312
2312
2313 """
2313 """
2314
2314
2315 repo = get_repo_or_error(repoid)
2315 repo = get_repo_or_error(repoid)
2316 if not has_superadmin_permission(apiuser):
2316 if not has_superadmin_permission(apiuser):
2317 _perms = ('repository.admin',)
2317 _perms = ('repository.admin',)
2318 validate_repo_permissions(apiuser, repoid, repo, _perms)
2318 validate_repo_permissions(apiuser, repoid, repo, _perms)
2319
2319
2320 try:
2320 try:
2321 maintenance = repo_maintenance.RepoMaintenance()
2321 maintenance = repo_maintenance.RepoMaintenance()
2322 executed_actions = maintenance.execute(repo)
2322 executed_actions = maintenance.execute(repo)
2323
2323
2324 return {
2324 return {
2325 'msg': 'executed maintenance command',
2325 'msg': 'executed maintenance command',
2326 'executed_actions': executed_actions,
2326 'executed_actions': executed_actions,
2327 'repository': repo.repo_name
2327 'repository': repo.repo_name
2328 }
2328 }
2329 except Exception:
2329 except Exception:
2330 log.exception("Exception occurred while trying to run maintenance")
2330 log.exception("Exception occurred while trying to run maintenance")
2331 raise JSONRPCError(
2331 raise JSONRPCError(
2332 'Unable to execute maintenance on `%s`' % repo.repo_name)
2332 'Unable to execute maintenance on `%s`' % repo.repo_name)
@@ -1,1551 +1,1552 b''
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 itertools
21 import itertools
22 import logging
22 import logging
23 import os
23 import os
24 import shutil
24 import shutil
25 import tempfile
25 import tempfile
26 import collections
26 import collections
27 import urllib
27 import urllib
28 import pathlib2
28 import pathlib2
29
29
30 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
30 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
31 from pyramid.view import view_config
31 from pyramid.view import view_config
32 from pyramid.renderers import render
32 from pyramid.renderers import render
33 from pyramid.response import Response
33 from pyramid.response import Response
34
34
35 import rhodecode
35 import rhodecode
36 from rhodecode.apps._base import RepoAppView
36 from rhodecode.apps._base import RepoAppView
37
37
38
38
39 from rhodecode.lib import diffs, helpers as h, rc_cache
39 from rhodecode.lib import diffs, helpers as h, rc_cache
40 from rhodecode.lib import audit_logger
40 from rhodecode.lib import audit_logger
41 from rhodecode.lib.view_utils import parse_path_ref
41 from rhodecode.lib.view_utils import parse_path_ref
42 from rhodecode.lib.exceptions import NonRelativePathError
42 from rhodecode.lib.exceptions import NonRelativePathError
43 from rhodecode.lib.codeblocks import (
43 from rhodecode.lib.codeblocks import (
44 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
44 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
45 from rhodecode.lib.utils2 import (
45 from rhodecode.lib.utils2 import (
46 convert_line_endings, detect_mode, safe_str, str2bool, safe_int, sha1, safe_unicode)
46 convert_line_endings, detect_mode, safe_str, str2bool, safe_int, sha1, safe_unicode)
47 from rhodecode.lib.auth import (
47 from rhodecode.lib.auth import (
48 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
48 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
49 from rhodecode.lib.vcs import path as vcspath
49 from rhodecode.lib.vcs import path as vcspath
50 from rhodecode.lib.vcs.backends.base import EmptyCommit
50 from rhodecode.lib.vcs.backends.base import EmptyCommit
51 from rhodecode.lib.vcs.conf import settings
51 from rhodecode.lib.vcs.conf import settings
52 from rhodecode.lib.vcs.nodes import FileNode
52 from rhodecode.lib.vcs.nodes import FileNode
53 from rhodecode.lib.vcs.exceptions import (
53 from rhodecode.lib.vcs.exceptions import (
54 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
54 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
55 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
55 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
56 NodeDoesNotExistError, CommitError, NodeError)
56 NodeDoesNotExistError, CommitError, NodeError)
57
57
58 from rhodecode.model.scm import ScmModel
58 from rhodecode.model.scm import ScmModel
59 from rhodecode.model.db import Repository
59 from rhodecode.model.db import Repository
60
60
61 log = logging.getLogger(__name__)
61 log = logging.getLogger(__name__)
62
62
63
63
64 class RepoFilesView(RepoAppView):
64 class RepoFilesView(RepoAppView):
65
65
66 @staticmethod
66 @staticmethod
67 def adjust_file_path_for_svn(f_path, repo):
67 def adjust_file_path_for_svn(f_path, repo):
68 """
68 """
69 Computes the relative path of `f_path`.
69 Computes the relative path of `f_path`.
70
70
71 This is mainly based on prefix matching of the recognized tags and
71 This is mainly based on prefix matching of the recognized tags and
72 branches in the underlying repository.
72 branches in the underlying repository.
73 """
73 """
74 tags_and_branches = itertools.chain(
74 tags_and_branches = itertools.chain(
75 repo.branches.iterkeys(),
75 repo.branches.iterkeys(),
76 repo.tags.iterkeys())
76 repo.tags.iterkeys())
77 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
77 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
78
78
79 for name in tags_and_branches:
79 for name in tags_and_branches:
80 if f_path.startswith('{}/'.format(name)):
80 if f_path.startswith('{}/'.format(name)):
81 f_path = vcspath.relpath(f_path, name)
81 f_path = vcspath.relpath(f_path, name)
82 break
82 break
83 return f_path
83 return f_path
84
84
85 def load_default_context(self):
85 def load_default_context(self):
86 c = self._get_local_tmpl_context(include_app_defaults=True)
86 c = self._get_local_tmpl_context(include_app_defaults=True)
87 c.rhodecode_repo = self.rhodecode_vcs_repo
87 c.rhodecode_repo = self.rhodecode_vcs_repo
88 c.enable_downloads = self.db_repo.enable_downloads
88 c.enable_downloads = self.db_repo.enable_downloads
89 return c
89 return c
90
90
91 def _ensure_not_locked(self, commit_id='tip'):
91 def _ensure_not_locked(self, commit_id='tip'):
92 _ = self.request.translate
92 _ = self.request.translate
93
93
94 repo = self.db_repo
94 repo = self.db_repo
95 if repo.enable_locking and repo.locked[0]:
95 if repo.enable_locking and repo.locked[0]:
96 h.flash(_('This repository has been locked by %s on %s')
96 h.flash(_('This repository has been locked by %s on %s')
97 % (h.person_by_id(repo.locked[0]),
97 % (h.person_by_id(repo.locked[0]),
98 h.format_date(h.time_to_datetime(repo.locked[1]))),
98 h.format_date(h.time_to_datetime(repo.locked[1]))),
99 'warning')
99 'warning')
100 files_url = h.route_path(
100 files_url = h.route_path(
101 'repo_files:default_path',
101 'repo_files:default_path',
102 repo_name=self.db_repo_name, commit_id=commit_id)
102 repo_name=self.db_repo_name, commit_id=commit_id)
103 raise HTTPFound(files_url)
103 raise HTTPFound(files_url)
104
104
105 def forbid_non_head(self, is_head, f_path, commit_id='tip', json_mode=False):
105 def forbid_non_head(self, is_head, f_path, commit_id='tip', json_mode=False):
106 _ = self.request.translate
106 _ = self.request.translate
107
107
108 if not is_head:
108 if not is_head:
109 message = _('Cannot modify file. '
109 message = _('Cannot modify file. '
110 'Given commit `{}` is not head of a branch.').format(commit_id)
110 'Given commit `{}` is not head of a branch.').format(commit_id)
111 h.flash(message, category='warning')
111 h.flash(message, category='warning')
112
112
113 if json_mode:
113 if json_mode:
114 return message
114 return message
115
115
116 files_url = h.route_path(
116 files_url = h.route_path(
117 'repo_files', repo_name=self.db_repo_name, commit_id=commit_id,
117 'repo_files', repo_name=self.db_repo_name, commit_id=commit_id,
118 f_path=f_path)
118 f_path=f_path)
119 raise HTTPFound(files_url)
119 raise HTTPFound(files_url)
120
120
121 def check_branch_permission(self, branch_name, commit_id='tip', json_mode=False):
121 def check_branch_permission(self, branch_name, commit_id='tip', json_mode=False):
122 _ = self.request.translate
122 _ = self.request.translate
123
123
124 rule, branch_perm = self._rhodecode_user.get_rule_and_branch_permission(
124 rule, branch_perm = self._rhodecode_user.get_rule_and_branch_permission(
125 self.db_repo_name, branch_name)
125 self.db_repo_name, branch_name)
126 if branch_perm and branch_perm not in ['branch.push', 'branch.push_force']:
126 if branch_perm and branch_perm not in ['branch.push', 'branch.push_force']:
127 message = _('Branch `{}` changes forbidden by rule {}.').format(
127 message = _('Branch `{}` changes forbidden by rule {}.').format(
128 branch_name, rule)
128 branch_name, rule)
129 h.flash(message, 'warning')
129 h.flash(message, 'warning')
130
130
131 if json_mode:
131 if json_mode:
132 return message
132 return message
133
133
134 files_url = h.route_path(
134 files_url = h.route_path(
135 'repo_files:default_path', repo_name=self.db_repo_name, commit_id=commit_id)
135 'repo_files:default_path', repo_name=self.db_repo_name, commit_id=commit_id)
136
136
137 raise HTTPFound(files_url)
137 raise HTTPFound(files_url)
138
138
139 def _get_commit_and_path(self):
139 def _get_commit_and_path(self):
140 default_commit_id = self.db_repo.landing_rev[1]
140 default_commit_id = self.db_repo.landing_rev[1]
141 default_f_path = '/'
141 default_f_path = '/'
142
142
143 commit_id = self.request.matchdict.get(
143 commit_id = self.request.matchdict.get(
144 'commit_id', default_commit_id)
144 'commit_id', default_commit_id)
145 f_path = self._get_f_path(self.request.matchdict, default_f_path)
145 f_path = self._get_f_path(self.request.matchdict, default_f_path)
146 return commit_id, f_path
146 return commit_id, f_path
147
147
148 def _get_default_encoding(self, c):
148 def _get_default_encoding(self, c):
149 enc_list = getattr(c, 'default_encodings', [])
149 enc_list = getattr(c, 'default_encodings', [])
150 return enc_list[0] if enc_list else 'UTF-8'
150 return enc_list[0] if enc_list else 'UTF-8'
151
151
152 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
152 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
153 """
153 """
154 This is a safe way to get commit. If an error occurs it redirects to
154 This is a safe way to get commit. If an error occurs it redirects to
155 tip with proper message
155 tip with proper message
156
156
157 :param commit_id: id of commit to fetch
157 :param commit_id: id of commit to fetch
158 :param redirect_after: toggle redirection
158 :param redirect_after: toggle redirection
159 """
159 """
160 _ = self.request.translate
160 _ = self.request.translate
161
161
162 try:
162 try:
163 return self.rhodecode_vcs_repo.get_commit(commit_id)
163 return self.rhodecode_vcs_repo.get_commit(commit_id)
164 except EmptyRepositoryError:
164 except EmptyRepositoryError:
165 if not redirect_after:
165 if not redirect_after:
166 return None
166 return None
167
167
168 _url = h.route_path(
168 _url = h.route_path(
169 'repo_files_add_file',
169 'repo_files_add_file',
170 repo_name=self.db_repo_name, commit_id=0, f_path='')
170 repo_name=self.db_repo_name, commit_id=0, f_path='')
171
171
172 if h.HasRepoPermissionAny(
172 if h.HasRepoPermissionAny(
173 'repository.write', 'repository.admin')(self.db_repo_name):
173 'repository.write', 'repository.admin')(self.db_repo_name):
174 add_new = h.link_to(
174 add_new = h.link_to(
175 _('Click here to add a new file.'), _url, class_="alert-link")
175 _('Click here to add a new file.'), _url, class_="alert-link")
176 else:
176 else:
177 add_new = ""
177 add_new = ""
178
178
179 h.flash(h.literal(
179 h.flash(h.literal(
180 _('There are no files yet. %s') % add_new), category='warning')
180 _('There are no files yet. %s') % add_new), category='warning')
181 raise HTTPFound(
181 raise HTTPFound(
182 h.route_path('repo_summary', repo_name=self.db_repo_name))
182 h.route_path('repo_summary', repo_name=self.db_repo_name))
183
183
184 except (CommitDoesNotExistError, LookupError):
184 except (CommitDoesNotExistError, LookupError):
185 msg = _('No such commit exists for this repository')
185 msg = _('No such commit exists for this repository')
186 h.flash(msg, category='error')
186 h.flash(msg, category='error')
187 raise HTTPNotFound()
187 raise HTTPNotFound()
188 except RepositoryError as e:
188 except RepositoryError as e:
189 h.flash(safe_str(h.escape(e)), category='error')
189 h.flash(safe_str(h.escape(e)), category='error')
190 raise HTTPNotFound()
190 raise HTTPNotFound()
191
191
192 def _get_filenode_or_redirect(self, commit_obj, path):
192 def _get_filenode_or_redirect(self, commit_obj, path):
193 """
193 """
194 Returns file_node, if error occurs or given path is directory,
194 Returns file_node, if error occurs or given path is directory,
195 it'll redirect to top level path
195 it'll redirect to top level path
196 """
196 """
197 _ = self.request.translate
197 _ = self.request.translate
198
198
199 try:
199 try:
200 file_node = commit_obj.get_node(path)
200 file_node = commit_obj.get_node(path)
201 if file_node.is_dir():
201 if file_node.is_dir():
202 raise RepositoryError('The given path is a directory')
202 raise RepositoryError('The given path is a directory')
203 except CommitDoesNotExistError:
203 except CommitDoesNotExistError:
204 log.exception('No such commit exists for this repository')
204 log.exception('No such commit exists for this repository')
205 h.flash(_('No such commit exists for this repository'), category='error')
205 h.flash(_('No such commit exists for this repository'), category='error')
206 raise HTTPNotFound()
206 raise HTTPNotFound()
207 except RepositoryError as e:
207 except RepositoryError as e:
208 log.warning('Repository error while fetching filenode `%s`. Err:%s', path, e)
208 log.warning('Repository error while fetching filenode `%s`. Err:%s', path, e)
209 h.flash(safe_str(h.escape(e)), category='error')
209 h.flash(safe_str(h.escape(e)), category='error')
210 raise HTTPNotFound()
210 raise HTTPNotFound()
211
211
212 return file_node
212 return file_node
213
213
214 def _is_valid_head(self, commit_id, repo):
214 def _is_valid_head(self, commit_id, repo):
215 branch_name = sha_commit_id = ''
215 branch_name = sha_commit_id = ''
216 is_head = False
216 is_head = False
217 log.debug('Checking if commit_id `%s` is a head for %s.', commit_id, repo)
217 log.debug('Checking if commit_id `%s` is a head for %s.', commit_id, repo)
218
218
219 for _branch_name, branch_commit_id in repo.branches.items():
219 for _branch_name, branch_commit_id in repo.branches.items():
220 # simple case we pass in branch name, it's a HEAD
220 # simple case we pass in branch name, it's a HEAD
221 if commit_id == _branch_name:
221 if commit_id == _branch_name:
222 is_head = True
222 is_head = True
223 branch_name = _branch_name
223 branch_name = _branch_name
224 sha_commit_id = branch_commit_id
224 sha_commit_id = branch_commit_id
225 break
225 break
226 # case when we pass in full sha commit_id, which is a head
226 # case when we pass in full sha commit_id, which is a head
227 elif commit_id == branch_commit_id:
227 elif commit_id == branch_commit_id:
228 is_head = True
228 is_head = True
229 branch_name = _branch_name
229 branch_name = _branch_name
230 sha_commit_id = branch_commit_id
230 sha_commit_id = branch_commit_id
231 break
231 break
232
232
233 if h.is_svn(repo) and not repo.is_empty():
233 if h.is_svn(repo) and not repo.is_empty():
234 # Note: Subversion only has one head.
234 # Note: Subversion only has one head.
235 if commit_id == repo.get_commit(commit_idx=-1).raw_id:
235 if commit_id == repo.get_commit(commit_idx=-1).raw_id:
236 is_head = True
236 is_head = True
237 return branch_name, sha_commit_id, is_head
237 return branch_name, sha_commit_id, is_head
238
238
239 # checked branches, means we only need to try to get the branch/commit_sha
239 # checked branches, means we only need to try to get the branch/commit_sha
240 if not repo.is_empty():
240 if not repo.is_empty():
241 commit = repo.get_commit(commit_id=commit_id)
241 commit = repo.get_commit(commit_id=commit_id)
242 if commit:
242 if commit:
243 branch_name = commit.branch
243 branch_name = commit.branch
244 sha_commit_id = commit.raw_id
244 sha_commit_id = commit.raw_id
245
245
246 return branch_name, sha_commit_id, is_head
246 return branch_name, sha_commit_id, is_head
247
247
248 def _get_tree_at_commit(self, c, commit_id, f_path, full_load=False):
248 def _get_tree_at_commit(self, c, commit_id, f_path, full_load=False):
249
249
250 repo_id = self.db_repo.repo_id
250 repo_id = self.db_repo.repo_id
251 force_recache = self.get_recache_flag()
251 force_recache = self.get_recache_flag()
252
252
253 cache_seconds = safe_int(
253 cache_seconds = safe_int(
254 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
254 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
255 cache_on = not force_recache and cache_seconds > 0
255 cache_on = not force_recache and cache_seconds > 0
256 log.debug(
256 log.debug(
257 'Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`'
257 'Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`'
258 'with caching: %s[TTL: %ss]' % (
258 'with caching: %s[TTL: %ss]' % (
259 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
259 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
260
260
261 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
261 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
262 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
262 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
263
263
264 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
264 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
265 condition=cache_on)
265 condition=cache_on)
266 def compute_file_tree(ver, repo_id, commit_id, f_path, full_load):
266 def compute_file_tree(ver, repo_id, commit_id, f_path, full_load):
267 log.debug('Generating cached file tree at ver:%s for repo_id: %s, %s, %s',
267 log.debug('Generating cached file tree at ver:%s for repo_id: %s, %s, %s',
268 ver, repo_id, commit_id, f_path)
268 ver, repo_id, commit_id, f_path)
269
269
270 c.full_load = full_load
270 c.full_load = full_load
271 return render(
271 return render(
272 'rhodecode:templates/files/files_browser_tree.mako',
272 'rhodecode:templates/files/files_browser_tree.mako',
273 self._get_template_context(c), self.request)
273 self._get_template_context(c), self.request)
274
274
275 return compute_file_tree('v1', self.db_repo.repo_id, commit_id, f_path, full_load)
275 return compute_file_tree(
276 rc_cache.FILE_TREE_CACHE_VER, self.db_repo.repo_id, commit_id, f_path, full_load)
276
277
277 def _get_archive_spec(self, fname):
278 def _get_archive_spec(self, fname):
278 log.debug('Detecting archive spec for: `%s`', fname)
279 log.debug('Detecting archive spec for: `%s`', fname)
279
280
280 fileformat = None
281 fileformat = None
281 ext = None
282 ext = None
282 content_type = None
283 content_type = None
283 for a_type, content_type, extension in settings.ARCHIVE_SPECS:
284 for a_type, content_type, extension in settings.ARCHIVE_SPECS:
284
285
285 if fname.endswith(extension):
286 if fname.endswith(extension):
286 fileformat = a_type
287 fileformat = a_type
287 log.debug('archive is of type: %s', fileformat)
288 log.debug('archive is of type: %s', fileformat)
288 ext = extension
289 ext = extension
289 break
290 break
290
291
291 if not fileformat:
292 if not fileformat:
292 raise ValueError()
293 raise ValueError()
293
294
294 # left over part of whole fname is the commit
295 # left over part of whole fname is the commit
295 commit_id = fname[:-len(ext)]
296 commit_id = fname[:-len(ext)]
296
297
297 return commit_id, ext, fileformat, content_type
298 return commit_id, ext, fileformat, content_type
298
299
299 def create_pure_path(self, *parts):
300 def create_pure_path(self, *parts):
300 # Split paths and sanitize them, removing any ../ etc
301 # Split paths and sanitize them, removing any ../ etc
301 sanitized_path = [
302 sanitized_path = [
302 x for x in pathlib2.PurePath(*parts).parts
303 x for x in pathlib2.PurePath(*parts).parts
303 if x not in ['.', '..']]
304 if x not in ['.', '..']]
304
305
305 pure_path = pathlib2.PurePath(*sanitized_path)
306 pure_path = pathlib2.PurePath(*sanitized_path)
306 return pure_path
307 return pure_path
307
308
308 def _is_lf_enabled(self, target_repo):
309 def _is_lf_enabled(self, target_repo):
309 lf_enabled = False
310 lf_enabled = False
310
311
311 lf_key_for_vcs_map = {
312 lf_key_for_vcs_map = {
312 'hg': 'extensions_largefiles',
313 'hg': 'extensions_largefiles',
313 'git': 'vcs_git_lfs_enabled'
314 'git': 'vcs_git_lfs_enabled'
314 }
315 }
315
316
316 lf_key_for_vcs = lf_key_for_vcs_map.get(target_repo.repo_type)
317 lf_key_for_vcs = lf_key_for_vcs_map.get(target_repo.repo_type)
317
318
318 if lf_key_for_vcs:
319 if lf_key_for_vcs:
319 lf_enabled = self._get_repo_setting(target_repo, lf_key_for_vcs)
320 lf_enabled = self._get_repo_setting(target_repo, lf_key_for_vcs)
320
321
321 return lf_enabled
322 return lf_enabled
322
323
323 @LoginRequired()
324 @LoginRequired()
324 @HasRepoPermissionAnyDecorator(
325 @HasRepoPermissionAnyDecorator(
325 'repository.read', 'repository.write', 'repository.admin')
326 'repository.read', 'repository.write', 'repository.admin')
326 @view_config(
327 @view_config(
327 route_name='repo_archivefile', request_method='GET',
328 route_name='repo_archivefile', request_method='GET',
328 renderer=None)
329 renderer=None)
329 def repo_archivefile(self):
330 def repo_archivefile(self):
330 # archive cache config
331 # archive cache config
331 from rhodecode import CONFIG
332 from rhodecode import CONFIG
332 _ = self.request.translate
333 _ = self.request.translate
333 self.load_default_context()
334 self.load_default_context()
334 default_at_path = '/'
335 default_at_path = '/'
335 fname = self.request.matchdict['fname']
336 fname = self.request.matchdict['fname']
336 subrepos = self.request.GET.get('subrepos') == 'true'
337 subrepos = self.request.GET.get('subrepos') == 'true'
337 at_path = self.request.GET.get('at_path') or default_at_path
338 at_path = self.request.GET.get('at_path') or default_at_path
338
339
339 if not self.db_repo.enable_downloads:
340 if not self.db_repo.enable_downloads:
340 return Response(_('Downloads disabled'))
341 return Response(_('Downloads disabled'))
341
342
342 try:
343 try:
343 commit_id, ext, fileformat, content_type = \
344 commit_id, ext, fileformat, content_type = \
344 self._get_archive_spec(fname)
345 self._get_archive_spec(fname)
345 except ValueError:
346 except ValueError:
346 return Response(_('Unknown archive type for: `{}`').format(
347 return Response(_('Unknown archive type for: `{}`').format(
347 h.escape(fname)))
348 h.escape(fname)))
348
349
349 try:
350 try:
350 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
351 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
351 except CommitDoesNotExistError:
352 except CommitDoesNotExistError:
352 return Response(_('Unknown commit_id {}').format(
353 return Response(_('Unknown commit_id {}').format(
353 h.escape(commit_id)))
354 h.escape(commit_id)))
354 except EmptyRepositoryError:
355 except EmptyRepositoryError:
355 return Response(_('Empty repository'))
356 return Response(_('Empty repository'))
356
357
357 try:
358 try:
358 at_path = commit.get_node(at_path).path or default_at_path
359 at_path = commit.get_node(at_path).path or default_at_path
359 except Exception:
360 except Exception:
360 return Response(_('No node at path {} for this repository').format(at_path))
361 return Response(_('No node at path {} for this repository').format(at_path))
361
362
362 path_sha = sha1(at_path)[:8]
363 path_sha = sha1(at_path)[:8]
363
364
364 # original backward compat name of archive
365 # original backward compat name of archive
365 clean_name = safe_str(self.db_repo_name.replace('/', '_'))
366 clean_name = safe_str(self.db_repo_name.replace('/', '_'))
366 short_sha = safe_str(commit.short_id)
367 short_sha = safe_str(commit.short_id)
367
368
368 if at_path == default_at_path:
369 if at_path == default_at_path:
369 archive_name = '{}-{}{}{}'.format(
370 archive_name = '{}-{}{}{}'.format(
370 clean_name,
371 clean_name,
371 '-sub' if subrepos else '',
372 '-sub' if subrepos else '',
372 short_sha,
373 short_sha,
373 ext)
374 ext)
374 # custom path and new name
375 # custom path and new name
375 else:
376 else:
376 archive_name = '{}-{}{}-{}{}'.format(
377 archive_name = '{}-{}{}-{}{}'.format(
377 clean_name,
378 clean_name,
378 '-sub' if subrepos else '',
379 '-sub' if subrepos else '',
379 short_sha,
380 short_sha,
380 path_sha,
381 path_sha,
381 ext)
382 ext)
382
383
383 use_cached_archive = False
384 use_cached_archive = False
384 archive_cache_enabled = CONFIG.get(
385 archive_cache_enabled = CONFIG.get(
385 'archive_cache_dir') and not self.request.GET.get('no_cache')
386 'archive_cache_dir') and not self.request.GET.get('no_cache')
386 cached_archive_path = None
387 cached_archive_path = None
387
388
388 if archive_cache_enabled:
389 if archive_cache_enabled:
389 # check if we it's ok to write
390 # check if we it's ok to write
390 if not os.path.isdir(CONFIG['archive_cache_dir']):
391 if not os.path.isdir(CONFIG['archive_cache_dir']):
391 os.makedirs(CONFIG['archive_cache_dir'])
392 os.makedirs(CONFIG['archive_cache_dir'])
392 cached_archive_path = os.path.join(
393 cached_archive_path = os.path.join(
393 CONFIG['archive_cache_dir'], archive_name)
394 CONFIG['archive_cache_dir'], archive_name)
394 if os.path.isfile(cached_archive_path):
395 if os.path.isfile(cached_archive_path):
395 log.debug('Found cached archive in %s', cached_archive_path)
396 log.debug('Found cached archive in %s', cached_archive_path)
396 fd, archive = None, cached_archive_path
397 fd, archive = None, cached_archive_path
397 use_cached_archive = True
398 use_cached_archive = True
398 else:
399 else:
399 log.debug('Archive %s is not yet cached', archive_name)
400 log.debug('Archive %s is not yet cached', archive_name)
400
401
401 if not use_cached_archive:
402 if not use_cached_archive:
402 # generate new archive
403 # generate new archive
403 fd, archive = tempfile.mkstemp()
404 fd, archive = tempfile.mkstemp()
404 log.debug('Creating new temp archive in %s', archive)
405 log.debug('Creating new temp archive in %s', archive)
405 try:
406 try:
406 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos,
407 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos,
407 archive_at_path=at_path)
408 archive_at_path=at_path)
408 except ImproperArchiveTypeError:
409 except ImproperArchiveTypeError:
409 return _('Unknown archive type')
410 return _('Unknown archive type')
410 if archive_cache_enabled:
411 if archive_cache_enabled:
411 # if we generated the archive and we have cache enabled
412 # if we generated the archive and we have cache enabled
412 # let's use this for future
413 # let's use this for future
413 log.debug('Storing new archive in %s', cached_archive_path)
414 log.debug('Storing new archive in %s', cached_archive_path)
414 shutil.move(archive, cached_archive_path)
415 shutil.move(archive, cached_archive_path)
415 archive = cached_archive_path
416 archive = cached_archive_path
416
417
417 # store download action
418 # store download action
418 audit_logger.store_web(
419 audit_logger.store_web(
419 'repo.archive.download', action_data={
420 'repo.archive.download', action_data={
420 'user_agent': self.request.user_agent,
421 'user_agent': self.request.user_agent,
421 'archive_name': archive_name,
422 'archive_name': archive_name,
422 'archive_spec': fname,
423 'archive_spec': fname,
423 'archive_cached': use_cached_archive},
424 'archive_cached': use_cached_archive},
424 user=self._rhodecode_user,
425 user=self._rhodecode_user,
425 repo=self.db_repo,
426 repo=self.db_repo,
426 commit=True
427 commit=True
427 )
428 )
428
429
429 def get_chunked_archive(archive_path):
430 def get_chunked_archive(archive_path):
430 with open(archive_path, 'rb') as stream:
431 with open(archive_path, 'rb') as stream:
431 while True:
432 while True:
432 data = stream.read(16 * 1024)
433 data = stream.read(16 * 1024)
433 if not data:
434 if not data:
434 if fd: # fd means we used temporary file
435 if fd: # fd means we used temporary file
435 os.close(fd)
436 os.close(fd)
436 if not archive_cache_enabled:
437 if not archive_cache_enabled:
437 log.debug('Destroying temp archive %s', archive_path)
438 log.debug('Destroying temp archive %s', archive_path)
438 os.remove(archive_path)
439 os.remove(archive_path)
439 break
440 break
440 yield data
441 yield data
441
442
442 response = Response(app_iter=get_chunked_archive(archive))
443 response = Response(app_iter=get_chunked_archive(archive))
443 response.content_disposition = str(
444 response.content_disposition = str(
444 'attachment; filename=%s' % archive_name)
445 'attachment; filename=%s' % archive_name)
445 response.content_type = str(content_type)
446 response.content_type = str(content_type)
446
447
447 return response
448 return response
448
449
449 def _get_file_node(self, commit_id, f_path):
450 def _get_file_node(self, commit_id, f_path):
450 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
451 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
451 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
452 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
452 try:
453 try:
453 node = commit.get_node(f_path)
454 node = commit.get_node(f_path)
454 if node.is_dir():
455 if node.is_dir():
455 raise NodeError('%s path is a %s not a file'
456 raise NodeError('%s path is a %s not a file'
456 % (node, type(node)))
457 % (node, type(node)))
457 except NodeDoesNotExistError:
458 except NodeDoesNotExistError:
458 commit = EmptyCommit(
459 commit = EmptyCommit(
459 commit_id=commit_id,
460 commit_id=commit_id,
460 idx=commit.idx,
461 idx=commit.idx,
461 repo=commit.repository,
462 repo=commit.repository,
462 alias=commit.repository.alias,
463 alias=commit.repository.alias,
463 message=commit.message,
464 message=commit.message,
464 author=commit.author,
465 author=commit.author,
465 date=commit.date)
466 date=commit.date)
466 node = FileNode(f_path, '', commit=commit)
467 node = FileNode(f_path, '', commit=commit)
467 else:
468 else:
468 commit = EmptyCommit(
469 commit = EmptyCommit(
469 repo=self.rhodecode_vcs_repo,
470 repo=self.rhodecode_vcs_repo,
470 alias=self.rhodecode_vcs_repo.alias)
471 alias=self.rhodecode_vcs_repo.alias)
471 node = FileNode(f_path, '', commit=commit)
472 node = FileNode(f_path, '', commit=commit)
472 return node
473 return node
473
474
474 @LoginRequired()
475 @LoginRequired()
475 @HasRepoPermissionAnyDecorator(
476 @HasRepoPermissionAnyDecorator(
476 'repository.read', 'repository.write', 'repository.admin')
477 'repository.read', 'repository.write', 'repository.admin')
477 @view_config(
478 @view_config(
478 route_name='repo_files_diff', request_method='GET',
479 route_name='repo_files_diff', request_method='GET',
479 renderer=None)
480 renderer=None)
480 def repo_files_diff(self):
481 def repo_files_diff(self):
481 c = self.load_default_context()
482 c = self.load_default_context()
482 f_path = self._get_f_path(self.request.matchdict)
483 f_path = self._get_f_path(self.request.matchdict)
483 diff1 = self.request.GET.get('diff1', '')
484 diff1 = self.request.GET.get('diff1', '')
484 diff2 = self.request.GET.get('diff2', '')
485 diff2 = self.request.GET.get('diff2', '')
485
486
486 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
487 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
487
488
488 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
489 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
489 line_context = self.request.GET.get('context', 3)
490 line_context = self.request.GET.get('context', 3)
490
491
491 if not any((diff1, diff2)):
492 if not any((diff1, diff2)):
492 h.flash(
493 h.flash(
493 'Need query parameter "diff1" or "diff2" to generate a diff.',
494 'Need query parameter "diff1" or "diff2" to generate a diff.',
494 category='error')
495 category='error')
495 raise HTTPBadRequest()
496 raise HTTPBadRequest()
496
497
497 c.action = self.request.GET.get('diff')
498 c.action = self.request.GET.get('diff')
498 if c.action not in ['download', 'raw']:
499 if c.action not in ['download', 'raw']:
499 compare_url = h.route_path(
500 compare_url = h.route_path(
500 'repo_compare',
501 'repo_compare',
501 repo_name=self.db_repo_name,
502 repo_name=self.db_repo_name,
502 source_ref_type='rev',
503 source_ref_type='rev',
503 source_ref=diff1,
504 source_ref=diff1,
504 target_repo=self.db_repo_name,
505 target_repo=self.db_repo_name,
505 target_ref_type='rev',
506 target_ref_type='rev',
506 target_ref=diff2,
507 target_ref=diff2,
507 _query=dict(f_path=f_path))
508 _query=dict(f_path=f_path))
508 # redirect to new view if we render diff
509 # redirect to new view if we render diff
509 raise HTTPFound(compare_url)
510 raise HTTPFound(compare_url)
510
511
511 try:
512 try:
512 node1 = self._get_file_node(diff1, path1)
513 node1 = self._get_file_node(diff1, path1)
513 node2 = self._get_file_node(diff2, f_path)
514 node2 = self._get_file_node(diff2, f_path)
514 except (RepositoryError, NodeError):
515 except (RepositoryError, NodeError):
515 log.exception("Exception while trying to get node from repository")
516 log.exception("Exception while trying to get node from repository")
516 raise HTTPFound(
517 raise HTTPFound(
517 h.route_path('repo_files', repo_name=self.db_repo_name,
518 h.route_path('repo_files', repo_name=self.db_repo_name,
518 commit_id='tip', f_path=f_path))
519 commit_id='tip', f_path=f_path))
519
520
520 if all(isinstance(node.commit, EmptyCommit)
521 if all(isinstance(node.commit, EmptyCommit)
521 for node in (node1, node2)):
522 for node in (node1, node2)):
522 raise HTTPNotFound()
523 raise HTTPNotFound()
523
524
524 c.commit_1 = node1.commit
525 c.commit_1 = node1.commit
525 c.commit_2 = node2.commit
526 c.commit_2 = node2.commit
526
527
527 if c.action == 'download':
528 if c.action == 'download':
528 _diff = diffs.get_gitdiff(node1, node2,
529 _diff = diffs.get_gitdiff(node1, node2,
529 ignore_whitespace=ignore_whitespace,
530 ignore_whitespace=ignore_whitespace,
530 context=line_context)
531 context=line_context)
531 diff = diffs.DiffProcessor(_diff, format='gitdiff')
532 diff = diffs.DiffProcessor(_diff, format='gitdiff')
532
533
533 response = Response(self.path_filter.get_raw_patch(diff))
534 response = Response(self.path_filter.get_raw_patch(diff))
534 response.content_type = 'text/plain'
535 response.content_type = 'text/plain'
535 response.content_disposition = (
536 response.content_disposition = (
536 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
537 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
537 )
538 )
538 charset = self._get_default_encoding(c)
539 charset = self._get_default_encoding(c)
539 if charset:
540 if charset:
540 response.charset = charset
541 response.charset = charset
541 return response
542 return response
542
543
543 elif c.action == 'raw':
544 elif c.action == 'raw':
544 _diff = diffs.get_gitdiff(node1, node2,
545 _diff = diffs.get_gitdiff(node1, node2,
545 ignore_whitespace=ignore_whitespace,
546 ignore_whitespace=ignore_whitespace,
546 context=line_context)
547 context=line_context)
547 diff = diffs.DiffProcessor(_diff, format='gitdiff')
548 diff = diffs.DiffProcessor(_diff, format='gitdiff')
548
549
549 response = Response(self.path_filter.get_raw_patch(diff))
550 response = Response(self.path_filter.get_raw_patch(diff))
550 response.content_type = 'text/plain'
551 response.content_type = 'text/plain'
551 charset = self._get_default_encoding(c)
552 charset = self._get_default_encoding(c)
552 if charset:
553 if charset:
553 response.charset = charset
554 response.charset = charset
554 return response
555 return response
555
556
556 # in case we ever end up here
557 # in case we ever end up here
557 raise HTTPNotFound()
558 raise HTTPNotFound()
558
559
559 @LoginRequired()
560 @LoginRequired()
560 @HasRepoPermissionAnyDecorator(
561 @HasRepoPermissionAnyDecorator(
561 'repository.read', 'repository.write', 'repository.admin')
562 'repository.read', 'repository.write', 'repository.admin')
562 @view_config(
563 @view_config(
563 route_name='repo_files_diff_2way_redirect', request_method='GET',
564 route_name='repo_files_diff_2way_redirect', request_method='GET',
564 renderer=None)
565 renderer=None)
565 def repo_files_diff_2way_redirect(self):
566 def repo_files_diff_2way_redirect(self):
566 """
567 """
567 Kept only to make OLD links work
568 Kept only to make OLD links work
568 """
569 """
569 f_path = self._get_f_path_unchecked(self.request.matchdict)
570 f_path = self._get_f_path_unchecked(self.request.matchdict)
570 diff1 = self.request.GET.get('diff1', '')
571 diff1 = self.request.GET.get('diff1', '')
571 diff2 = self.request.GET.get('diff2', '')
572 diff2 = self.request.GET.get('diff2', '')
572
573
573 if not any((diff1, diff2)):
574 if not any((diff1, diff2)):
574 h.flash(
575 h.flash(
575 'Need query parameter "diff1" or "diff2" to generate a diff.',
576 'Need query parameter "diff1" or "diff2" to generate a diff.',
576 category='error')
577 category='error')
577 raise HTTPBadRequest()
578 raise HTTPBadRequest()
578
579
579 compare_url = h.route_path(
580 compare_url = h.route_path(
580 'repo_compare',
581 'repo_compare',
581 repo_name=self.db_repo_name,
582 repo_name=self.db_repo_name,
582 source_ref_type='rev',
583 source_ref_type='rev',
583 source_ref=diff1,
584 source_ref=diff1,
584 target_ref_type='rev',
585 target_ref_type='rev',
585 target_ref=diff2,
586 target_ref=diff2,
586 _query=dict(f_path=f_path, diffmode='sideside',
587 _query=dict(f_path=f_path, diffmode='sideside',
587 target_repo=self.db_repo_name,))
588 target_repo=self.db_repo_name,))
588 raise HTTPFound(compare_url)
589 raise HTTPFound(compare_url)
589
590
590 @LoginRequired()
591 @LoginRequired()
591 @HasRepoPermissionAnyDecorator(
592 @HasRepoPermissionAnyDecorator(
592 'repository.read', 'repository.write', 'repository.admin')
593 'repository.read', 'repository.write', 'repository.admin')
593 @view_config(
594 @view_config(
594 route_name='repo_files', request_method='GET',
595 route_name='repo_files', request_method='GET',
595 renderer=None)
596 renderer=None)
596 @view_config(
597 @view_config(
597 route_name='repo_files:default_path', request_method='GET',
598 route_name='repo_files:default_path', request_method='GET',
598 renderer=None)
599 renderer=None)
599 @view_config(
600 @view_config(
600 route_name='repo_files:default_commit', request_method='GET',
601 route_name='repo_files:default_commit', request_method='GET',
601 renderer=None)
602 renderer=None)
602 @view_config(
603 @view_config(
603 route_name='repo_files:rendered', request_method='GET',
604 route_name='repo_files:rendered', request_method='GET',
604 renderer=None)
605 renderer=None)
605 @view_config(
606 @view_config(
606 route_name='repo_files:annotated', request_method='GET',
607 route_name='repo_files:annotated', request_method='GET',
607 renderer=None)
608 renderer=None)
608 def repo_files(self):
609 def repo_files(self):
609 c = self.load_default_context()
610 c = self.load_default_context()
610
611
611 view_name = getattr(self.request.matched_route, 'name', None)
612 view_name = getattr(self.request.matched_route, 'name', None)
612
613
613 c.annotate = view_name == 'repo_files:annotated'
614 c.annotate = view_name == 'repo_files:annotated'
614 # default is false, but .rst/.md files later are auto rendered, we can
615 # default is false, but .rst/.md files later are auto rendered, we can
615 # overwrite auto rendering by setting this GET flag
616 # overwrite auto rendering by setting this GET flag
616 c.renderer = view_name == 'repo_files:rendered' or \
617 c.renderer = view_name == 'repo_files:rendered' or \
617 not self.request.GET.get('no-render', False)
618 not self.request.GET.get('no-render', False)
618
619
619 # redirect to given commit_id from form if given
620 # redirect to given commit_id from form if given
620 get_commit_id = self.request.GET.get('at_rev', None)
621 get_commit_id = self.request.GET.get('at_rev', None)
621 if get_commit_id:
622 if get_commit_id:
622 self._get_commit_or_redirect(get_commit_id)
623 self._get_commit_or_redirect(get_commit_id)
623
624
624 commit_id, f_path = self._get_commit_and_path()
625 commit_id, f_path = self._get_commit_and_path()
625 c.commit = self._get_commit_or_redirect(commit_id)
626 c.commit = self._get_commit_or_redirect(commit_id)
626 c.branch = self.request.GET.get('branch', None)
627 c.branch = self.request.GET.get('branch', None)
627 c.f_path = f_path
628 c.f_path = f_path
628
629
629 # prev link
630 # prev link
630 try:
631 try:
631 prev_commit = c.commit.prev(c.branch)
632 prev_commit = c.commit.prev(c.branch)
632 c.prev_commit = prev_commit
633 c.prev_commit = prev_commit
633 c.url_prev = h.route_path(
634 c.url_prev = h.route_path(
634 'repo_files', repo_name=self.db_repo_name,
635 'repo_files', repo_name=self.db_repo_name,
635 commit_id=prev_commit.raw_id, f_path=f_path)
636 commit_id=prev_commit.raw_id, f_path=f_path)
636 if c.branch:
637 if c.branch:
637 c.url_prev += '?branch=%s' % c.branch
638 c.url_prev += '?branch=%s' % c.branch
638 except (CommitDoesNotExistError, VCSError):
639 except (CommitDoesNotExistError, VCSError):
639 c.url_prev = '#'
640 c.url_prev = '#'
640 c.prev_commit = EmptyCommit()
641 c.prev_commit = EmptyCommit()
641
642
642 # next link
643 # next link
643 try:
644 try:
644 next_commit = c.commit.next(c.branch)
645 next_commit = c.commit.next(c.branch)
645 c.next_commit = next_commit
646 c.next_commit = next_commit
646 c.url_next = h.route_path(
647 c.url_next = h.route_path(
647 'repo_files', repo_name=self.db_repo_name,
648 'repo_files', repo_name=self.db_repo_name,
648 commit_id=next_commit.raw_id, f_path=f_path)
649 commit_id=next_commit.raw_id, f_path=f_path)
649 if c.branch:
650 if c.branch:
650 c.url_next += '?branch=%s' % c.branch
651 c.url_next += '?branch=%s' % c.branch
651 except (CommitDoesNotExistError, VCSError):
652 except (CommitDoesNotExistError, VCSError):
652 c.url_next = '#'
653 c.url_next = '#'
653 c.next_commit = EmptyCommit()
654 c.next_commit = EmptyCommit()
654
655
655 # files or dirs
656 # files or dirs
656 try:
657 try:
657 c.file = c.commit.get_node(f_path)
658 c.file = c.commit.get_node(f_path)
658 c.file_author = True
659 c.file_author = True
659 c.file_tree = ''
660 c.file_tree = ''
660
661
661 # load file content
662 # load file content
662 if c.file.is_file():
663 if c.file.is_file():
663 c.lf_node = {}
664 c.lf_node = {}
664
665
665 has_lf_enabled = self._is_lf_enabled(self.db_repo)
666 has_lf_enabled = self._is_lf_enabled(self.db_repo)
666 if has_lf_enabled:
667 if has_lf_enabled:
667 c.lf_node = c.file.get_largefile_node()
668 c.lf_node = c.file.get_largefile_node()
668
669
669 c.file_source_page = 'true'
670 c.file_source_page = 'true'
670 c.file_last_commit = c.file.last_commit
671 c.file_last_commit = c.file.last_commit
671
672
672 c.file_size_too_big = c.file.size > c.visual.cut_off_limit_file
673 c.file_size_too_big = c.file.size > c.visual.cut_off_limit_file
673
674
674 if not (c.file_size_too_big or c.file.is_binary):
675 if not (c.file_size_too_big or c.file.is_binary):
675 if c.annotate: # annotation has precedence over renderer
676 if c.annotate: # annotation has precedence over renderer
676 c.annotated_lines = filenode_as_annotated_lines_tokens(
677 c.annotated_lines = filenode_as_annotated_lines_tokens(
677 c.file
678 c.file
678 )
679 )
679 else:
680 else:
680 c.renderer = (
681 c.renderer = (
681 c.renderer and h.renderer_from_filename(c.file.path)
682 c.renderer and h.renderer_from_filename(c.file.path)
682 )
683 )
683 if not c.renderer:
684 if not c.renderer:
684 c.lines = filenode_as_lines_tokens(c.file)
685 c.lines = filenode_as_lines_tokens(c.file)
685
686
686 _branch_name, _sha_commit_id, is_head = self._is_valid_head(
687 _branch_name, _sha_commit_id, is_head = self._is_valid_head(
687 commit_id, self.rhodecode_vcs_repo)
688 commit_id, self.rhodecode_vcs_repo)
688 c.on_branch_head = is_head
689 c.on_branch_head = is_head
689
690
690 branch = c.commit.branch if (
691 branch = c.commit.branch if (
691 c.commit.branch and '/' not in c.commit.branch) else None
692 c.commit.branch and '/' not in c.commit.branch) else None
692 c.branch_or_raw_id = branch or c.commit.raw_id
693 c.branch_or_raw_id = branch or c.commit.raw_id
693 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
694 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
694
695
695 author = c.file_last_commit.author
696 author = c.file_last_commit.author
696 c.authors = [[
697 c.authors = [[
697 h.email(author),
698 h.email(author),
698 h.person(author, 'username_or_name_or_email'),
699 h.person(author, 'username_or_name_or_email'),
699 1
700 1
700 ]]
701 ]]
701
702
702 else: # load tree content at path
703 else: # load tree content at path
703 c.file_source_page = 'false'
704 c.file_source_page = 'false'
704 c.authors = []
705 c.authors = []
705 # this loads a simple tree without metadata to speed things up
706 # this loads a simple tree without metadata to speed things up
706 # later via ajax we call repo_nodetree_full and fetch whole
707 # later via ajax we call repo_nodetree_full and fetch whole
707 c.file_tree = self._get_tree_at_commit(c, c.commit.raw_id, f_path)
708 c.file_tree = self._get_tree_at_commit(c, c.commit.raw_id, f_path)
708
709
709 c.readme_data, c.readme_file = \
710 c.readme_data, c.readme_file = \
710 self._get_readme_data(self.db_repo, c.visual.default_renderer,
711 self._get_readme_data(self.db_repo, c.visual.default_renderer,
711 c.commit.raw_id, f_path)
712 c.commit.raw_id, f_path)
712
713
713 except RepositoryError as e:
714 except RepositoryError as e:
714 h.flash(safe_str(h.escape(e)), category='error')
715 h.flash(safe_str(h.escape(e)), category='error')
715 raise HTTPNotFound()
716 raise HTTPNotFound()
716
717
717 if self.request.environ.get('HTTP_X_PJAX'):
718 if self.request.environ.get('HTTP_X_PJAX'):
718 html = render('rhodecode:templates/files/files_pjax.mako',
719 html = render('rhodecode:templates/files/files_pjax.mako',
719 self._get_template_context(c), self.request)
720 self._get_template_context(c), self.request)
720 else:
721 else:
721 html = render('rhodecode:templates/files/files.mako',
722 html = render('rhodecode:templates/files/files.mako',
722 self._get_template_context(c), self.request)
723 self._get_template_context(c), self.request)
723 return Response(html)
724 return Response(html)
724
725
725 @HasRepoPermissionAnyDecorator(
726 @HasRepoPermissionAnyDecorator(
726 'repository.read', 'repository.write', 'repository.admin')
727 'repository.read', 'repository.write', 'repository.admin')
727 @view_config(
728 @view_config(
728 route_name='repo_files:annotated_previous', request_method='GET',
729 route_name='repo_files:annotated_previous', request_method='GET',
729 renderer=None)
730 renderer=None)
730 def repo_files_annotated_previous(self):
731 def repo_files_annotated_previous(self):
731 self.load_default_context()
732 self.load_default_context()
732
733
733 commit_id, f_path = self._get_commit_and_path()
734 commit_id, f_path = self._get_commit_and_path()
734 commit = self._get_commit_or_redirect(commit_id)
735 commit = self._get_commit_or_redirect(commit_id)
735 prev_commit_id = commit.raw_id
736 prev_commit_id = commit.raw_id
736 line_anchor = self.request.GET.get('line_anchor')
737 line_anchor = self.request.GET.get('line_anchor')
737 is_file = False
738 is_file = False
738 try:
739 try:
739 _file = commit.get_node(f_path)
740 _file = commit.get_node(f_path)
740 is_file = _file.is_file()
741 is_file = _file.is_file()
741 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
742 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
742 pass
743 pass
743
744
744 if is_file:
745 if is_file:
745 history = commit.get_path_history(f_path)
746 history = commit.get_path_history(f_path)
746 prev_commit_id = history[1].raw_id \
747 prev_commit_id = history[1].raw_id \
747 if len(history) > 1 else prev_commit_id
748 if len(history) > 1 else prev_commit_id
748 prev_url = h.route_path(
749 prev_url = h.route_path(
749 'repo_files:annotated', repo_name=self.db_repo_name,
750 'repo_files:annotated', repo_name=self.db_repo_name,
750 commit_id=prev_commit_id, f_path=f_path,
751 commit_id=prev_commit_id, f_path=f_path,
751 _anchor='L{}'.format(line_anchor))
752 _anchor='L{}'.format(line_anchor))
752
753
753 raise HTTPFound(prev_url)
754 raise HTTPFound(prev_url)
754
755
755 @LoginRequired()
756 @LoginRequired()
756 @HasRepoPermissionAnyDecorator(
757 @HasRepoPermissionAnyDecorator(
757 'repository.read', 'repository.write', 'repository.admin')
758 'repository.read', 'repository.write', 'repository.admin')
758 @view_config(
759 @view_config(
759 route_name='repo_nodetree_full', request_method='GET',
760 route_name='repo_nodetree_full', request_method='GET',
760 renderer=None, xhr=True)
761 renderer=None, xhr=True)
761 @view_config(
762 @view_config(
762 route_name='repo_nodetree_full:default_path', request_method='GET',
763 route_name='repo_nodetree_full:default_path', request_method='GET',
763 renderer=None, xhr=True)
764 renderer=None, xhr=True)
764 def repo_nodetree_full(self):
765 def repo_nodetree_full(self):
765 """
766 """
766 Returns rendered html of file tree that contains commit date,
767 Returns rendered html of file tree that contains commit date,
767 author, commit_id for the specified combination of
768 author, commit_id for the specified combination of
768 repo, commit_id and file path
769 repo, commit_id and file path
769 """
770 """
770 c = self.load_default_context()
771 c = self.load_default_context()
771
772
772 commit_id, f_path = self._get_commit_and_path()
773 commit_id, f_path = self._get_commit_and_path()
773 commit = self._get_commit_or_redirect(commit_id)
774 commit = self._get_commit_or_redirect(commit_id)
774 try:
775 try:
775 dir_node = commit.get_node(f_path)
776 dir_node = commit.get_node(f_path)
776 except RepositoryError as e:
777 except RepositoryError as e:
777 return Response('error: {}'.format(h.escape(safe_str(e))))
778 return Response('error: {}'.format(h.escape(safe_str(e))))
778
779
779 if dir_node.is_file():
780 if dir_node.is_file():
780 return Response('')
781 return Response('')
781
782
782 c.file = dir_node
783 c.file = dir_node
783 c.commit = commit
784 c.commit = commit
784
785
785 html = self._get_tree_at_commit(
786 html = self._get_tree_at_commit(
786 c, commit.raw_id, dir_node.path, full_load=True)
787 c, commit.raw_id, dir_node.path, full_load=True)
787
788
788 return Response(html)
789 return Response(html)
789
790
790 def _get_attachement_headers(self, f_path):
791 def _get_attachement_headers(self, f_path):
791 f_name = safe_str(f_path.split(Repository.NAME_SEP)[-1])
792 f_name = safe_str(f_path.split(Repository.NAME_SEP)[-1])
792 safe_path = f_name.replace('"', '\\"')
793 safe_path = f_name.replace('"', '\\"')
793 encoded_path = urllib.quote(f_name)
794 encoded_path = urllib.quote(f_name)
794
795
795 return "attachment; " \
796 return "attachment; " \
796 "filename=\"{}\"; " \
797 "filename=\"{}\"; " \
797 "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
798 "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
798
799
799 @LoginRequired()
800 @LoginRequired()
800 @HasRepoPermissionAnyDecorator(
801 @HasRepoPermissionAnyDecorator(
801 'repository.read', 'repository.write', 'repository.admin')
802 'repository.read', 'repository.write', 'repository.admin')
802 @view_config(
803 @view_config(
803 route_name='repo_file_raw', request_method='GET',
804 route_name='repo_file_raw', request_method='GET',
804 renderer=None)
805 renderer=None)
805 def repo_file_raw(self):
806 def repo_file_raw(self):
806 """
807 """
807 Action for show as raw, some mimetypes are "rendered",
808 Action for show as raw, some mimetypes are "rendered",
808 those include images, icons.
809 those include images, icons.
809 """
810 """
810 c = self.load_default_context()
811 c = self.load_default_context()
811
812
812 commit_id, f_path = self._get_commit_and_path()
813 commit_id, f_path = self._get_commit_and_path()
813 commit = self._get_commit_or_redirect(commit_id)
814 commit = self._get_commit_or_redirect(commit_id)
814 file_node = self._get_filenode_or_redirect(commit, f_path)
815 file_node = self._get_filenode_or_redirect(commit, f_path)
815
816
816 raw_mimetype_mapping = {
817 raw_mimetype_mapping = {
817 # map original mimetype to a mimetype used for "show as raw"
818 # map original mimetype to a mimetype used for "show as raw"
818 # you can also provide a content-disposition to override the
819 # you can also provide a content-disposition to override the
819 # default "attachment" disposition.
820 # default "attachment" disposition.
820 # orig_type: (new_type, new_dispo)
821 # orig_type: (new_type, new_dispo)
821
822
822 # show images inline:
823 # show images inline:
823 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
824 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
824 # for example render an SVG with javascript inside or even render
825 # for example render an SVG with javascript inside or even render
825 # HTML.
826 # HTML.
826 'image/x-icon': ('image/x-icon', 'inline'),
827 'image/x-icon': ('image/x-icon', 'inline'),
827 'image/png': ('image/png', 'inline'),
828 'image/png': ('image/png', 'inline'),
828 'image/gif': ('image/gif', 'inline'),
829 'image/gif': ('image/gif', 'inline'),
829 'image/jpeg': ('image/jpeg', 'inline'),
830 'image/jpeg': ('image/jpeg', 'inline'),
830 'application/pdf': ('application/pdf', 'inline'),
831 'application/pdf': ('application/pdf', 'inline'),
831 }
832 }
832
833
833 mimetype = file_node.mimetype
834 mimetype = file_node.mimetype
834 try:
835 try:
835 mimetype, disposition = raw_mimetype_mapping[mimetype]
836 mimetype, disposition = raw_mimetype_mapping[mimetype]
836 except KeyError:
837 except KeyError:
837 # we don't know anything special about this, handle it safely
838 # we don't know anything special about this, handle it safely
838 if file_node.is_binary:
839 if file_node.is_binary:
839 # do same as download raw for binary files
840 # do same as download raw for binary files
840 mimetype, disposition = 'application/octet-stream', 'attachment'
841 mimetype, disposition = 'application/octet-stream', 'attachment'
841 else:
842 else:
842 # do not just use the original mimetype, but force text/plain,
843 # do not just use the original mimetype, but force text/plain,
843 # otherwise it would serve text/html and that might be unsafe.
844 # otherwise it would serve text/html and that might be unsafe.
844 # Note: underlying vcs library fakes text/plain mimetype if the
845 # Note: underlying vcs library fakes text/plain mimetype if the
845 # mimetype can not be determined and it thinks it is not
846 # mimetype can not be determined and it thinks it is not
846 # binary.This might lead to erroneous text display in some
847 # binary.This might lead to erroneous text display in some
847 # cases, but helps in other cases, like with text files
848 # cases, but helps in other cases, like with text files
848 # without extension.
849 # without extension.
849 mimetype, disposition = 'text/plain', 'inline'
850 mimetype, disposition = 'text/plain', 'inline'
850
851
851 if disposition == 'attachment':
852 if disposition == 'attachment':
852 disposition = self._get_attachement_headers(f_path)
853 disposition = self._get_attachement_headers(f_path)
853
854
854 stream_content = file_node.stream_bytes()
855 stream_content = file_node.stream_bytes()
855
856
856 response = Response(app_iter=stream_content)
857 response = Response(app_iter=stream_content)
857 response.content_disposition = disposition
858 response.content_disposition = disposition
858 response.content_type = mimetype
859 response.content_type = mimetype
859
860
860 charset = self._get_default_encoding(c)
861 charset = self._get_default_encoding(c)
861 if charset:
862 if charset:
862 response.charset = charset
863 response.charset = charset
863
864
864 return response
865 return response
865
866
866 @LoginRequired()
867 @LoginRequired()
867 @HasRepoPermissionAnyDecorator(
868 @HasRepoPermissionAnyDecorator(
868 'repository.read', 'repository.write', 'repository.admin')
869 'repository.read', 'repository.write', 'repository.admin')
869 @view_config(
870 @view_config(
870 route_name='repo_file_download', request_method='GET',
871 route_name='repo_file_download', request_method='GET',
871 renderer=None)
872 renderer=None)
872 @view_config(
873 @view_config(
873 route_name='repo_file_download:legacy', request_method='GET',
874 route_name='repo_file_download:legacy', request_method='GET',
874 renderer=None)
875 renderer=None)
875 def repo_file_download(self):
876 def repo_file_download(self):
876 c = self.load_default_context()
877 c = self.load_default_context()
877
878
878 commit_id, f_path = self._get_commit_and_path()
879 commit_id, f_path = self._get_commit_and_path()
879 commit = self._get_commit_or_redirect(commit_id)
880 commit = self._get_commit_or_redirect(commit_id)
880 file_node = self._get_filenode_or_redirect(commit, f_path)
881 file_node = self._get_filenode_or_redirect(commit, f_path)
881
882
882 if self.request.GET.get('lf'):
883 if self.request.GET.get('lf'):
883 # only if lf get flag is passed, we download this file
884 # only if lf get flag is passed, we download this file
884 # as LFS/Largefile
885 # as LFS/Largefile
885 lf_node = file_node.get_largefile_node()
886 lf_node = file_node.get_largefile_node()
886 if lf_node:
887 if lf_node:
887 # overwrite our pointer with the REAL large-file
888 # overwrite our pointer with the REAL large-file
888 file_node = lf_node
889 file_node = lf_node
889
890
890 disposition = self._get_attachement_headers(f_path)
891 disposition = self._get_attachement_headers(f_path)
891
892
892 stream_content = file_node.stream_bytes()
893 stream_content = file_node.stream_bytes()
893
894
894 response = Response(app_iter=stream_content)
895 response = Response(app_iter=stream_content)
895 response.content_disposition = disposition
896 response.content_disposition = disposition
896 response.content_type = file_node.mimetype
897 response.content_type = file_node.mimetype
897
898
898 charset = self._get_default_encoding(c)
899 charset = self._get_default_encoding(c)
899 if charset:
900 if charset:
900 response.charset = charset
901 response.charset = charset
901
902
902 return response
903 return response
903
904
904 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
905 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
905
906
906 cache_seconds = safe_int(
907 cache_seconds = safe_int(
907 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
908 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
908 cache_on = cache_seconds > 0
909 cache_on = cache_seconds > 0
909 log.debug(
910 log.debug(
910 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
911 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
911 'with caching: %s[TTL: %ss]' % (
912 'with caching: %s[TTL: %ss]' % (
912 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
913 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
913
914
914 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
915 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
915 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
916 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
916
917
917 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
918 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
918 condition=cache_on)
919 condition=cache_on)
919 def compute_file_search(repo_id, commit_id, f_path):
920 def compute_file_search(repo_id, commit_id, f_path):
920 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
921 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
921 repo_id, commit_id, f_path)
922 repo_id, commit_id, f_path)
922 try:
923 try:
923 _d, _f = ScmModel().get_quick_filter_nodes(repo_name, commit_id, f_path)
924 _d, _f = ScmModel().get_quick_filter_nodes(repo_name, commit_id, f_path)
924 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
925 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
925 log.exception(safe_str(e))
926 log.exception(safe_str(e))
926 h.flash(safe_str(h.escape(e)), category='error')
927 h.flash(safe_str(h.escape(e)), category='error')
927 raise HTTPFound(h.route_path(
928 raise HTTPFound(h.route_path(
928 'repo_files', repo_name=self.db_repo_name,
929 'repo_files', repo_name=self.db_repo_name,
929 commit_id='tip', f_path='/'))
930 commit_id='tip', f_path='/'))
930
931
931 return _d + _f
932 return _d + _f
932
933
933 result = compute_file_search(self.db_repo.repo_id, commit_id, f_path)
934 result = compute_file_search(self.db_repo.repo_id, commit_id, f_path)
934 return filter(lambda n: self.path_filter.path_access_allowed(n['name']), result)
935 return filter(lambda n: self.path_filter.path_access_allowed(n['name']), result)
935
936
936 @LoginRequired()
937 @LoginRequired()
937 @HasRepoPermissionAnyDecorator(
938 @HasRepoPermissionAnyDecorator(
938 'repository.read', 'repository.write', 'repository.admin')
939 'repository.read', 'repository.write', 'repository.admin')
939 @view_config(
940 @view_config(
940 route_name='repo_files_nodelist', request_method='GET',
941 route_name='repo_files_nodelist', request_method='GET',
941 renderer='json_ext', xhr=True)
942 renderer='json_ext', xhr=True)
942 def repo_nodelist(self):
943 def repo_nodelist(self):
943 self.load_default_context()
944 self.load_default_context()
944
945
945 commit_id, f_path = self._get_commit_and_path()
946 commit_id, f_path = self._get_commit_and_path()
946 commit = self._get_commit_or_redirect(commit_id)
947 commit = self._get_commit_or_redirect(commit_id)
947
948
948 metadata = self._get_nodelist_at_commit(
949 metadata = self._get_nodelist_at_commit(
949 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
950 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
950 return {'nodes': metadata}
951 return {'nodes': metadata}
951
952
952 def _create_references(self, branches_or_tags, symbolic_reference, f_path, ref_type):
953 def _create_references(self, branches_or_tags, symbolic_reference, f_path, ref_type):
953 items = []
954 items = []
954 for name, commit_id in branches_or_tags.items():
955 for name, commit_id in branches_or_tags.items():
955 sym_ref = symbolic_reference(commit_id, name, f_path, ref_type)
956 sym_ref = symbolic_reference(commit_id, name, f_path, ref_type)
956 items.append((sym_ref, name, ref_type))
957 items.append((sym_ref, name, ref_type))
957 return items
958 return items
958
959
959 def _symbolic_reference(self, commit_id, name, f_path, ref_type):
960 def _symbolic_reference(self, commit_id, name, f_path, ref_type):
960 return commit_id
961 return commit_id
961
962
962 def _symbolic_reference_svn(self, commit_id, name, f_path, ref_type):
963 def _symbolic_reference_svn(self, commit_id, name, f_path, ref_type):
963 new_f_path = vcspath.join(name, f_path)
964 new_f_path = vcspath.join(name, f_path)
964 return u'%s@%s' % (new_f_path, commit_id)
965 return u'%s@%s' % (new_f_path, commit_id)
965
966
966 def _get_node_history(self, commit_obj, f_path, commits=None):
967 def _get_node_history(self, commit_obj, f_path, commits=None):
967 """
968 """
968 get commit history for given node
969 get commit history for given node
969
970
970 :param commit_obj: commit to calculate history
971 :param commit_obj: commit to calculate history
971 :param f_path: path for node to calculate history for
972 :param f_path: path for node to calculate history for
972 :param commits: if passed don't calculate history and take
973 :param commits: if passed don't calculate history and take
973 commits defined in this list
974 commits defined in this list
974 """
975 """
975 _ = self.request.translate
976 _ = self.request.translate
976
977
977 # calculate history based on tip
978 # calculate history based on tip
978 tip = self.rhodecode_vcs_repo.get_commit()
979 tip = self.rhodecode_vcs_repo.get_commit()
979 if commits is None:
980 if commits is None:
980 pre_load = ["author", "branch"]
981 pre_load = ["author", "branch"]
981 try:
982 try:
982 commits = tip.get_path_history(f_path, pre_load=pre_load)
983 commits = tip.get_path_history(f_path, pre_load=pre_load)
983 except (NodeDoesNotExistError, CommitError):
984 except (NodeDoesNotExistError, CommitError):
984 # this node is not present at tip!
985 # this node is not present at tip!
985 commits = commit_obj.get_path_history(f_path, pre_load=pre_load)
986 commits = commit_obj.get_path_history(f_path, pre_load=pre_load)
986
987
987 history = []
988 history = []
988 commits_group = ([], _("Changesets"))
989 commits_group = ([], _("Changesets"))
989 for commit in commits:
990 for commit in commits:
990 branch = ' (%s)' % commit.branch if commit.branch else ''
991 branch = ' (%s)' % commit.branch if commit.branch else ''
991 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
992 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
992 commits_group[0].append((commit.raw_id, n_desc, 'sha'))
993 commits_group[0].append((commit.raw_id, n_desc, 'sha'))
993 history.append(commits_group)
994 history.append(commits_group)
994
995
995 symbolic_reference = self._symbolic_reference
996 symbolic_reference = self._symbolic_reference
996
997
997 if self.rhodecode_vcs_repo.alias == 'svn':
998 if self.rhodecode_vcs_repo.alias == 'svn':
998 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
999 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
999 f_path, self.rhodecode_vcs_repo)
1000 f_path, self.rhodecode_vcs_repo)
1000 if adjusted_f_path != f_path:
1001 if adjusted_f_path != f_path:
1001 log.debug(
1002 log.debug(
1002 'Recognized svn tag or branch in file "%s", using svn '
1003 'Recognized svn tag or branch in file "%s", using svn '
1003 'specific symbolic references', f_path)
1004 'specific symbolic references', f_path)
1004 f_path = adjusted_f_path
1005 f_path = adjusted_f_path
1005 symbolic_reference = self._symbolic_reference_svn
1006 symbolic_reference = self._symbolic_reference_svn
1006
1007
1007 branches = self._create_references(
1008 branches = self._create_references(
1008 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path, 'branch')
1009 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path, 'branch')
1009 branches_group = (branches, _("Branches"))
1010 branches_group = (branches, _("Branches"))
1010
1011
1011 tags = self._create_references(
1012 tags = self._create_references(
1012 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path, 'tag')
1013 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path, 'tag')
1013 tags_group = (tags, _("Tags"))
1014 tags_group = (tags, _("Tags"))
1014
1015
1015 history.append(branches_group)
1016 history.append(branches_group)
1016 history.append(tags_group)
1017 history.append(tags_group)
1017
1018
1018 return history, commits
1019 return history, commits
1019
1020
1020 @LoginRequired()
1021 @LoginRequired()
1021 @HasRepoPermissionAnyDecorator(
1022 @HasRepoPermissionAnyDecorator(
1022 'repository.read', 'repository.write', 'repository.admin')
1023 'repository.read', 'repository.write', 'repository.admin')
1023 @view_config(
1024 @view_config(
1024 route_name='repo_file_history', request_method='GET',
1025 route_name='repo_file_history', request_method='GET',
1025 renderer='json_ext')
1026 renderer='json_ext')
1026 def repo_file_history(self):
1027 def repo_file_history(self):
1027 self.load_default_context()
1028 self.load_default_context()
1028
1029
1029 commit_id, f_path = self._get_commit_and_path()
1030 commit_id, f_path = self._get_commit_and_path()
1030 commit = self._get_commit_or_redirect(commit_id)
1031 commit = self._get_commit_or_redirect(commit_id)
1031 file_node = self._get_filenode_or_redirect(commit, f_path)
1032 file_node = self._get_filenode_or_redirect(commit, f_path)
1032
1033
1033 if file_node.is_file():
1034 if file_node.is_file():
1034 file_history, _hist = self._get_node_history(commit, f_path)
1035 file_history, _hist = self._get_node_history(commit, f_path)
1035
1036
1036 res = []
1037 res = []
1037 for obj in file_history:
1038 for obj in file_history:
1038 res.append({
1039 res.append({
1039 'text': obj[1],
1040 'text': obj[1],
1040 'children': [{'id': o[0], 'text': o[1], 'type': o[2]} for o in obj[0]]
1041 'children': [{'id': o[0], 'text': o[1], 'type': o[2]} for o in obj[0]]
1041 })
1042 })
1042
1043
1043 data = {
1044 data = {
1044 'more': False,
1045 'more': False,
1045 'results': res
1046 'results': res
1046 }
1047 }
1047 return data
1048 return data
1048
1049
1049 log.warning('Cannot fetch history for directory')
1050 log.warning('Cannot fetch history for directory')
1050 raise HTTPBadRequest()
1051 raise HTTPBadRequest()
1051
1052
1052 @LoginRequired()
1053 @LoginRequired()
1053 @HasRepoPermissionAnyDecorator(
1054 @HasRepoPermissionAnyDecorator(
1054 'repository.read', 'repository.write', 'repository.admin')
1055 'repository.read', 'repository.write', 'repository.admin')
1055 @view_config(
1056 @view_config(
1056 route_name='repo_file_authors', request_method='GET',
1057 route_name='repo_file_authors', request_method='GET',
1057 renderer='rhodecode:templates/files/file_authors_box.mako')
1058 renderer='rhodecode:templates/files/file_authors_box.mako')
1058 def repo_file_authors(self):
1059 def repo_file_authors(self):
1059 c = self.load_default_context()
1060 c = self.load_default_context()
1060
1061
1061 commit_id, f_path = self._get_commit_and_path()
1062 commit_id, f_path = self._get_commit_and_path()
1062 commit = self._get_commit_or_redirect(commit_id)
1063 commit = self._get_commit_or_redirect(commit_id)
1063 file_node = self._get_filenode_or_redirect(commit, f_path)
1064 file_node = self._get_filenode_or_redirect(commit, f_path)
1064
1065
1065 if not file_node.is_file():
1066 if not file_node.is_file():
1066 raise HTTPBadRequest()
1067 raise HTTPBadRequest()
1067
1068
1068 c.file_last_commit = file_node.last_commit
1069 c.file_last_commit = file_node.last_commit
1069 if self.request.GET.get('annotate') == '1':
1070 if self.request.GET.get('annotate') == '1':
1070 # use _hist from annotation if annotation mode is on
1071 # use _hist from annotation if annotation mode is on
1071 commit_ids = set(x[1] for x in file_node.annotate)
1072 commit_ids = set(x[1] for x in file_node.annotate)
1072 _hist = (
1073 _hist = (
1073 self.rhodecode_vcs_repo.get_commit(commit_id)
1074 self.rhodecode_vcs_repo.get_commit(commit_id)
1074 for commit_id in commit_ids)
1075 for commit_id in commit_ids)
1075 else:
1076 else:
1076 _f_history, _hist = self._get_node_history(commit, f_path)
1077 _f_history, _hist = self._get_node_history(commit, f_path)
1077 c.file_author = False
1078 c.file_author = False
1078
1079
1079 unique = collections.OrderedDict()
1080 unique = collections.OrderedDict()
1080 for commit in _hist:
1081 for commit in _hist:
1081 author = commit.author
1082 author = commit.author
1082 if author not in unique:
1083 if author not in unique:
1083 unique[commit.author] = [
1084 unique[commit.author] = [
1084 h.email(author),
1085 h.email(author),
1085 h.person(author, 'username_or_name_or_email'),
1086 h.person(author, 'username_or_name_or_email'),
1086 1 # counter
1087 1 # counter
1087 ]
1088 ]
1088
1089
1089 else:
1090 else:
1090 # increase counter
1091 # increase counter
1091 unique[commit.author][2] += 1
1092 unique[commit.author][2] += 1
1092
1093
1093 c.authors = [val for val in unique.values()]
1094 c.authors = [val for val in unique.values()]
1094
1095
1095 return self._get_template_context(c)
1096 return self._get_template_context(c)
1096
1097
1097 @LoginRequired()
1098 @LoginRequired()
1098 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1099 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1099 @view_config(
1100 @view_config(
1100 route_name='repo_files_remove_file', request_method='GET',
1101 route_name='repo_files_remove_file', request_method='GET',
1101 renderer='rhodecode:templates/files/files_delete.mako')
1102 renderer='rhodecode:templates/files/files_delete.mako')
1102 def repo_files_remove_file(self):
1103 def repo_files_remove_file(self):
1103 _ = self.request.translate
1104 _ = self.request.translate
1104 c = self.load_default_context()
1105 c = self.load_default_context()
1105 commit_id, f_path = self._get_commit_and_path()
1106 commit_id, f_path = self._get_commit_and_path()
1106
1107
1107 self._ensure_not_locked()
1108 self._ensure_not_locked()
1108 _branch_name, _sha_commit_id, is_head = \
1109 _branch_name, _sha_commit_id, is_head = \
1109 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1110 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1110
1111
1111 self.forbid_non_head(is_head, f_path)
1112 self.forbid_non_head(is_head, f_path)
1112 self.check_branch_permission(_branch_name)
1113 self.check_branch_permission(_branch_name)
1113
1114
1114 c.commit = self._get_commit_or_redirect(commit_id)
1115 c.commit = self._get_commit_or_redirect(commit_id)
1115 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1116 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1116
1117
1117 c.default_message = _(
1118 c.default_message = _(
1118 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1119 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1119 c.f_path = f_path
1120 c.f_path = f_path
1120
1121
1121 return self._get_template_context(c)
1122 return self._get_template_context(c)
1122
1123
1123 @LoginRequired()
1124 @LoginRequired()
1124 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1125 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1125 @CSRFRequired()
1126 @CSRFRequired()
1126 @view_config(
1127 @view_config(
1127 route_name='repo_files_delete_file', request_method='POST',
1128 route_name='repo_files_delete_file', request_method='POST',
1128 renderer=None)
1129 renderer=None)
1129 def repo_files_delete_file(self):
1130 def repo_files_delete_file(self):
1130 _ = self.request.translate
1131 _ = self.request.translate
1131
1132
1132 c = self.load_default_context()
1133 c = self.load_default_context()
1133 commit_id, f_path = self._get_commit_and_path()
1134 commit_id, f_path = self._get_commit_and_path()
1134
1135
1135 self._ensure_not_locked()
1136 self._ensure_not_locked()
1136 _branch_name, _sha_commit_id, is_head = \
1137 _branch_name, _sha_commit_id, is_head = \
1137 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1138 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1138
1139
1139 self.forbid_non_head(is_head, f_path)
1140 self.forbid_non_head(is_head, f_path)
1140 self.check_branch_permission(_branch_name)
1141 self.check_branch_permission(_branch_name)
1141
1142
1142 c.commit = self._get_commit_or_redirect(commit_id)
1143 c.commit = self._get_commit_or_redirect(commit_id)
1143 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1144 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1144
1145
1145 c.default_message = _(
1146 c.default_message = _(
1146 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1147 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1147 c.f_path = f_path
1148 c.f_path = f_path
1148 node_path = f_path
1149 node_path = f_path
1149 author = self._rhodecode_db_user.full_contact
1150 author = self._rhodecode_db_user.full_contact
1150 message = self.request.POST.get('message') or c.default_message
1151 message = self.request.POST.get('message') or c.default_message
1151 try:
1152 try:
1152 nodes = {
1153 nodes = {
1153 node_path: {
1154 node_path: {
1154 'content': ''
1155 'content': ''
1155 }
1156 }
1156 }
1157 }
1157 ScmModel().delete_nodes(
1158 ScmModel().delete_nodes(
1158 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1159 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1159 message=message,
1160 message=message,
1160 nodes=nodes,
1161 nodes=nodes,
1161 parent_commit=c.commit,
1162 parent_commit=c.commit,
1162 author=author,
1163 author=author,
1163 )
1164 )
1164
1165
1165 h.flash(
1166 h.flash(
1166 _('Successfully deleted file `{}`').format(
1167 _('Successfully deleted file `{}`').format(
1167 h.escape(f_path)), category='success')
1168 h.escape(f_path)), category='success')
1168 except Exception:
1169 except Exception:
1169 log.exception('Error during commit operation')
1170 log.exception('Error during commit operation')
1170 h.flash(_('Error occurred during commit'), category='error')
1171 h.flash(_('Error occurred during commit'), category='error')
1171 raise HTTPFound(
1172 raise HTTPFound(
1172 h.route_path('repo_commit', repo_name=self.db_repo_name,
1173 h.route_path('repo_commit', repo_name=self.db_repo_name,
1173 commit_id='tip'))
1174 commit_id='tip'))
1174
1175
1175 @LoginRequired()
1176 @LoginRequired()
1176 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1177 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1177 @view_config(
1178 @view_config(
1178 route_name='repo_files_edit_file', request_method='GET',
1179 route_name='repo_files_edit_file', request_method='GET',
1179 renderer='rhodecode:templates/files/files_edit.mako')
1180 renderer='rhodecode:templates/files/files_edit.mako')
1180 def repo_files_edit_file(self):
1181 def repo_files_edit_file(self):
1181 _ = self.request.translate
1182 _ = self.request.translate
1182 c = self.load_default_context()
1183 c = self.load_default_context()
1183 commit_id, f_path = self._get_commit_and_path()
1184 commit_id, f_path = self._get_commit_and_path()
1184
1185
1185 self._ensure_not_locked()
1186 self._ensure_not_locked()
1186 _branch_name, _sha_commit_id, is_head = \
1187 _branch_name, _sha_commit_id, is_head = \
1187 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1188 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1188
1189
1189 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1190 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1190 self.check_branch_permission(_branch_name, commit_id=commit_id)
1191 self.check_branch_permission(_branch_name, commit_id=commit_id)
1191
1192
1192 c.commit = self._get_commit_or_redirect(commit_id)
1193 c.commit = self._get_commit_or_redirect(commit_id)
1193 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1194 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1194
1195
1195 if c.file.is_binary:
1196 if c.file.is_binary:
1196 files_url = h.route_path(
1197 files_url = h.route_path(
1197 'repo_files',
1198 'repo_files',
1198 repo_name=self.db_repo_name,
1199 repo_name=self.db_repo_name,
1199 commit_id=c.commit.raw_id, f_path=f_path)
1200 commit_id=c.commit.raw_id, f_path=f_path)
1200 raise HTTPFound(files_url)
1201 raise HTTPFound(files_url)
1201
1202
1202 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1203 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1203 c.f_path = f_path
1204 c.f_path = f_path
1204
1205
1205 return self._get_template_context(c)
1206 return self._get_template_context(c)
1206
1207
1207 @LoginRequired()
1208 @LoginRequired()
1208 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1209 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1209 @CSRFRequired()
1210 @CSRFRequired()
1210 @view_config(
1211 @view_config(
1211 route_name='repo_files_update_file', request_method='POST',
1212 route_name='repo_files_update_file', request_method='POST',
1212 renderer=None)
1213 renderer=None)
1213 def repo_files_update_file(self):
1214 def repo_files_update_file(self):
1214 _ = self.request.translate
1215 _ = self.request.translate
1215 c = self.load_default_context()
1216 c = self.load_default_context()
1216 commit_id, f_path = self._get_commit_and_path()
1217 commit_id, f_path = self._get_commit_and_path()
1217
1218
1218 self._ensure_not_locked()
1219 self._ensure_not_locked()
1219
1220
1220 c.commit = self._get_commit_or_redirect(commit_id)
1221 c.commit = self._get_commit_or_redirect(commit_id)
1221 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1222 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1222
1223
1223 if c.file.is_binary:
1224 if c.file.is_binary:
1224 raise HTTPFound(h.route_path('repo_files', repo_name=self.db_repo_name,
1225 raise HTTPFound(h.route_path('repo_files', repo_name=self.db_repo_name,
1225 commit_id=c.commit.raw_id, f_path=f_path))
1226 commit_id=c.commit.raw_id, f_path=f_path))
1226
1227
1227 _branch_name, _sha_commit_id, is_head = \
1228 _branch_name, _sha_commit_id, is_head = \
1228 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1229 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1229
1230
1230 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1231 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1231 self.check_branch_permission(_branch_name, commit_id=commit_id)
1232 self.check_branch_permission(_branch_name, commit_id=commit_id)
1232
1233
1233 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1234 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1234 c.f_path = f_path
1235 c.f_path = f_path
1235
1236
1236 old_content = c.file.content
1237 old_content = c.file.content
1237 sl = old_content.splitlines(1)
1238 sl = old_content.splitlines(1)
1238 first_line = sl[0] if sl else ''
1239 first_line = sl[0] if sl else ''
1239
1240
1240 r_post = self.request.POST
1241 r_post = self.request.POST
1241 # line endings: 0 - Unix, 1 - Mac, 2 - DOS
1242 # line endings: 0 - Unix, 1 - Mac, 2 - DOS
1242 line_ending_mode = detect_mode(first_line, 0)
1243 line_ending_mode = detect_mode(first_line, 0)
1243 content = convert_line_endings(r_post.get('content', ''), line_ending_mode)
1244 content = convert_line_endings(r_post.get('content', ''), line_ending_mode)
1244
1245
1245 message = r_post.get('message') or c.default_message
1246 message = r_post.get('message') or c.default_message
1246 org_node_path = c.file.unicode_path
1247 org_node_path = c.file.unicode_path
1247 filename = r_post['filename']
1248 filename = r_post['filename']
1248
1249
1249 root_path = c.file.dir_path
1250 root_path = c.file.dir_path
1250 pure_path = self.create_pure_path(root_path, filename)
1251 pure_path = self.create_pure_path(root_path, filename)
1251 node_path = safe_unicode(bytes(pure_path))
1252 node_path = safe_unicode(bytes(pure_path))
1252
1253
1253 default_redirect_url = h.route_path('repo_commit', repo_name=self.db_repo_name,
1254 default_redirect_url = h.route_path('repo_commit', repo_name=self.db_repo_name,
1254 commit_id=commit_id)
1255 commit_id=commit_id)
1255 if content == old_content and node_path == org_node_path:
1256 if content == old_content and node_path == org_node_path:
1256 h.flash(_('No changes detected on {}').format(h.escape(org_node_path)),
1257 h.flash(_('No changes detected on {}').format(h.escape(org_node_path)),
1257 category='warning')
1258 category='warning')
1258 raise HTTPFound(default_redirect_url)
1259 raise HTTPFound(default_redirect_url)
1259
1260
1260 try:
1261 try:
1261 mapping = {
1262 mapping = {
1262 org_node_path: {
1263 org_node_path: {
1263 'org_filename': org_node_path,
1264 'org_filename': org_node_path,
1264 'filename': node_path,
1265 'filename': node_path,
1265 'content': content,
1266 'content': content,
1266 'lexer': '',
1267 'lexer': '',
1267 'op': 'mod',
1268 'op': 'mod',
1268 'mode': c.file.mode
1269 'mode': c.file.mode
1269 }
1270 }
1270 }
1271 }
1271
1272
1272 commit = ScmModel().update_nodes(
1273 commit = ScmModel().update_nodes(
1273 user=self._rhodecode_db_user.user_id,
1274 user=self._rhodecode_db_user.user_id,
1274 repo=self.db_repo,
1275 repo=self.db_repo,
1275 message=message,
1276 message=message,
1276 nodes=mapping,
1277 nodes=mapping,
1277 parent_commit=c.commit,
1278 parent_commit=c.commit,
1278 )
1279 )
1279
1280
1280 h.flash(_('Successfully committed changes to file `{}`').format(
1281 h.flash(_('Successfully committed changes to file `{}`').format(
1281 h.escape(f_path)), category='success')
1282 h.escape(f_path)), category='success')
1282 default_redirect_url = h.route_path(
1283 default_redirect_url = h.route_path(
1283 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1284 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1284
1285
1285 except Exception:
1286 except Exception:
1286 log.exception('Error occurred during commit')
1287 log.exception('Error occurred during commit')
1287 h.flash(_('Error occurred during commit'), category='error')
1288 h.flash(_('Error occurred during commit'), category='error')
1288
1289
1289 raise HTTPFound(default_redirect_url)
1290 raise HTTPFound(default_redirect_url)
1290
1291
1291 @LoginRequired()
1292 @LoginRequired()
1292 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1293 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1293 @view_config(
1294 @view_config(
1294 route_name='repo_files_add_file', request_method='GET',
1295 route_name='repo_files_add_file', request_method='GET',
1295 renderer='rhodecode:templates/files/files_add.mako')
1296 renderer='rhodecode:templates/files/files_add.mako')
1296 @view_config(
1297 @view_config(
1297 route_name='repo_files_upload_file', request_method='GET',
1298 route_name='repo_files_upload_file', request_method='GET',
1298 renderer='rhodecode:templates/files/files_upload.mako')
1299 renderer='rhodecode:templates/files/files_upload.mako')
1299 def repo_files_add_file(self):
1300 def repo_files_add_file(self):
1300 _ = self.request.translate
1301 _ = self.request.translate
1301 c = self.load_default_context()
1302 c = self.load_default_context()
1302 commit_id, f_path = self._get_commit_and_path()
1303 commit_id, f_path = self._get_commit_and_path()
1303
1304
1304 self._ensure_not_locked()
1305 self._ensure_not_locked()
1305
1306
1306 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1307 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1307 if c.commit is None:
1308 if c.commit is None:
1308 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1309 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1309
1310
1310 if self.rhodecode_vcs_repo.is_empty():
1311 if self.rhodecode_vcs_repo.is_empty():
1311 # for empty repository we cannot check for current branch, we rely on
1312 # for empty repository we cannot check for current branch, we rely on
1312 # c.commit.branch instead
1313 # c.commit.branch instead
1313 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1314 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1314 else:
1315 else:
1315 _branch_name, _sha_commit_id, is_head = \
1316 _branch_name, _sha_commit_id, is_head = \
1316 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1317 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1317
1318
1318 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1319 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1319 self.check_branch_permission(_branch_name, commit_id=commit_id)
1320 self.check_branch_permission(_branch_name, commit_id=commit_id)
1320
1321
1321 c.default_message = (_('Added file via RhodeCode Enterprise'))
1322 c.default_message = (_('Added file via RhodeCode Enterprise'))
1322 c.f_path = f_path.lstrip('/') # ensure not relative path
1323 c.f_path = f_path.lstrip('/') # ensure not relative path
1323
1324
1324 return self._get_template_context(c)
1325 return self._get_template_context(c)
1325
1326
1326 @LoginRequired()
1327 @LoginRequired()
1327 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1328 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1328 @CSRFRequired()
1329 @CSRFRequired()
1329 @view_config(
1330 @view_config(
1330 route_name='repo_files_create_file', request_method='POST',
1331 route_name='repo_files_create_file', request_method='POST',
1331 renderer=None)
1332 renderer=None)
1332 def repo_files_create_file(self):
1333 def repo_files_create_file(self):
1333 _ = self.request.translate
1334 _ = self.request.translate
1334 c = self.load_default_context()
1335 c = self.load_default_context()
1335 commit_id, f_path = self._get_commit_and_path()
1336 commit_id, f_path = self._get_commit_and_path()
1336
1337
1337 self._ensure_not_locked()
1338 self._ensure_not_locked()
1338
1339
1339 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1340 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1340 if c.commit is None:
1341 if c.commit is None:
1341 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1342 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1342
1343
1343 # calculate redirect URL
1344 # calculate redirect URL
1344 if self.rhodecode_vcs_repo.is_empty():
1345 if self.rhodecode_vcs_repo.is_empty():
1345 default_redirect_url = h.route_path(
1346 default_redirect_url = h.route_path(
1346 'repo_summary', repo_name=self.db_repo_name)
1347 'repo_summary', repo_name=self.db_repo_name)
1347 else:
1348 else:
1348 default_redirect_url = h.route_path(
1349 default_redirect_url = h.route_path(
1349 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1350 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1350
1351
1351 if self.rhodecode_vcs_repo.is_empty():
1352 if self.rhodecode_vcs_repo.is_empty():
1352 # for empty repository we cannot check for current branch, we rely on
1353 # for empty repository we cannot check for current branch, we rely on
1353 # c.commit.branch instead
1354 # c.commit.branch instead
1354 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1355 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1355 else:
1356 else:
1356 _branch_name, _sha_commit_id, is_head = \
1357 _branch_name, _sha_commit_id, is_head = \
1357 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1358 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1358
1359
1359 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1360 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1360 self.check_branch_permission(_branch_name, commit_id=commit_id)
1361 self.check_branch_permission(_branch_name, commit_id=commit_id)
1361
1362
1362 c.default_message = (_('Added file via RhodeCode Enterprise'))
1363 c.default_message = (_('Added file via RhodeCode Enterprise'))
1363 c.f_path = f_path
1364 c.f_path = f_path
1364
1365
1365 r_post = self.request.POST
1366 r_post = self.request.POST
1366 message = r_post.get('message') or c.default_message
1367 message = r_post.get('message') or c.default_message
1367 filename = r_post.get('filename')
1368 filename = r_post.get('filename')
1368 unix_mode = 0
1369 unix_mode = 0
1369 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1370 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1370
1371
1371 if not filename:
1372 if not filename:
1372 # If there's no commit, redirect to repo summary
1373 # If there's no commit, redirect to repo summary
1373 if type(c.commit) is EmptyCommit:
1374 if type(c.commit) is EmptyCommit:
1374 redirect_url = h.route_path(
1375 redirect_url = h.route_path(
1375 'repo_summary', repo_name=self.db_repo_name)
1376 'repo_summary', repo_name=self.db_repo_name)
1376 else:
1377 else:
1377 redirect_url = default_redirect_url
1378 redirect_url = default_redirect_url
1378 h.flash(_('No filename specified'), category='warning')
1379 h.flash(_('No filename specified'), category='warning')
1379 raise HTTPFound(redirect_url)
1380 raise HTTPFound(redirect_url)
1380
1381
1381 root_path = f_path
1382 root_path = f_path
1382 pure_path = self.create_pure_path(root_path, filename)
1383 pure_path = self.create_pure_path(root_path, filename)
1383 node_path = safe_unicode(bytes(pure_path).lstrip('/'))
1384 node_path = safe_unicode(bytes(pure_path).lstrip('/'))
1384
1385
1385 author = self._rhodecode_db_user.full_contact
1386 author = self._rhodecode_db_user.full_contact
1386 nodes = {
1387 nodes = {
1387 node_path: {
1388 node_path: {
1388 'content': content
1389 'content': content
1389 }
1390 }
1390 }
1391 }
1391
1392
1392 try:
1393 try:
1393
1394
1394 commit = ScmModel().create_nodes(
1395 commit = ScmModel().create_nodes(
1395 user=self._rhodecode_db_user.user_id,
1396 user=self._rhodecode_db_user.user_id,
1396 repo=self.db_repo,
1397 repo=self.db_repo,
1397 message=message,
1398 message=message,
1398 nodes=nodes,
1399 nodes=nodes,
1399 parent_commit=c.commit,
1400 parent_commit=c.commit,
1400 author=author,
1401 author=author,
1401 )
1402 )
1402
1403
1403 h.flash(_('Successfully committed new file `{}`').format(
1404 h.flash(_('Successfully committed new file `{}`').format(
1404 h.escape(node_path)), category='success')
1405 h.escape(node_path)), category='success')
1405
1406
1406 default_redirect_url = h.route_path(
1407 default_redirect_url = h.route_path(
1407 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1408 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1408
1409
1409 except NonRelativePathError:
1410 except NonRelativePathError:
1410 log.exception('Non Relative path found')
1411 log.exception('Non Relative path found')
1411 h.flash(_('The location specified must be a relative path and must not '
1412 h.flash(_('The location specified must be a relative path and must not '
1412 'contain .. in the path'), category='warning')
1413 'contain .. in the path'), category='warning')
1413 raise HTTPFound(default_redirect_url)
1414 raise HTTPFound(default_redirect_url)
1414 except (NodeError, NodeAlreadyExistsError) as e:
1415 except (NodeError, NodeAlreadyExistsError) as e:
1415 h.flash(_(h.escape(e)), category='error')
1416 h.flash(_(h.escape(e)), category='error')
1416 except Exception:
1417 except Exception:
1417 log.exception('Error occurred during commit')
1418 log.exception('Error occurred during commit')
1418 h.flash(_('Error occurred during commit'), category='error')
1419 h.flash(_('Error occurred during commit'), category='error')
1419
1420
1420 raise HTTPFound(default_redirect_url)
1421 raise HTTPFound(default_redirect_url)
1421
1422
1422 @LoginRequired()
1423 @LoginRequired()
1423 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1424 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1424 @CSRFRequired()
1425 @CSRFRequired()
1425 @view_config(
1426 @view_config(
1426 route_name='repo_files_upload_file', request_method='POST',
1427 route_name='repo_files_upload_file', request_method='POST',
1427 renderer='json_ext')
1428 renderer='json_ext')
1428 def repo_files_upload_file(self):
1429 def repo_files_upload_file(self):
1429 _ = self.request.translate
1430 _ = self.request.translate
1430 c = self.load_default_context()
1431 c = self.load_default_context()
1431 commit_id, f_path = self._get_commit_and_path()
1432 commit_id, f_path = self._get_commit_and_path()
1432
1433
1433 self._ensure_not_locked()
1434 self._ensure_not_locked()
1434
1435
1435 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1436 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1436 if c.commit is None:
1437 if c.commit is None:
1437 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1438 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1438
1439
1439 # calculate redirect URL
1440 # calculate redirect URL
1440 if self.rhodecode_vcs_repo.is_empty():
1441 if self.rhodecode_vcs_repo.is_empty():
1441 default_redirect_url = h.route_path(
1442 default_redirect_url = h.route_path(
1442 'repo_summary', repo_name=self.db_repo_name)
1443 'repo_summary', repo_name=self.db_repo_name)
1443 else:
1444 else:
1444 default_redirect_url = h.route_path(
1445 default_redirect_url = h.route_path(
1445 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1446 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1446
1447
1447 if self.rhodecode_vcs_repo.is_empty():
1448 if self.rhodecode_vcs_repo.is_empty():
1448 # for empty repository we cannot check for current branch, we rely on
1449 # for empty repository we cannot check for current branch, we rely on
1449 # c.commit.branch instead
1450 # c.commit.branch instead
1450 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1451 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1451 else:
1452 else:
1452 _branch_name, _sha_commit_id, is_head = \
1453 _branch_name, _sha_commit_id, is_head = \
1453 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1454 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1454
1455
1455 error = self.forbid_non_head(is_head, f_path, json_mode=True)
1456 error = self.forbid_non_head(is_head, f_path, json_mode=True)
1456 if error:
1457 if error:
1457 return {
1458 return {
1458 'error': error,
1459 'error': error,
1459 'redirect_url': default_redirect_url
1460 'redirect_url': default_redirect_url
1460 }
1461 }
1461 error = self.check_branch_permission(_branch_name, json_mode=True)
1462 error = self.check_branch_permission(_branch_name, json_mode=True)
1462 if error:
1463 if error:
1463 return {
1464 return {
1464 'error': error,
1465 'error': error,
1465 'redirect_url': default_redirect_url
1466 'redirect_url': default_redirect_url
1466 }
1467 }
1467
1468
1468 c.default_message = (_('Uploaded file via RhodeCode Enterprise'))
1469 c.default_message = (_('Uploaded file via RhodeCode Enterprise'))
1469 c.f_path = f_path
1470 c.f_path = f_path
1470
1471
1471 r_post = self.request.POST
1472 r_post = self.request.POST
1472
1473
1473 message = c.default_message
1474 message = c.default_message
1474 user_message = r_post.getall('message')
1475 user_message = r_post.getall('message')
1475 if isinstance(user_message, list) and user_message:
1476 if isinstance(user_message, list) and user_message:
1476 # we take the first from duplicated results if it's not empty
1477 # we take the first from duplicated results if it's not empty
1477 message = user_message[0] if user_message[0] else message
1478 message = user_message[0] if user_message[0] else message
1478
1479
1479 nodes = {}
1480 nodes = {}
1480
1481
1481 for file_obj in r_post.getall('files_upload') or []:
1482 for file_obj in r_post.getall('files_upload') or []:
1482 content = file_obj.file
1483 content = file_obj.file
1483 filename = file_obj.filename
1484 filename = file_obj.filename
1484
1485
1485 root_path = f_path
1486 root_path = f_path
1486 pure_path = self.create_pure_path(root_path, filename)
1487 pure_path = self.create_pure_path(root_path, filename)
1487 node_path = safe_unicode(bytes(pure_path).lstrip('/'))
1488 node_path = safe_unicode(bytes(pure_path).lstrip('/'))
1488
1489
1489 nodes[node_path] = {
1490 nodes[node_path] = {
1490 'content': content
1491 'content': content
1491 }
1492 }
1492
1493
1493 if not nodes:
1494 if not nodes:
1494 error = 'missing files'
1495 error = 'missing files'
1495 return {
1496 return {
1496 'error': error,
1497 'error': error,
1497 'redirect_url': default_redirect_url
1498 'redirect_url': default_redirect_url
1498 }
1499 }
1499
1500
1500 author = self._rhodecode_db_user.full_contact
1501 author = self._rhodecode_db_user.full_contact
1501
1502
1502 try:
1503 try:
1503 commit = ScmModel().create_nodes(
1504 commit = ScmModel().create_nodes(
1504 user=self._rhodecode_db_user.user_id,
1505 user=self._rhodecode_db_user.user_id,
1505 repo=self.db_repo,
1506 repo=self.db_repo,
1506 message=message,
1507 message=message,
1507 nodes=nodes,
1508 nodes=nodes,
1508 parent_commit=c.commit,
1509 parent_commit=c.commit,
1509 author=author,
1510 author=author,
1510 )
1511 )
1511 if len(nodes) == 1:
1512 if len(nodes) == 1:
1512 flash_message = _('Successfully committed {} new files').format(len(nodes))
1513 flash_message = _('Successfully committed {} new files').format(len(nodes))
1513 else:
1514 else:
1514 flash_message = _('Successfully committed 1 new file')
1515 flash_message = _('Successfully committed 1 new file')
1515
1516
1516 h.flash(flash_message, category='success')
1517 h.flash(flash_message, category='success')
1517
1518
1518 default_redirect_url = h.route_path(
1519 default_redirect_url = h.route_path(
1519 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1520 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1520
1521
1521 except NonRelativePathError:
1522 except NonRelativePathError:
1522 log.exception('Non Relative path found')
1523 log.exception('Non Relative path found')
1523 error = _('The location specified must be a relative path and must not '
1524 error = _('The location specified must be a relative path and must not '
1524 'contain .. in the path')
1525 'contain .. in the path')
1525 h.flash(error, category='warning')
1526 h.flash(error, category='warning')
1526
1527
1527 return {
1528 return {
1528 'error': error,
1529 'error': error,
1529 'redirect_url': default_redirect_url
1530 'redirect_url': default_redirect_url
1530 }
1531 }
1531 except (NodeError, NodeAlreadyExistsError) as e:
1532 except (NodeError, NodeAlreadyExistsError) as e:
1532 error = h.escape(e)
1533 error = h.escape(e)
1533 h.flash(error, category='error')
1534 h.flash(error, category='error')
1534
1535
1535 return {
1536 return {
1536 'error': error,
1537 'error': error,
1537 'redirect_url': default_redirect_url
1538 'redirect_url': default_redirect_url
1538 }
1539 }
1539 except Exception:
1540 except Exception:
1540 log.exception('Error occurred during commit')
1541 log.exception('Error occurred during commit')
1541 error = _('Error occurred during commit')
1542 error = _('Error occurred during commit')
1542 h.flash(error, category='error')
1543 h.flash(error, category='error')
1543 return {
1544 return {
1544 'error': error,
1545 'error': error,
1545 'redirect_url': default_redirect_url
1546 'redirect_url': default_redirect_url
1546 }
1547 }
1547
1548
1548 return {
1549 return {
1549 'error': None,
1550 'error': None,
1550 'redirect_url': default_redirect_url
1551 'redirect_url': default_redirect_url
1551 }
1552 }
@@ -1,78 +1,81 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2015-2019 RhodeCode GmbH
3 # Copyright (C) 2015-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 from dogpile.cache import register_backend
22 from dogpile.cache import register_backend
23
23
24 register_backend(
24 register_backend(
25 "dogpile.cache.rc.memory_lru", "rhodecode.lib.rc_cache.backends",
25 "dogpile.cache.rc.memory_lru", "rhodecode.lib.rc_cache.backends",
26 "LRUMemoryBackend")
26 "LRUMemoryBackend")
27
27
28 register_backend(
28 register_backend(
29 "dogpile.cache.rc.file_namespace", "rhodecode.lib.rc_cache.backends",
29 "dogpile.cache.rc.file_namespace", "rhodecode.lib.rc_cache.backends",
30 "FileNamespaceBackend")
30 "FileNamespaceBackend")
31
31
32 register_backend(
32 register_backend(
33 "dogpile.cache.rc.redis", "rhodecode.lib.rc_cache.backends",
33 "dogpile.cache.rc.redis", "rhodecode.lib.rc_cache.backends",
34 "RedisPickleBackend")
34 "RedisPickleBackend")
35
35
36 register_backend(
36 register_backend(
37 "dogpile.cache.rc.redis_msgpack", "rhodecode.lib.rc_cache.backends",
37 "dogpile.cache.rc.redis_msgpack", "rhodecode.lib.rc_cache.backends",
38 "RedisMsgPackBackend")
38 "RedisMsgPackBackend")
39
39
40
40
41 log = logging.getLogger(__name__)
41 log = logging.getLogger(__name__)
42
42
43 from . import region_meta
43 from . import region_meta
44 from .utils import (
44 from .utils import (
45 get_default_cache_settings, backend_key_generator, get_or_create_region,
45 get_default_cache_settings, backend_key_generator, get_or_create_region,
46 clear_cache_namespace, make_region, InvalidationContext,
46 clear_cache_namespace, make_region, InvalidationContext,
47 FreshRegionCache, ActiveRegionCache)
47 FreshRegionCache, ActiveRegionCache)
48
48
49
49
50 FILE_TREE_CACHE_VER = 'v2'
51
52
50 def configure_dogpile_cache(settings):
53 def configure_dogpile_cache(settings):
51 cache_dir = settings.get('cache_dir')
54 cache_dir = settings.get('cache_dir')
52 if cache_dir:
55 if cache_dir:
53 region_meta.dogpile_config_defaults['cache_dir'] = cache_dir
56 region_meta.dogpile_config_defaults['cache_dir'] = cache_dir
54
57
55 rc_cache_data = get_default_cache_settings(settings, prefixes=['rc_cache.'])
58 rc_cache_data = get_default_cache_settings(settings, prefixes=['rc_cache.'])
56
59
57 # inspect available namespaces
60 # inspect available namespaces
58 avail_regions = set()
61 avail_regions = set()
59 for key in rc_cache_data.keys():
62 for key in rc_cache_data.keys():
60 namespace_name = key.split('.', 1)[0]
63 namespace_name = key.split('.', 1)[0]
61 avail_regions.add(namespace_name)
64 avail_regions.add(namespace_name)
62 log.debug('dogpile: found following cache regions: %s', avail_regions)
65 log.debug('dogpile: found following cache regions: %s', avail_regions)
63
66
64 # register them into namespace
67 # register them into namespace
65 for region_name in avail_regions:
68 for region_name in avail_regions:
66 new_region = make_region(
69 new_region = make_region(
67 name=region_name,
70 name=region_name,
68 function_key_generator=None
71 function_key_generator=None
69 )
72 )
70
73
71 new_region.configure_from_config(settings, 'rc_cache.{}.'.format(region_name))
74 new_region.configure_from_config(settings, 'rc_cache.{}.'.format(region_name))
72 new_region.function_key_generator = backend_key_generator(new_region.actual_backend)
75 new_region.function_key_generator = backend_key_generator(new_region.actual_backend)
73 log.debug('dogpile: registering a new region %s[%s]', region_name, new_region.__dict__)
76 log.debug('dogpile: registering a new region %s[%s]', region_name, new_region.__dict__)
74 region_meta.dogpile_cache_regions[region_name] = new_region
77 region_meta.dogpile_cache_regions[region_name] = new_region
75
78
76
79
77 def includeme(config):
80 def includeme(config):
78 configure_dogpile_cache(config.registry.settings)
81 configure_dogpile_cache(config.registry.settings)
General Comments 0
You need to be logged in to leave comments. Login now