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